Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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-10"
},
"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 = { name: aToken.source };
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;
}
}
}
}
26 changes: 26 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 { getFirstNonWhitespaceCharacterAfterOffset } from '../utils/strings';

interface YamlDiagnosticData {
schemaUri: string[];
Expand All @@ -43,6 +45,7 @@ export class YamlCodeActions {
result.push(...this.getConvertToBooleanActions(params.context.diagnostics, document));
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 @@ -163,6 +166,29 @@ 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 { name } = diag.data as { name: string };
const range = Range.create(diag.range.start, diag.range.end);
const lineContent = buffer.getLineContent(range.end.line);
const lastWhitespaceChar = getFirstNonWhitespaceCharacterAfterOffset(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;
}

private getConvertToBooleanActions(diagnostics: Diagnostic[], document: TextDocument): CodeAction[] {
const results: CodeAction[] = [];
for (const diagnostic of diagnostics) {
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