diff --git a/src/schema.ts b/src/schema.ts index 4368c26..885d398 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -1,14 +1,31 @@ import * as vscode from 'vscode'; -import { parseDocument, Document } from 'yaml'; +import { parseDocument, isMap, isSeq, Pair, Node, Scalar, YAMLMap, YAMLSeq, Document } from 'yaml'; import { getPackagePath } from './npm'; import Ajv from 'ajv'; -const findNodeByPath = (doc: Document.Parsed, path: string): any => { +const buildPathMap = (node: Node | null, path = '', map = new Map()): Map => { + if (!node) return map; + map.set(path, node); + if (isMap(node)) { + for (const item of (node as YAMLMap).items) { + const key = item.key instanceof Scalar ? String(item.key.value) : String(item.key); + const newPath = `${path}/${key}`; + if (item.value && typeof item.value === 'object') { + buildPathMap(item.value as Node, newPath, map); + } + } + } else if (isSeq(node)) { + (node as YAMLSeq).items.forEach((item, index) => { + const newPath = `${path}/${index}`; + if (item && typeof item === 'object') { + map.set(newPath, item as Node); + buildPathMap(item as Node, newPath, map); + } + }); + } - const parts = path.split('/').slice(1); // Remove leading slash - const node = doc.getIn(parts, true); // Get original YAML node, not JSON-converted - return node?.cstNode || node?.key?.cstNode || null; + return map; }; export function validate(context: vscode.ExtensionContext) { @@ -26,30 +43,34 @@ export function validate(context: vscode.ExtensionContext) { const diagnostics: vscode.Diagnostic[] = []; try { - const doc = parseDocument(text, { keepCstNodes: true }); - - const jsonData = doc.toJSON(); // Just for AJV + const doc = parseDocument(text, { keepSourceTokens: true }); + const pathMap = buildPathMap(doc.contents); + const jsonData = doc.toJSON(); const valid = ajv.validate(schema, jsonData); if (!valid && ajv.errors) { for (const error of ajv.errors) { - const cstNode = findNodeByPath(doc, error.instancePath || ''); - const range = cstNode?.rangeAsLinePos; + let node = pathMap.get(error.instancePath || ''); + if (!node && error.instancePath) { + const parentPath = error.instancePath.split('/').slice(0, -1).join('/'); + node = pathMap.get(parentPath || ''); + } + const cstNode = (node as any)?.cstNode; - if (range) { - const startPos = new vscode.Position(range.start.line, range.start.col); - const endPos = new vscode.Position(range.end.line, range.end.col); + if (cstNode?.range) { + const [startOffset, endOffset] = cstNode.range; + const startPos = document.positionAt(startOffset); + const endPos = document.positionAt(endOffset); - const diagnostic = new vscode.Diagnostic( + diagnostics.push(new vscode.Diagnostic( new vscode.Range(startPos, endPos), - error.instancePath + ' ' + error.message || 'Validation error', + `${error.instancePath || '/'} ${error.message ?? 'Validation error'}`, vscode.DiagnosticSeverity.Error - ); - diagnostics.push(diagnostic); + )); } else { diagnostics.push(new vscode.Diagnostic( - new vscode.Range(0, 0, 0, 999), - error.instancePath + ' ' + error.message || 'Validation error (no position info)', + new vscode.Range(0, 0, 0, 1), + `${error.instancePath || '/'} ${error.message ?? 'Validation error (no position info)'}`, vscode.DiagnosticSeverity.Error )); } @@ -57,7 +78,7 @@ export function validate(context: vscode.ExtensionContext) { } } catch (err: any) { diagnostics.push(new vscode.Diagnostic( - new vscode.Range(0, 0, 0, 999), + new vscode.Range(0, 0, 0, 1), `YAML Parse error: ${err.message}`, vscode.DiagnosticSeverity.Error )); diff --git a/test.js b/test.js new file mode 100644 index 0000000..e69de29