Skip to content

Commit 700db67

Browse files
authored
Merge pull request #197 from redhat-developer/fix-schema-sequence
Fix for schema sequences
2 parents c1c91bd + 6dc9a60 commit 700db67

9 files changed

+240
-106
lines changed

src/languageservice/jsonSchema04.ts

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ export interface JSONSchema {
4848
patternErrorMessage?: string; // VSCode extension
4949
deprecationMessage?: string; // VSCode extension
5050
enumDescriptions?: string[]; // VSCode extension
51+
52+
schemaSequence?: JSONSchema[]; // extension for multiple schemas related to multiple documents in single yaml file
5153
}
5254

5355
export interface JSONSchemaMap {

src/languageservice/jsonSchema07.ts

+2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ export interface JSONSchema {
7474
markdownDescription?: string; // VSCode extension
7575
doNotSuggest?: boolean; // VSCode extension
7676
allowComments?: boolean; // VSCode extension
77+
78+
schemaSequence?: JSONSchema[]; // extension for multiple schemas related to multiple documents in single yaml file
7779
}
7880

7981
export interface JSONSchemaMap {

src/languageservice/parser/yamlParser07.ts

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class SingleYAMLDocument extends JSONDocument {
2525
public errors;
2626
public warnings;
2727
public isKubernetes: boolean;
28+
public currentDocIndex: number;
2829

2930
constructor(lines: number[]) {
3031
super(null, []);

src/languageservice/services/yamlCompletion.ts

+1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export class YAMLCompletion {
133133
this.getCustomTagValueCompletions(collector);
134134
}
135135

136+
currentDoc.currentDocIndex = currentDocIndex;
136137
return this.schemaService.getSchemaForResource(document.uri, currentDoc).then(schema => {
137138

138139
if (!schema) {

src/languageservice/services/yamlHover.ts

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export class YAMLHover {
4343
return this.promise.resolve(void 0);
4444
}
4545

46+
const currentDocIndex = doc.documents.indexOf(currentDoc);
47+
currentDoc.currentDocIndex = currentDocIndex;
4648
return this.jsonHover.doHover(document, position, currentDoc);
4749
}
4850
}

src/languageservice/services/yamlSchemaService.ts

+152-5
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,21 @@
55
*--------------------------------------------------------------------------------------------*/
66
'use strict';
77

8-
import { JSONSchema } from '../jsonSchema07';
8+
import { JSONSchema, JSONSchemaMap, JSONSchemaRef } from '../jsonSchema07';
99
import { SchemaRequestService, WorkspaceContextService, PromiseConstructor, Thenable } from '../yamlLanguageService';
1010
import { UnresolvedSchema, ResolvedSchema, JSONSchemaService,
1111
SchemaDependencies, FilePatternAssociation, ISchemaContributions } from 'vscode-json-languageservice/lib/umd/services/jsonSchemaService';
1212

13+
import * as nls from 'vscode-nls';
14+
const localize = nls.loadMessageBundle();
15+
1316
export declare type CustomSchemaProvider = (uri: string) => Thenable<string>;
1417

1518
export class YAMLSchemaService extends JSONSchemaService {
1619

1720
private customSchemaProvider: CustomSchemaProvider | undefined;
1821
private filePatternAssociations: FilePatternAssociation[];
22+
private contextService: WorkspaceContextService;
1923

2024
constructor(requestService: SchemaRequestService, contextService?: WorkspaceContextService, promiseConstructor?: PromiseConstructor) {
2125
super(requestService, contextService, promiseConstructor);
@@ -26,6 +30,135 @@ export class YAMLSchemaService extends JSONSchemaService {
2630
this.customSchemaProvider = customSchemaProvider;
2731
}
2832

33+
//tslint:disable
34+
public resolveSchemaContent(schemaToResolve: UnresolvedSchema, schemaURL: string, dependencies: SchemaDependencies): Thenable<ResolvedSchema> {
35+
36+
let resolveErrors: string[] = schemaToResolve.errors.slice(0);
37+
let schema = schemaToResolve.schema;
38+
let contextService = this.contextService;
39+
40+
let findSection = (schema: JSONSchema, path: string): any => {
41+
if (!path) {
42+
return schema;
43+
}
44+
let current: any = schema;
45+
if (path[0] === '/') {
46+
path = path.substr(1);
47+
}
48+
path.split('/').some((part) => {
49+
current = current[part];
50+
return !current;
51+
});
52+
return current;
53+
};
54+
55+
let merge = (target: JSONSchema, sourceRoot: JSONSchema, sourceURI: string, path: string): void => {
56+
let section = findSection(sourceRoot, path);
57+
if (section) {
58+
for (let key in section) {
59+
if (section.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
60+
target[key] = section[key];
61+
}
62+
}
63+
} else {
64+
resolveErrors.push(localize('json.schema.invalidref', '$ref \'{0}\' in \'{1}\' can not be resolved.', path, sourceURI));
65+
}
66+
};
67+
68+
let resolveExternalLink = (node: JSONSchema, uri: string, linkPath: string, parentSchemaURL: string, parentSchemaDependencies: SchemaDependencies): Thenable<any> => {
69+
if (contextService && !/^\w+:\/\/.*/.test(uri)) {
70+
uri = contextService.resolveRelativePath(uri, parentSchemaURL);
71+
}
72+
uri = this.normalizeId(uri);
73+
const referencedHandle = this.getOrAddSchemaHandle(uri);
74+
return referencedHandle.getUnresolvedSchema().then(unresolvedSchema => {
75+
parentSchemaDependencies[uri] = true;
76+
if (unresolvedSchema.errors.length) {
77+
let loc = linkPath ? uri + '#' + linkPath : uri;
78+
resolveErrors.push(localize('json.schema.problemloadingref', 'Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0]));
79+
}
80+
merge(node, unresolvedSchema.schema, uri, linkPath);
81+
return resolveRefs(node, unresolvedSchema.schema, uri, referencedHandle.dependencies);
82+
});
83+
};
84+
85+
let resolveRefs = (node: JSONSchema, parentSchema: JSONSchema, parentSchemaURL: string, parentSchemaDependencies: SchemaDependencies): Thenable<any> => {
86+
if (!node || typeof node !== 'object') {
87+
return Promise.resolve(null);
88+
}
89+
90+
let toWalk: JSONSchema[] = [node];
91+
let seen: JSONSchema[] = [];
92+
93+
let openPromises: Thenable<any>[] = [];
94+
95+
let collectEntries = (...entries: JSONSchemaRef[]) => {
96+
for (let entry of entries) {
97+
if (typeof entry === 'object') {
98+
toWalk.push(entry);
99+
}
100+
}
101+
};
102+
let collectMapEntries = (...maps: JSONSchemaMap[]) => {
103+
for (let map of maps) {
104+
if (typeof map === 'object') {
105+
for (let key in map) {
106+
let entry = map[key];
107+
if (typeof entry === 'object') {
108+
toWalk.push(entry);
109+
}
110+
}
111+
}
112+
}
113+
};
114+
let collectArrayEntries = (...arrays: JSONSchemaRef[][]) => {
115+
for (let array of arrays) {
116+
if (Array.isArray(array)) {
117+
for (let entry of array) {
118+
if (typeof entry === 'object') {
119+
toWalk.push(entry);
120+
}
121+
}
122+
}
123+
}
124+
};
125+
let handleRef = (next: JSONSchema) => {
126+
let seenRefs = [];
127+
while (next.$ref) {
128+
const ref = next.$ref;
129+
let segments = ref.split('#', 2);
130+
delete next.$ref;
131+
if (segments[0].length > 0) {
132+
openPromises.push(resolveExternalLink(next, segments[0], segments[1], parentSchemaURL, parentSchemaDependencies));
133+
return;
134+
} else {
135+
if (seenRefs.indexOf(ref) === -1) {
136+
merge(next, parentSchema, parentSchemaURL, segments[1]); // can set next.$ref again, use seenRefs to avoid circle
137+
seenRefs.push(ref);
138+
}
139+
}
140+
}
141+
142+
collectEntries(<JSONSchema>next.items, <JSONSchema>next.additionalProperties, next.not, next.contains, next.propertyNames, next.if, next.then, next.else);
143+
collectMapEntries(next.definitions, next.properties, next.patternProperties, <JSONSchemaMap>next.dependencies);
144+
collectArrayEntries(next.anyOf, next.allOf, next.oneOf, <JSONSchema[]>next.items, next.schemaSequence);
145+
};
146+
147+
while (toWalk.length) {
148+
let next = toWalk.pop();
149+
if (seen.indexOf(next) >= 0) {
150+
continue;
151+
}
152+
seen.push(next);
153+
handleRef(next);
154+
}
155+
return Promise.all(openPromises);
156+
};
157+
158+
return resolveRefs(schema, schema, schemaURL, dependencies).then(_ => new ResolvedSchema(schema, resolveErrors));
159+
}
160+
//tslint:enable
161+
29162
public getSchemaForResource(resource: string, doc = undefined): Thenable<ResolvedSchema> {
30163
const resolveSchema = () => {
31164

@@ -43,7 +176,12 @@ export class YAMLSchemaService extends JSONSchemaService {
43176
}
44177

45178
if (schemas.length > 0) {
46-
return super.createCombinedSchema(resource, schemas).getResolvedSchema();
179+
return super.createCombinedSchema(resource, schemas).getResolvedSchema().then(schema => {
180+
if (schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[doc.currentDocIndex]) {
181+
return new ResolvedSchema(schema.schema.schemaSequence[doc.currentDocIndex]);
182+
}
183+
return schema;
184+
});
47185
}
48186

49187
return Promise.resolve(null);
@@ -56,7 +194,12 @@ export class YAMLSchemaService extends JSONSchemaService {
56194
}
57195

58196
return this.loadSchema(schemaUri)
59-
.then(unsolvedSchema => this.resolveSchemaContent(unsolvedSchema, schemaUri, []));
197+
.then(unsolvedSchema => this.resolveSchemaContent(unsolvedSchema, schemaUri, []).then(schema => {
198+
if (schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[doc.currentDocIndex]) {
199+
return new ResolvedSchema(schema.schema.schemaSequence[doc.currentDocIndex]);
200+
}
201+
return schema;
202+
}));
60203
})
61204
.then(schema => schema, err => resolveSchema());
62205
} else {
@@ -69,8 +212,12 @@ export class YAMLSchemaService extends JSONSchemaService {
69212
* to provide a wrapper around the javascript methods we are calling since they have no type
70213
*/
71214

72-
resolveSchemaContent(schemaToResolve: UnresolvedSchema, schemaURL: string, dependencies: SchemaDependencies): Thenable<ResolvedSchema> {
73-
return super.resolveSchemaContent(schemaToResolve, schemaURL, dependencies);
215+
normalizeId(id: string) {
216+
return super.normalizeId(id);
217+
}
218+
219+
getOrAddSchemaHandle(id: string, unresolvedSchemaContent?: JSONSchema) {
220+
return super.getOrAddSchemaHandle(id, unresolvedSchemaContent);
74221
}
75222

76223
// tslint:disable-next-line: no-any

src/languageservice/services/yamlValidation.ts

+3
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@ export class YAMLValidation {
4141
}
4242
const yamlDocument: YAMLDocument = parseYAML(textDocument.getText(), this.customTags);
4343
const validationResult: Diagnostic[] = [];
44+
let index = 0;
4445
for (const currentYAMLDoc of yamlDocument.documents) {
4546
currentYAMLDoc.isKubernetes = isKubernetes;
47+
currentYAMLDoc.currentDocIndex = index;
4648

4749
const validation = await this.jsonValidation.doValidation(textDocument, currentYAMLDoc);
4850
const syd = currentYAMLDoc as unknown as SingleYAMLDocument;
@@ -51,6 +53,7 @@ export class YAMLValidation {
5153
}
5254

5355
validationResult.push(...validation);
56+
index++;
5457
}
5558

5659
const foundSignatures = new Set();

test/fixtures/customMultipleSchemaSequences.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"oneOf": [
2+
"schemaSequence": [
33
{
44
"$schema": "http://json-schema.org/draft-04/schema#",
55
"description": "Person object",
@@ -14,7 +14,7 @@
1414
"description": "The age of this person"
1515
}
1616
},
17-
"required":["name","age"]
17+
"required": ["name","age"]
1818
},
1919
{
2020
"$ref": "http://json.schemastore.org/bowerrc"

0 commit comments

Comments
 (0)