Skip to content

Commit 38e8a2c

Browse files
author
Keegan Caruso
committed
Merge remote-tracking branch 'upstream/main' into users/keegancaruso/2019-09-schema
2 parents 5c8674a + f5de98b commit 38e8a2c

File tree

4 files changed

+68
-21
lines changed

4 files changed

+68
-21
lines changed

package-lock.json

Lines changed: 13 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/parser/jsonParser.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,17 @@ function validate(n: ASTNode | undefined, schema: JSONSchema, validationResult:
738738
valueMap.set(schema.const, []);
739739
}
740740
valueMap.get(schema.const)!.push(i);
741+
} else if (schema.enum && Array.isArray(schema.enum)) {
742+
for (const enumValue of schema.enum) {
743+
if (!constMap.has(key)) {
744+
constMap.set(key, new Map());
745+
}
746+
const valueMap = constMap.get(key)!;
747+
if (!valueMap.has(enumValue)) {
748+
valueMap.set(enumValue, []);
749+
}
750+
valueMap.get(enumValue)!.push(i);
751+
}
741752
}
742753
});
743754
}

src/test/completion.test.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -898,11 +898,13 @@ suite('JSON Completion', () => {
898898
]
899899
});
900900
await testCompletionsFor('{ "type": "1", "a" : { "x": "", "z":"" }, |', schema, {
901-
// both alternatives have errors: intellisense proposes all options
902-
count: 2,
901+
// Prior to the `enum` discriminator optimization, the parser failed to correlate `type: "1"`
902+
// via enums and fell back to offering completions from ALL `oneOf` branches (i.e., both 'b' and 'c').
903+
// Now that `enum` properties are properly indexed as discriminators, `type: "1"` flawlessly
904+
// isolates the first branch, so it correctly proposes ONLY the 'b' property from that branch.
905+
count: 1,
903906
items: [
904-
{ label: 'b' },
905-
{ label: 'c' }
907+
{ label: 'b' }
906908
]
907909
});
908910
await testCompletionsFor('{ "a" : { "x": "", "z":"" }, |', schema, {
@@ -1461,9 +1463,11 @@ suite('JSON Completion', () => {
14611463
]
14621464
});
14631465
await testCompletionsFor('{ "type": "foo|"', schema, {
1466+
// Since the user explicitly typed "foo", the parser instantly matches the enum discriminator
1467+
// for the first `oneOf` branch. Therefore, it discards the second branch (which expects "bar")
1468+
// and appropriately proposes ONLY "foo", rather than falling back to proposing both options.
14641469
items: [
1465-
{ label: '"foo"' },
1466-
{ label: '"bar"' }
1470+
{ label: '"foo"' }
14671471
]
14681472
});
14691473
});

src/test/parser.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3610,6 +3610,40 @@ suite('JSON Parser', () => {
36103610
assert.strictEqual(res.length, 0);
36113611
});
36123612

3613+
test('Validation should not take exponential time for recursive schemas with enum discriminators', async function() {
3614+
const schema: JSONSchema = {
3615+
$id: "http://example.com/schema",
3616+
definitions: {
3617+
rule: {
3618+
anyOf: [
3619+
{ type: "object", properties: { type: { enum: ["A"] }, content: { $ref: "#/definitions/rule" } } },
3620+
{ type: "object", properties: { type: { enum: ["B"] }, content: { $ref: "#/definitions/rule" } } },
3621+
{ type: "object", properties: { type: { enum: ["C"] }, content: { $ref: "#/definitions/rule" } } }
3622+
]
3623+
}
3624+
},
3625+
$ref: "#/definitions/rule"
3626+
};
3627+
3628+
// {"content": {"content": {"content": ... }}}
3629+
let nested = '{"type": "A"}';
3630+
for (let i = 0; i < 14; i++) {
3631+
nested = `{"type": "A", "content": ${nested}}`;
3632+
}
3633+
3634+
const { textDoc, jsonDoc } = toDocument(nested);
3635+
const ls = getLanguageService({});
3636+
ls.configure({ schemas: [{ fileMatch: ["*.json"], uri: "http://example.com/schema", schema }] });
3637+
3638+
const startTime = Date.now();
3639+
await ls.doValidation(textDoc, jsonDoc, undefined, schema);
3640+
const endTime = Date.now();
3641+
3642+
// Validation should finish well under 10ms. Before the `enum` optimization,
3643+
// this produced 4.7 million branch iterations and took nearly 5,000ms.
3644+
assert.ok(endTime - startTime < 100, "Validation took too long! Exponential scaling detected.");
3645+
});
3646+
36133647
test('$recursiveRef without $recursiveAnchor works like $ref', function () {
36143648
// $recursiveRef without $recursiveAnchor should behave like a regular $ref
36153649
const schema: JSONSchema = {

0 commit comments

Comments
 (0)