Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"vscode-languageserver-types": "^3.16.0",
"vscode-nls": "^5.0.0",
"vscode-uri": "^3.0.2",
"yaml": "2.0.0-8"
"yaml": "2.0.0-9"
Comment thread
evidolob marked this conversation as resolved.
Outdated
},
"devDependencies": {
"@types/chai": "^4.2.12",
Expand Down
9 changes: 6 additions & 3 deletions src/languageserver/handlers/validationHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,12 @@ export class ValidationHandler {
.doValidation(textDocument, isKubernetesAssociatedDocument(textDocument, this.yamlSettings.specificValidatorPaths))
.then((diagnosticResults) => {
const diagnostics = [];
for (const diagnosticItem in diagnosticResults) {
diagnosticResults[diagnosticItem].severity = 1; //Convert all warnings to errors
diagnostics.push(diagnosticResults[diagnosticItem]);
for (const diagnosticItem of diagnosticResults) {
// Convert all warnings to errors
if (diagnosticItem.severity === 2) {
diagnosticItem.severity = 1;
}
diagnostics.push(diagnosticItem);
}

const removeDuplicatesDiagnostics = removeDuplicatesObj(diagnostics);
Expand Down
6 changes: 4 additions & 2 deletions src/languageservice/jsonASTTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Node } from 'yaml';
import { Node, Pair } from 'yaml';

export type YamlNode = Node | Pair;

export type ASTNode =
| ObjectASTNode
Expand All @@ -21,7 +23,7 @@ export interface BaseASTNode {
readonly length: number;
readonly children?: ASTNode[];
readonly value?: string | boolean | number | null;
readonly internalNode: Node;
readonly internalNode: YamlNode;
location: string;
getNodeFromOffsetEndInclusive(offset: number): ASTNode;
}
Expand Down
4 changes: 2 additions & 2 deletions src/languageservice/parser/ast-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
Document,
LineCounter,
} from 'yaml';
import { ASTNode } from '../jsonASTTypes';
import { ASTNode, YamlNode } from '../jsonASTTypes';
import {
NullASTNodeImpl,
PropertyASTNodeImpl,
Expand All @@ -35,7 +35,7 @@ type NodeRange = [number, number, number];
const maxRefCount = 1000;
let refDepth = 0;

export function convertAST(parent: ASTNode, node: Node, doc: Document, lineCounter: LineCounter): ASTNode {
export function convertAST(parent: ASTNode, node: YamlNode, doc: Document, lineCounter: LineCounter): ASTNode {
if (!parent) {
// first invocation
refDepth = 0;
Expand Down
9 changes: 5 additions & 4 deletions src/languageservice/parser/jsonParser07.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
StringASTNode,
NullASTNode,
PropertyASTNode,
YamlNode,
} from '../jsonASTTypes';
import { ErrorCode } from 'vscode-json-languageservice';
import * as nls from 'vscode-nls';
Expand All @@ -24,7 +25,7 @@ import { DiagnosticSeverity, Range } from 'vscode-languageserver-types';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { Diagnostic } from 'vscode-languageserver';
import { isArrayEqual } from '../utils/arrUtils';
import { Node } from 'yaml';
import { Node, Pair } from 'yaml';
import { safeCreateUnicodeRegExp } from '../utils/strings';

const localize = nls.loadMessageBundle();
Expand Down Expand Up @@ -89,9 +90,9 @@ export abstract class ASTNodeImpl {
public length: number;
public readonly parent: ASTNode;
public location: string;
readonly internalNode: Node;
readonly internalNode: YamlNode;

constructor(parent: ASTNode, internalNode: Node, offset: number, length?: number) {
constructor(parent: ASTNode, internalNode: YamlNode, offset: number, length?: number) {
this.offset = offset;
this.length = length;
this.parent = parent;
Expand Down Expand Up @@ -204,7 +205,7 @@ export class PropertyASTNodeImpl extends ASTNodeImpl implements PropertyASTNode
public valueNode: ASTNode;
public colonOffset: number;

constructor(parent: ObjectASTNode, internalNode: Node, offset: number, length?: number) {
constructor(parent: ObjectASTNode, internalNode: Pair, offset: number, length?: number) {
super(parent, internalNode, offset, length);
this.colonOffset = -1;
}
Expand Down
14 changes: 7 additions & 7 deletions src/languageservice/parser/yaml-documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { TextDocument } from 'vscode-languageserver-textdocument';
import { JSONDocument } from './jsonParser07';
import { Document, isNode, isPair, isScalar, LineCounter, visit, YAMLError } from 'yaml';
import { ASTNode } from '../jsonASTTypes';
import { ASTNode, YamlNode } from '../jsonASTTypes';
import { defaultOptions, parse as parseYAML, ParserOptions } from './yamlParser07';
import { ErrorCode } from 'vscode-json-languageservice';
import { Node } from 'yaml';
Expand Down Expand Up @@ -87,7 +87,7 @@ export class SingleYAMLDocument extends JSONDocument {
return matchingSchemas;
}

getNodeFromPosition(positionOffset: number, textBuffer: TextBuffer): [Node | undefined, boolean] {
getNodeFromPosition(positionOffset: number, textBuffer: TextBuffer): [YamlNode | undefined, boolean] {
const position = textBuffer.getPosition(positionOffset);
const lineContent = textBuffer.getLineContent(position.line);
if (lineContent.trim().length === 0) {
Expand All @@ -114,10 +114,10 @@ export class SingleYAMLDocument extends JSONDocument {
return [closestNode, false];
}

findClosestNode(offset: number, textBuffer: TextBuffer): Node {
findClosestNode(offset: number, textBuffer: TextBuffer): YamlNode {
let offsetDiff = this.internalDocument.range[2];
let maxOffset = this.internalDocument.range[0];
let closestNode: Node;
let closestNode: YamlNode;
visit(this.internalDocument, (key, node: Node) => {
if (!node) {
return;
Expand Down Expand Up @@ -149,11 +149,11 @@ export class SingleYAMLDocument extends JSONDocument {
return closestNode;
}

private getProperParentByIndentation(indentation: number, node: Node, textBuffer: TextBuffer): Node {
private getProperParentByIndentation(indentation: number, node: YamlNode, textBuffer: TextBuffer): YamlNode {
if (!node) {
return this.internalDocument.contents as Node;
}
if (node.range) {
if (isNode(node) && node.range) {
const position = textBuffer.getPosition(node.range[0]);
if (position.character > indentation && position.character > 0) {
const parent = this.getParent(node);
Expand All @@ -175,7 +175,7 @@ export class SingleYAMLDocument extends JSONDocument {
return node;
}

getParent(node: Node): Node | undefined {
getParent(node: YamlNode): YamlNode | undefined {
return getParent(this.internalDocument, node);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/languageservice/parser/yamlParser07.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export function parse(text: string, parserOptions: ParserOptions = defaultOption
strict: false,
customTags: getCustomTags(parserOptions.customTags),
version: parserOptions.yamlVersion,
keepSourceTokens: true,
};
const composer = new Composer(options);
const lineCounter = new LineCounter();
Expand Down
12 changes: 12 additions & 0 deletions src/languageservice/services/validation/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { TextDocument } from 'vscode-languageserver-textdocument';
import { Diagnostic } from 'vscode-languageserver-types';
import { SingleYAMLDocument } from '../../parser/yaml-documents';

export interface AdditionalValidator {
Comment thread
evidolob marked this conversation as resolved.
validate(document: TextDocument, yamlDoc: SingleYAMLDocument): Diagnostic[];
}
82 changes: 82 additions & 0 deletions src/languageservice/services/validation/unused-anchors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { TextDocument } from 'vscode-languageserver-textdocument';
import { Diagnostic, DiagnosticSeverity, DiagnosticTag, Range } from 'vscode-languageserver-types';
import { isAlias, isCollection, isNode, isScalar, Node, Scalar, visit, YAMLMap, YAMLSeq, CST, Pair } from 'yaml';
import { YamlNode } from '../../jsonASTTypes';
import { SingleYAMLDocument } from '../../parser/yaml-documents';
import { AdditionalValidator } from './types';
import { isCollectionItem } from '../../../languageservice/utils/astUtils';

export class UnusedAnchorsValidator implements AdditionalValidator {
validate(document: TextDocument, yamlDoc: SingleYAMLDocument): Diagnostic[] {
const result = [];
const anchors = new Set<Scalar | YAMLMap | YAMLSeq>();
const usedAnchors = new Set<Node>();
const anchorParent = new Map<Scalar | YAMLMap | YAMLSeq, Node | Pair>();

visit(yamlDoc.internalDocument, (key, node, path) => {
if (!isNode(node)) {
return;
}
if ((isCollection(node) || isScalar(node)) && node.anchor) {
anchors.add(node);
anchorParent.set(node, path[path.length - 1] as Node);
}
if (isAlias(node)) {
usedAnchors.add(node.resolve(yamlDoc.internalDocument));
}
});

for (const anchor of anchors) {
if (!usedAnchors.has(anchor)) {
const aToken = this.getAnchorNode(anchorParent.get(anchor));
if (aToken) {
const range = Range.create(
document.positionAt(aToken.offset),
document.positionAt(aToken.offset + aToken.source.length)
);
const warningDiagnostic = Diagnostic.create(range, `Unused anchor "${aToken.source}"`, DiagnosticSeverity.Hint, 0);
warningDiagnostic.tags = [DiagnosticTag.Unnecessary];
warningDiagnostic.data = { range, name: aToken.source };
Comment thread
evidolob marked this conversation as resolved.
Outdated
result.push(warningDiagnostic);
}
}
}

return result;
}
private getAnchorNode(parentNode: YamlNode): CST.SourceToken | undefined {
if (parentNode && parentNode.srcToken) {
const token = parentNode.srcToken;
if (isCollectionItem(token)) {
return getAnchorFromCollectionItem(token);
} else if (CST.isCollection(token)) {
for (const t of token.items) {
const anchor = getAnchorFromCollectionItem(t);
if (anchor) {
return anchor;
}
}
}
}
return undefined;
}
}
function getAnchorFromCollectionItem(token: CST.CollectionItem): CST.SourceToken | undefined {
for (const t of token.start) {
if (t.type === 'anchor') {
return t;
}
}
if (token.sep && Array.isArray(token.sep)) {
for (const t of token.sep) {
if (t.type === 'anchor') {
return t;
}
}
}
}
24 changes: 24 additions & 0 deletions src/languageservice/services/yamlCodeActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { YamlCommands } from '../../commands';
import * as path from 'path';
import { TextBuffer } from '../utils/textBuffer';
import { LanguageSettings } from '../yamlLanguageService';
import { YAML_SOURCE } from '../parser/jsonParser07';
import { getLastWhitespaceAfterChar } from '../utils/strings';

interface YamlDiagnosticData {
schemaUri: string[];
Expand All @@ -42,6 +44,7 @@ export class YamlCodeActions {

result.push(...this.getJumpToSchemaActions(params.context.diagnostics));
result.push(...this.getTabToSpaceConverting(params.context.diagnostics, document));
result.push(...this.getUnusedAnchorsDelete(params.context.diagnostics, document));

return result;
}
Expand Down Expand Up @@ -162,6 +165,27 @@ export class YamlCodeActions {

return result;
}

private getUnusedAnchorsDelete(diagnostics: Diagnostic[], document: TextDocument): CodeAction[] {
const result = [];
const buffer = new TextBuffer(document);
for (const diag of diagnostics) {
if (diag.message.startsWith('Unused anchor') && diag.source === YAML_SOURCE) {
const { range, name } = diag.data as { range: Range; name: string };
const lineContent = buffer.getLineContent(range.end.line);
const lastWhitespaceChar = getLastWhitespaceAfterChar(lineContent, range.end.character);
range.end.character = lastWhitespaceChar;
const action = CodeAction.create(
`Delete unused anchor: ${name}`,
createWorkspaceEdit(document.uri, [TextEdit.del(range)]),
CodeActionKind.QuickFix
);
action.diagnostics = [diag];
result.push(action);
}
}
return result;
}
}

function createWorkspaceEdit(uri: string, edits: TextEdit[]): WorkspaceEdit {
Expand Down
7 changes: 4 additions & 3 deletions src/languageservice/services/yamlCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { isInComment, isMapContainsEmptyPair } from '../utils/astUtils';
import { indexOf } from '../utils/astUtils';
import { isModeline } from './modelineUtil';
import { getSchemaTypeName } from '../utils/schemaUtils';
import { YamlNode } from '../jsonASTTypes';

const localize = nls.loadMessageBundle();

Expand Down Expand Up @@ -222,7 +223,7 @@ export class YamlCompletion {
return result;
}

let currentProperty: Node = null;
let currentProperty: YamlNode = null;

if (!node) {
if (!currentDoc.internalDocument.contents || isScalar(currentDoc.internalDocument.contents)) {
Expand Down Expand Up @@ -411,7 +412,7 @@ export class YamlCompletion {
schema: ResolvedSchema,
doc: SingleYAMLDocument,
node: YAMLMap,
originalNode: Node,
originalNode: YamlNode,
separatorAfter: string,
collector: CompletionsCollector,
textBuffer: TextBuffer,
Expand Down Expand Up @@ -568,7 +569,7 @@ export class YamlCompletion {
private getValueCompletions(
schema: ResolvedSchema,
doc: SingleYAMLDocument,
node: Node,
node: YamlNode,
offset: number,
document: TextDocument,
collector: CompletionsCollector,
Expand Down
22 changes: 21 additions & 1 deletion src/languageservice/services/yamlValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { YAML_SOURCE } from '../parser/jsonParser07';
import { TextBuffer } from '../utils/textBuffer';
import { yamlDocumentsCache } from '../parser/yaml-documents';
import { convertErrorToTelemetryMsg } from '../utils/objects';
import { AdditionalValidator } from './validation/types';
import { UnusedAnchorsValidator } from './validation/unused-anchors';

/**
* Convert a YAMLDocDiagnostic to a language server Diagnostic
Expand All @@ -41,12 +43,14 @@ export class YAMLValidation {
private jsonValidation;
private disableAdditionalProperties: boolean;
private yamlVersion: YamlVersion;
private additionalValidation: AdditionalValidation;

private MATCHES_MULTIPLE = 'Matches multiple schemas when only one must validate.';

public constructor(schemaService: YAMLSchemaService) {
constructor(schemaService: YAMLSchemaService) {
this.validationEnabled = true;
this.jsonValidation = new JSONValidation(schemaService, Promise);
this.additionalValidation = new AdditionalValidation();
}

public configure(settings: LanguageSettings): void {
Expand Down Expand Up @@ -89,6 +93,7 @@ export class YAMLValidation {
}

validationResult.push(...validation);
validationResult.push(...this.additionalValidation.validate(textDocument, currentYAMLDoc));
index++;
}
} catch (err) {
Expand Down Expand Up @@ -138,3 +143,18 @@ export class YAMLValidation {
return duplicateMessagesRemoved;
}
}

class AdditionalValidation {
private validators: AdditionalValidator[] = [];
constructor() {
this.validators.push(new UnusedAnchorsValidator());
}
validate(document: TextDocument, yarnDoc: SingleYAMLDocument): Diagnostic[] {
const result = [];

for (const validator of this.validators) {
result.push(...validator.validate(document, yarnDoc));
}
return result;
}
}
Loading