diff --git a/package.json b/package.json index ebed1794b..d6ed3eeec 100755 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/languageserver/handlers/validationHandlers.ts b/src/languageserver/handlers/validationHandlers.ts index 6621c6364..2de0030e8 100644 --- a/src/languageserver/handlers/validationHandlers.ts +++ b/src/languageserver/handlers/validationHandlers.ts @@ -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); diff --git a/src/languageservice/jsonASTTypes.ts b/src/languageservice/jsonASTTypes.ts index a82a01bcf..fa9bb8169 100644 --- a/src/languageservice/jsonASTTypes.ts +++ b/src/languageservice/jsonASTTypes.ts @@ -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 @@ -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; } diff --git a/src/languageservice/parser/ast-converter.ts b/src/languageservice/parser/ast-converter.ts index e95e513b4..6a0f6c610 100644 --- a/src/languageservice/parser/ast-converter.ts +++ b/src/languageservice/parser/ast-converter.ts @@ -19,7 +19,7 @@ import { Document, LineCounter, } from 'yaml'; -import { ASTNode } from '../jsonASTTypes'; +import { ASTNode, YamlNode } from '../jsonASTTypes'; import { NullASTNodeImpl, PropertyASTNodeImpl, @@ -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; diff --git a/src/languageservice/parser/jsonParser07.ts b/src/languageservice/parser/jsonParser07.ts index a1175b6d7..be9427c1c 100644 --- a/src/languageservice/parser/jsonParser07.ts +++ b/src/languageservice/parser/jsonParser07.ts @@ -16,6 +16,7 @@ import { StringASTNode, NullASTNode, PropertyASTNode, + YamlNode, } from '../jsonASTTypes'; import { ErrorCode } from 'vscode-json-languageservice'; import * as nls from 'vscode-nls'; @@ -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(); @@ -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; @@ -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; } diff --git a/src/languageservice/parser/yaml-documents.ts b/src/languageservice/parser/yaml-documents.ts index 588d90542..1d8ccfdc2 100644 --- a/src/languageservice/parser/yaml-documents.ts +++ b/src/languageservice/parser/yaml-documents.ts @@ -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'; @@ -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) { @@ -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; @@ -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); @@ -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); } } diff --git a/src/languageservice/parser/yamlParser07.ts b/src/languageservice/parser/yamlParser07.ts index 3424ea34a..19a2680b8 100644 --- a/src/languageservice/parser/yamlParser07.ts +++ b/src/languageservice/parser/yamlParser07.ts @@ -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(); diff --git a/src/languageservice/services/validation/types.ts b/src/languageservice/services/validation/types.ts new file mode 100644 index 000000000..c31bcfaad --- /dev/null +++ b/src/languageservice/services/validation/types.ts @@ -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 { + validate(document: TextDocument, yamlDoc: SingleYAMLDocument): Diagnostic[]; +} diff --git a/src/languageservice/services/validation/unused-anchors.ts b/src/languageservice/services/validation/unused-anchors.ts new file mode 100644 index 000000000..62d42680f --- /dev/null +++ b/src/languageservice/services/validation/unused-anchors.ts @@ -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(); + const usedAnchors = new Set(); + const anchorParent = new Map(); + + 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; + } + } + } +} diff --git a/src/languageservice/services/yamlCodeActions.ts b/src/languageservice/services/yamlCodeActions.ts index 61c85d2d4..cc7118156 100644 --- a/src/languageservice/services/yamlCodeActions.ts +++ b/src/languageservice/services/yamlCodeActions.ts @@ -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[]; @@ -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; } @@ -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) { diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts index 1c0246c56..3a638c309 100644 --- a/src/languageservice/services/yamlCompletion.ts +++ b/src/languageservice/services/yamlCompletion.ts @@ -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(); @@ -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)) { @@ -411,7 +412,7 @@ export class YamlCompletion { schema: ResolvedSchema, doc: SingleYAMLDocument, node: YAMLMap, - originalNode: Node, + originalNode: YamlNode, separatorAfter: string, collector: CompletionsCollector, textBuffer: TextBuffer, @@ -568,7 +569,7 @@ export class YamlCompletion { private getValueCompletions( schema: ResolvedSchema, doc: SingleYAMLDocument, - node: Node, + node: YamlNode, offset: number, document: TextDocument, collector: CompletionsCollector, diff --git a/src/languageservice/services/yamlValidation.ts b/src/languageservice/services/yamlValidation.ts index 262e364f6..51e86b0b5 100644 --- a/src/languageservice/services/yamlValidation.ts +++ b/src/languageservice/services/yamlValidation.ts @@ -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 @@ -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 { @@ -89,6 +93,7 @@ export class YAMLValidation { } validationResult.push(...validation); + validationResult.push(...this.additionalValidation.validate(textDocument, currentYAMLDoc)); index++; } } catch (err) { @@ -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; + } +} diff --git a/src/languageservice/utils/astUtils.ts b/src/languageservice/utils/astUtils.ts index 62d943c56..0c5778cdd 100644 --- a/src/languageservice/utils/astUtils.ts +++ b/src/languageservice/utils/astUtils.ts @@ -6,10 +6,11 @@ import { Document, isDocument, isScalar, Node, visit, YAMLMap, YAMLSeq } from 'yaml'; import { CollectionItem, SourceToken, Token } from 'yaml/dist/parse/cst'; import { VisitPath } from 'yaml/dist/parse/cst-visit'; +import { YamlNode } from '../jsonASTTypes'; type Visitor = (item: SourceToken, path: VisitPath) => number | symbol | Visitor | void; -export function getParent(doc: Document, nodeToFind: Node): Node | undefined { +export function getParent(doc: Document, nodeToFind: YamlNode): YamlNode | undefined { let parentNode: Node; visit(doc, (_, node: Node, path) => { if (node === nodeToFind) { @@ -37,7 +38,7 @@ export function isMapContainsEmptyPair(map: YAMLMap): boolean { return false; } -export function indexOf(seq: YAMLSeq, item: Node): number | undefined { +export function indexOf(seq: YAMLSeq, item: YamlNode): number | undefined { for (const [i, obj] of seq.items.entries()) { if (item === obj) { return i; @@ -79,7 +80,7 @@ export function isInComment(tokens: Token[], offset: number): boolean { return inComment; } -function isCollectionItem(token: unknown): token is CollectionItem { +export function isCollectionItem(token: unknown): token is CollectionItem { return token['start'] !== undefined; } diff --git a/src/languageservice/utils/strings.ts b/src/languageservice/utils/strings.ts index 2ad3ecaa0..726b260da 100644 --- a/src/languageservice/utils/strings.ts +++ b/src/languageservice/utils/strings.ts @@ -75,3 +75,16 @@ export function safeCreateUnicodeRegExp(pattern: string): RegExp { return new RegExp(pattern); } } + +export function getFirstNonWhitespaceCharacterAfterOffset(str: string, offset: number): number { + offset++; + for (let i = offset; i < str.length; i++) { + const char = str.charAt(i); + if (char === ' ' || char === '\t') { + offset++; + } else { + return offset; + } + } + return offset; +} diff --git a/test/utils/verifyError.ts b/test/utils/verifyError.ts index d96310e43..191a1a634 100644 --- a/test/utils/verifyError.ts +++ b/test/utils/verifyError.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DocumentSymbol, SymbolKind, InsertTextFormat, Range } from 'vscode-languageserver-types'; +import { DocumentSymbol, SymbolKind, InsertTextFormat, Range, DiagnosticTag } from 'vscode-languageserver-types'; import { CompletionItem, CompletionItemKind, SymbolInformation, Diagnostic, DiagnosticSeverity } from 'vscode-languageserver'; import { ErrorCode } from 'vscode-json-languageservice'; @@ -35,6 +35,28 @@ export function createDiagnosticWithData( return diagnostic; } +export function createUnusedAnchorDiagnostic( + message: string, + name: string, + startLine: number, + startCharacter: number, + endLine: number, + endCharacter: number +): Diagnostic { + const diagnostic = createExpectedError( + message, + startLine, + startCharacter, + endLine, + endCharacter, + DiagnosticSeverity.Hint, + 'YAML' + ); + diagnostic.tags = [DiagnosticTag.Unnecessary]; + diagnostic.data = { name }; + return diagnostic; +} + export function createExpectedSymbolInformation( name: string, kind: SymbolKind, diff --git a/test/yamlCodeActions.test.ts b/test/yamlCodeActions.test.ts index 8ca6b15e6..9d2b71453 100644 --- a/test/yamlCodeActions.test.ts +++ b/test/yamlCodeActions.test.ts @@ -19,7 +19,7 @@ import { WorkspaceEdit, } from 'vscode-languageserver'; import { setupTextDocument, TEST_URI } from './utils/testHelper'; -import { createDiagnosticWithData, createExpectedError } from './utils/verifyError'; +import { createDiagnosticWithData, createExpectedError, createUnusedAnchorDiagnostic } from './utils/verifyError'; import { YamlCommands } from '../src/commands'; import { LanguageSettings } from '../src'; @@ -154,4 +154,34 @@ describe('CodeActions Tests', () => { ]); }); }); + + describe('Remove Unused Anchor', () => { + it('should generate proper action', () => { + const doc = setupTextDocument('foo: &bar bar\n'); + const diagnostics = [createUnusedAnchorDiagnostic('Unused anchor "&bar"', '&bar', 0, 5, 0, 9)]; + const params: CodeActionParams = { + context: CodeActionContext.create(diagnostics), + range: undefined, + textDocument: TextDocumentIdentifier.create(TEST_URI), + }; + const actions = new YamlCodeActions(clientCapabilities); + const result = actions.getCodeAction(doc, params); + expect(result[0].title).to.be.equal('Delete unused anchor: &bar'); + expect(result[0].edit.changes[TEST_URI]).deep.equal([TextEdit.del(Range.create(0, 5, 0, 10))]); + }); + + it('should delete all whitespace after unused anchor', () => { + const doc = setupTextDocument('foo: &bar \tbar\n'); + const diagnostics = [createUnusedAnchorDiagnostic('Unused anchor "&bar"', '&bar', 0, 5, 0, 9)]; + const params: CodeActionParams = { + context: CodeActionContext.create(diagnostics), + range: undefined, + textDocument: TextDocumentIdentifier.create(TEST_URI), + }; + const actions = new YamlCodeActions(clientCapabilities); + const result = actions.getCodeAction(doc, params); + expect(result[0].title).to.be.equal('Delete unused anchor: &bar'); + expect(result[0].edit.changes[TEST_URI]).deep.equal([TextEdit.del(Range.create(0, 5, 0, 13))]); + }); + }); }); diff --git a/test/yamlValidation.test.ts b/test/yamlValidation.test.ts index 3cb1bcf8f..c98ff8586 100644 --- a/test/yamlValidation.test.ts +++ b/test/yamlValidation.test.ts @@ -8,7 +8,7 @@ import { SettingsState, TextDocumentTestManager } from '../src/yamlSettings'; import { ServiceSetup } from './utils/serviceSetup'; import { setupLanguageService, setupSchemaIDTextDocument } from './utils/testHelper'; import { expect } from 'chai'; -import { createExpectedError } from './utils/verifyError'; +import { createExpectedError, createUnusedAnchorDiagnostic } from './utils/verifyError'; describe('YAML Validation Tests', () => { let languageSettingsSetup: ServiceSetup; @@ -54,4 +54,42 @@ describe('YAML Validation Tests', () => { expect(result[0]).deep.equal(createExpectedError('Tabs are not allowed as indentation', 1, 1, 1, 10)); }); }); + + describe('Unused anchors diagnostics', () => { + it('should report unused anchor', async () => { + const yaml = 'foo: &bar bar\n'; + const result = await parseSetup(yaml); + expect(result).is.not.empty; + expect(result.length).to.be.equal(1); + expect(result[0]).deep.equal(createUnusedAnchorDiagnostic('Unused anchor "&bar"', '&bar', 0, 5, 0, 9)); + }); + + it('should not report used anchor', async () => { + const yaml = 'foo: &bar bar\nfff: *bar'; + const result = await parseSetup(yaml); + expect(result).is.empty; + }); + + it('should report unused anchors in array ', async () => { + const yaml = `foo: &bar doe +aaa: some +dd: *ba +some: + &a ss: ss +&aa ff: + - s + - o + - &e m + - e`; + const result = await parseSetup(yaml); + expect(result).is.not.empty; + expect(result.length).to.be.equal(4); + expect(result).to.include.deep.members([ + createUnusedAnchorDiagnostic('Unused anchor "&bar"', '&bar', 0, 5, 0, 9), + createUnusedAnchorDiagnostic('Unused anchor "&a"', '&a', 4, 2, 4, 4), + createUnusedAnchorDiagnostic('Unused anchor "&aa"', '&aa', 5, 0, 5, 3), + createUnusedAnchorDiagnostic('Unused anchor "&e"', '&e', 8, 4, 8, 6), + ]); + }); + }); }); diff --git a/yarn.lock b/yarn.lock index f14ba964b..1534d40c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2636,10 +2636,10 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@2.0.0-8: - version "2.0.0-8" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.0-8.tgz#226365f0d804ba7fb8cc2b527a00a7a4a3d8ea5f" - integrity sha512-QaYgJZMfWD6fKN/EYMk6w1oLWPCr1xj9QaPSZW5qkDb3y8nGCXhy2Ono+AF4F+CSL/vGcqswcAT0BaS//pgD2A== +yaml@2.0.0-10: + version "2.0.0-10" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.0-10.tgz#d5b59e2d14b8683313a534f2bbc648e211a2753e" + integrity sha512-FHV8s5ODFFQXX/enJEU2EkanNl1UDBUz8oa4k5Qo/sR+Iq7VmhCDkRMb0/mjJCNeAWQ31W8WV6PYStDE4d9EIw== yargs-parser@20.2.4: version "20.2.4"