diff --git a/README.md b/README.md index 311c50eca..31b5b3733 100755 --- a/README.md +++ b/README.md @@ -179,6 +179,14 @@ yaml.schemas: { `yaml.schemas` allows you to specify json schemas that you want to validate against the yaml that you write. Kubernetes is an optional field. It does not require a url as the language server will provide that. You just need the keyword kubernetes and a glob pattern. +### Using `$schema` key to specify schema + +Like [VSCode](https://code.visualstudio.com/Docs/languages/json#_mapping-in-the-json), specifying schema via `$schema` key at root is supported. + +``` +$schema: +``` + ### Using inlined schema It is possible to specify a yaml schema using a modeline. diff --git a/src/languageservice/services/yamlSchemaService.ts b/src/languageservice/services/yamlSchemaService.ts index fa8ab5944..f88acba08 100644 --- a/src/languageservice/services/yamlSchemaService.ts +++ b/src/languageservice/services/yamlSchemaService.ts @@ -21,6 +21,7 @@ import { URI } from 'vscode-uri'; import * as nls from 'vscode-nls'; import { convertSimple2RegExpPattern } from '../utils/strings'; import { SingleYAMLDocument } from '../parser/yamlParser07'; +import { ObjectASTNode, StringASTNode } from '../jsonASTTypes'; import { JSONDocument } from '../parser/jsonParser07'; import * as yaml from 'js-yaml'; @@ -268,10 +269,10 @@ export class YAMLSchemaService extends JSONSchemaService { const seen: { [schemaId: string]: boolean } = Object.create(null); const schemas: string[] = []; - const schemaFromModeline = this.getSchemaFromModeline(doc); - if (schemaFromModeline !== undefined) { - schemas.push(schemaFromModeline); - seen[schemaFromModeline] = true; + const schemaFromFileContent = this.getSchemaFromFileContent(doc); + if (schemaFromFileContent !== undefined) { + schemas.push(schemaFromFileContent); + seen[schemaFromFileContent] = true; } for (const entry of this.filePatternAssociations) { @@ -364,12 +365,25 @@ export class YAMLSchemaService extends JSONSchemaService { } /** - * Retrieve schema if declared as modeline. + * Retrieve schema if declared in file content. * Public for testing purpose, not part of the API. * @param doc */ - public getSchemaFromModeline(doc: SingleYAMLDocument | JSONDocument): string { + public getSchemaFromFileContent(doc: SingleYAMLDocument | JSONDocument): string { + let schema = undefined; if (doc instanceof SingleYAMLDocument) { + const properties = (doc.root as ObjectASTNode)?.properties; + if (Array.isArray(properties)) { + for (const prop of properties) { + if (prop.keyNode.value == '$schema') { + schema = (prop.valueNode as StringASTNode).value; + } + } + } + if (schema !== undefined) { + return schema; + } + const yamlLanguageServerModeline = doc.lineComments.find((lineComment) => { const matchModeline = lineComment.match(/^#\s+yaml-language-server\s*:/g); return matchModeline !== null && matchModeline.length === 1; @@ -382,11 +396,11 @@ export class YAMLSchemaService extends JSONSchemaService { 'Several $schema attributes have been found on the yaml-language-server modeline. The first one will be picked.' ); } - return schemaMatchs[0].substring('$schema='.length); + schema = schemaMatchs[0].substring('$schema='.length); } } } - return undefined; + return schema; } private async resolveCustomSchema(schemaUri, doc): ResolvedSchema { diff --git a/test/schema.test.ts b/test/schema.test.ts index 74c1bbf39..935c2a4ac 100644 --- a/test/schema.test.ts +++ b/test/schema.test.ts @@ -538,7 +538,7 @@ suite('JSON Schema', () => { assert.equal(hello_world_schema, null); }); - describe('Test getSchemaFromModeline', function () { + describe('Test getSchemaFromFileContent', function () { test('simple case', async () => { checkReturnSchemaUrl('# yaml-language-server: $schema=expectedUrl', 'expectedUrl'); }); @@ -586,11 +586,22 @@ suite('JSON Schema', () => { checkReturnSchemaUrl('# yaml-language-server: $notschema=url1', undefined); }); - function checkReturnSchemaUrl(modeline: string, expectedResult: string): void { + test('schema key', async () => { + checkReturnSchemaUrl('$schema: expectedUrl', 'expectedUrl'); + }); + + test('schema key and simple case', async () => { + checkReturnSchemaUrl('# yaml-language-server: $schema=expectedUrl1\n$schema: expectedUrl2', 'expectedUrl2'); + }); + + test('schema key with wrong type', async () => { + checkReturnSchemaUrl('# yaml-language-server: $schema=expectedUrl1\n$schema: [expectedUrl2,expectedUrl3]', 'expectedUrl1'); + }); + + function checkReturnSchemaUrl(fileContent: string, expectedResult: string): void { const service = new SchemaService.YAMLSchemaService(schemaRequestServiceForURL, workspaceContext); - const yamlDoc = new parser.SingleYAMLDocument([]); - yamlDoc.lineComments = [modeline]; - assert.equal(service.getSchemaFromModeline(yamlDoc), expectedResult); + const yamlDoc = parser.parse(fileContent).documents[0]; + assert.equal(service.getSchemaFromFileContent(yamlDoc), expectedResult); } }); });