mirror of
https://github.com/coder/code-server.git
synced 2026-06-21 09:27:11 +02:00
328 lines
12 KiB
TypeScript
328 lines
12 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||
*--------------------------------------------------------------------------------------------*/
|
||
import * as assert from 'assert';
|
||
import {
|
||
SyntaxKind, createScanner, parse, Node, ParseError, parseTree, ParseErrorCode, ParseOptions, ScanError
|
||
} from 'vs/base/common/json';
|
||
import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
|
||
|
||
function assertKinds(text: string, ...kinds: SyntaxKind[]): void {
|
||
let scanner = createScanner(text);
|
||
let kind: SyntaxKind;
|
||
while ((kind = scanner.scan()) !== SyntaxKind.EOF) {
|
||
assert.strictEqual(kind, kinds.shift());
|
||
}
|
||
assert.strictEqual(kinds.length, 0);
|
||
}
|
||
function assertScanError(text: string, expectedKind: SyntaxKind, scanError: ScanError): void {
|
||
let scanner = createScanner(text);
|
||
scanner.scan();
|
||
assert.strictEqual(scanner.getToken(), expectedKind);
|
||
assert.strictEqual(scanner.getTokenError(), scanError);
|
||
}
|
||
|
||
function assertValidParse(input: string, expected: any, options?: ParseOptions): void {
|
||
let errors: ParseError[] = [];
|
||
let actual = parse(input, errors, options);
|
||
|
||
if (errors.length !== 0) {
|
||
assert(false, getParseErrorMessage(errors[0].error));
|
||
}
|
||
assert.deepStrictEqual(actual, expected);
|
||
}
|
||
|
||
function assertInvalidParse(input: string, expected: any, options?: ParseOptions): void {
|
||
let errors: ParseError[] = [];
|
||
let actual = parse(input, errors, options);
|
||
|
||
assert(errors.length > 0);
|
||
assert.deepStrictEqual(actual, expected);
|
||
}
|
||
|
||
function assertTree(input: string, expected: any, expectedErrors: number[] = [], options?: ParseOptions): void {
|
||
let errors: ParseError[] = [];
|
||
let actual = parseTree(input, errors, options);
|
||
|
||
assert.deepStrictEqual(errors.map(e => e.error, expected), expectedErrors);
|
||
let checkParent = (node: Node) => {
|
||
if (node.children) {
|
||
for (let child of node.children) {
|
||
assert.strictEqual(node, child.parent);
|
||
delete (<any>child).parent; // delete to avoid recursion in deep equal
|
||
checkParent(child);
|
||
}
|
||
}
|
||
};
|
||
checkParent(actual);
|
||
|
||
assert.deepStrictEqual(actual, expected);
|
||
}
|
||
|
||
suite('JSON', () => {
|
||
test('tokens', () => {
|
||
assertKinds('{', SyntaxKind.OpenBraceToken);
|
||
assertKinds('}', SyntaxKind.CloseBraceToken);
|
||
assertKinds('[', SyntaxKind.OpenBracketToken);
|
||
assertKinds(']', SyntaxKind.CloseBracketToken);
|
||
assertKinds(':', SyntaxKind.ColonToken);
|
||
assertKinds(',', SyntaxKind.CommaToken);
|
||
});
|
||
|
||
test('comments', () => {
|
||
assertKinds('// this is a comment', SyntaxKind.LineCommentTrivia);
|
||
assertKinds('// this is a comment\n', SyntaxKind.LineCommentTrivia, SyntaxKind.LineBreakTrivia);
|
||
assertKinds('/* this is a comment*/', SyntaxKind.BlockCommentTrivia);
|
||
assertKinds('/* this is a \r\ncomment*/', SyntaxKind.BlockCommentTrivia);
|
||
assertKinds('/* this is a \ncomment*/', SyntaxKind.BlockCommentTrivia);
|
||
|
||
// unexpected end
|
||
assertKinds('/* this is a', SyntaxKind.BlockCommentTrivia);
|
||
assertKinds('/* this is a \ncomment', SyntaxKind.BlockCommentTrivia);
|
||
|
||
// broken comment
|
||
assertKinds('/ ttt', SyntaxKind.Unknown, SyntaxKind.Trivia, SyntaxKind.Unknown);
|
||
});
|
||
|
||
test('strings', () => {
|
||
assertKinds('"test"', SyntaxKind.StringLiteral);
|
||
assertKinds('"\\""', SyntaxKind.StringLiteral);
|
||
assertKinds('"\\/"', SyntaxKind.StringLiteral);
|
||
assertKinds('"\\b"', SyntaxKind.StringLiteral);
|
||
assertKinds('"\\f"', SyntaxKind.StringLiteral);
|
||
assertKinds('"\\n"', SyntaxKind.StringLiteral);
|
||
assertKinds('"\\r"', SyntaxKind.StringLiteral);
|
||
assertKinds('"\\t"', SyntaxKind.StringLiteral);
|
||
assertKinds('"\\v"', SyntaxKind.StringLiteral);
|
||
assertKinds('"\u88ff"', SyntaxKind.StringLiteral);
|
||
assertKinds('"\u2028"', SyntaxKind.StringLiteral);
|
||
|
||
// unexpected end
|
||
assertKinds('"test', SyntaxKind.StringLiteral);
|
||
assertKinds('"test\n"', SyntaxKind.StringLiteral, SyntaxKind.LineBreakTrivia, SyntaxKind.StringLiteral);
|
||
|
||
// invalid characters
|
||
assertScanError('"\t"', SyntaxKind.StringLiteral, ScanError.InvalidCharacter);
|
||
assertScanError('"\t "', SyntaxKind.StringLiteral, ScanError.InvalidCharacter);
|
||
});
|
||
|
||
test('numbers', () => {
|
||
assertKinds('0', SyntaxKind.NumericLiteral);
|
||
assertKinds('0.1', SyntaxKind.NumericLiteral);
|
||
assertKinds('-0.1', SyntaxKind.NumericLiteral);
|
||
assertKinds('-1', SyntaxKind.NumericLiteral);
|
||
assertKinds('1', SyntaxKind.NumericLiteral);
|
||
assertKinds('123456789', SyntaxKind.NumericLiteral);
|
||
assertKinds('10', SyntaxKind.NumericLiteral);
|
||
assertKinds('90', SyntaxKind.NumericLiteral);
|
||
assertKinds('90E+123', SyntaxKind.NumericLiteral);
|
||
assertKinds('90e+123', SyntaxKind.NumericLiteral);
|
||
assertKinds('90e-123', SyntaxKind.NumericLiteral);
|
||
assertKinds('90E-123', SyntaxKind.NumericLiteral);
|
||
assertKinds('90E123', SyntaxKind.NumericLiteral);
|
||
assertKinds('90e123', SyntaxKind.NumericLiteral);
|
||
|
||
// zero handling
|
||
assertKinds('01', SyntaxKind.NumericLiteral, SyntaxKind.NumericLiteral);
|
||
assertKinds('-01', SyntaxKind.NumericLiteral, SyntaxKind.NumericLiteral);
|
||
|
||
// unexpected end
|
||
assertKinds('-', SyntaxKind.Unknown);
|
||
assertKinds('.0', SyntaxKind.Unknown);
|
||
});
|
||
|
||
test('keywords: true, false, null', () => {
|
||
assertKinds('true', SyntaxKind.TrueKeyword);
|
||
assertKinds('false', SyntaxKind.FalseKeyword);
|
||
assertKinds('null', SyntaxKind.NullKeyword);
|
||
|
||
|
||
assertKinds('true false null',
|
||
SyntaxKind.TrueKeyword,
|
||
SyntaxKind.Trivia,
|
||
SyntaxKind.FalseKeyword,
|
||
SyntaxKind.Trivia,
|
||
SyntaxKind.NullKeyword);
|
||
|
||
// invalid words
|
||
assertKinds('nulllll', SyntaxKind.Unknown);
|
||
assertKinds('True', SyntaxKind.Unknown);
|
||
assertKinds('foo-bar', SyntaxKind.Unknown);
|
||
assertKinds('foo bar', SyntaxKind.Unknown, SyntaxKind.Trivia, SyntaxKind.Unknown);
|
||
});
|
||
|
||
test('trivia', () => {
|
||
assertKinds(' ', SyntaxKind.Trivia);
|
||
assertKinds(' \t ', SyntaxKind.Trivia);
|
||
assertKinds(' \t \n \t ', SyntaxKind.Trivia, SyntaxKind.LineBreakTrivia, SyntaxKind.Trivia);
|
||
assertKinds('\r\n', SyntaxKind.LineBreakTrivia);
|
||
assertKinds('\r', SyntaxKind.LineBreakTrivia);
|
||
assertKinds('\n', SyntaxKind.LineBreakTrivia);
|
||
assertKinds('\n\r', SyntaxKind.LineBreakTrivia, SyntaxKind.LineBreakTrivia);
|
||
assertKinds('\n \n', SyntaxKind.LineBreakTrivia, SyntaxKind.Trivia, SyntaxKind.LineBreakTrivia);
|
||
});
|
||
|
||
test('parse: literals', () => {
|
||
|
||
assertValidParse('true', true);
|
||
assertValidParse('false', false);
|
||
assertValidParse('null', null);
|
||
assertValidParse('"foo"', 'foo');
|
||
assertValidParse('"\\"-\\\\-\\/-\\b-\\f-\\n-\\r-\\t"', '"-\\-/-\b-\f-\n-\r-\t');
|
||
assertValidParse('"\\u00DC"', 'Ü');
|
||
assertValidParse('9', 9);
|
||
assertValidParse('-9', -9);
|
||
assertValidParse('0.129', 0.129);
|
||
assertValidParse('23e3', 23e3);
|
||
assertValidParse('1.2E+3', 1.2E+3);
|
||
assertValidParse('1.2E-3', 1.2E-3);
|
||
assertValidParse('1.2E-3 // comment', 1.2E-3);
|
||
});
|
||
|
||
test('parse: objects', () => {
|
||
assertValidParse('{}', {});
|
||
assertValidParse('{ "foo": true }', { foo: true });
|
||
assertValidParse('{ "bar": 8, "xoo": "foo" }', { bar: 8, xoo: 'foo' });
|
||
assertValidParse('{ "hello": [], "world": {} }', { hello: [], world: {} });
|
||
assertValidParse('{ "a": false, "b": true, "c": [ 7.4 ] }', { a: false, b: true, c: [7.4] });
|
||
assertValidParse('{ "lineComment": "//", "blockComment": ["/*", "*/"], "brackets": [ ["{", "}"], ["[", "]"], ["(", ")"] ] }', { lineComment: '//', blockComment: ['/*', '*/'], brackets: [['{', '}'], ['[', ']'], ['(', ')']] });
|
||
assertValidParse('{ "hello": [], "world": {} }', { hello: [], world: {} });
|
||
assertValidParse('{ "hello": { "again": { "inside": 5 }, "world": 1 }}', { hello: { again: { inside: 5 }, world: 1 } });
|
||
assertValidParse('{ "foo": /*hello*/true }', { foo: true });
|
||
});
|
||
|
||
test('parse: arrays', () => {
|
||
assertValidParse('[]', []);
|
||
assertValidParse('[ [], [ [] ]]', [[], [[]]]);
|
||
assertValidParse('[ 1, 2, 3 ]', [1, 2, 3]);
|
||
assertValidParse('[ { "a": null } ]', [{ a: null }]);
|
||
});
|
||
|
||
test('parse: objects with errors', () => {
|
||
assertInvalidParse('{,}', {});
|
||
assertInvalidParse('{ "foo": true, }', { foo: true }, { allowTrailingComma: false });
|
||
assertInvalidParse('{ "bar": 8 "xoo": "foo" }', { bar: 8, xoo: 'foo' });
|
||
assertInvalidParse('{ ,"bar": 8 }', { bar: 8 });
|
||
assertInvalidParse('{ ,"bar": 8, "foo" }', { bar: 8 });
|
||
assertInvalidParse('{ "bar": 8, "foo": }', { bar: 8 });
|
||
assertInvalidParse('{ 8, "foo": 9 }', { foo: 9 });
|
||
});
|
||
|
||
test('parse: array with errors', () => {
|
||
assertInvalidParse('[,]', []);
|
||
assertInvalidParse('[ 1, 2, ]', [1, 2], { allowTrailingComma: false });
|
||
assertInvalidParse('[ 1 2, 3 ]', [1, 2, 3]);
|
||
assertInvalidParse('[ ,1, 2, 3 ]', [1, 2, 3]);
|
||
assertInvalidParse('[ ,1, 2, 3, ]', [1, 2, 3], { allowTrailingComma: false });
|
||
});
|
||
|
||
test('parse: disallow commments', () => {
|
||
let options = { disallowComments: true };
|
||
|
||
assertValidParse('[ 1, 2, null, "foo" ]', [1, 2, null, 'foo'], options);
|
||
assertValidParse('{ "hello": [], "world": {} }', { hello: [], world: {} }, options);
|
||
|
||
assertInvalidParse('{ "foo": /*comment*/ true }', { foo: true }, options);
|
||
});
|
||
|
||
test('parse: trailing comma', () => {
|
||
// default is allow
|
||
assertValidParse('{ "hello": [], }', { hello: [] });
|
||
|
||
let options = { allowTrailingComma: true };
|
||
assertValidParse('{ "hello": [], }', { hello: [] }, options);
|
||
assertValidParse('{ "hello": [] }', { hello: [] }, options);
|
||
assertValidParse('{ "hello": [], "world": {}, }', { hello: [], world: {} }, options);
|
||
assertValidParse('{ "hello": [], "world": {} }', { hello: [], world: {} }, options);
|
||
assertValidParse('{ "hello": [1,] }', { hello: [1] }, options);
|
||
|
||
options = { allowTrailingComma: false };
|
||
assertInvalidParse('{ "hello": [], }', { hello: [] }, options);
|
||
assertInvalidParse('{ "hello": [], "world": {}, }', { hello: [], world: {} }, options);
|
||
});
|
||
|
||
test('tree: literals', () => {
|
||
assertTree('true', { type: 'boolean', offset: 0, length: 4, value: true });
|
||
assertTree('false', { type: 'boolean', offset: 0, length: 5, value: false });
|
||
assertTree('null', { type: 'null', offset: 0, length: 4, value: null });
|
||
assertTree('23', { type: 'number', offset: 0, length: 2, value: 23 });
|
||
assertTree('-1.93e-19', { type: 'number', offset: 0, length: 9, value: -1.93e-19 });
|
||
assertTree('"hello"', { type: 'string', offset: 0, length: 7, value: 'hello' });
|
||
});
|
||
|
||
test('tree: arrays', () => {
|
||
assertTree('[]', { type: 'array', offset: 0, length: 2, children: [] });
|
||
assertTree('[ 1 ]', { type: 'array', offset: 0, length: 5, children: [{ type: 'number', offset: 2, length: 1, value: 1 }] });
|
||
assertTree('[ 1,"x"]', {
|
||
type: 'array', offset: 0, length: 8, children: [
|
||
{ type: 'number', offset: 2, length: 1, value: 1 },
|
||
{ type: 'string', offset: 4, length: 3, value: 'x' }
|
||
]
|
||
});
|
||
assertTree('[[]]', {
|
||
type: 'array', offset: 0, length: 4, children: [
|
||
{ type: 'array', offset: 1, length: 2, children: [] }
|
||
]
|
||
});
|
||
});
|
||
|
||
test('tree: objects', () => {
|
||
assertTree('{ }', { type: 'object', offset: 0, length: 3, children: [] });
|
||
assertTree('{ "val": 1 }', {
|
||
type: 'object', offset: 0, length: 12, children: [
|
||
{
|
||
type: 'property', offset: 2, length: 8, colonOffset: 7, children: [
|
||
{ type: 'string', offset: 2, length: 5, value: 'val' },
|
||
{ type: 'number', offset: 9, length: 1, value: 1 }
|
||
]
|
||
}
|
||
]
|
||
});
|
||
assertTree('{"id": "$", "v": [ null, null] }',
|
||
{
|
||
type: 'object', offset: 0, length: 32, children: [
|
||
{
|
||
type: 'property', offset: 1, length: 9, colonOffset: 5, children: [
|
||
{ type: 'string', offset: 1, length: 4, value: 'id' },
|
||
{ type: 'string', offset: 7, length: 3, value: '$' }
|
||
]
|
||
},
|
||
{
|
||
type: 'property', offset: 12, length: 18, colonOffset: 15, children: [
|
||
{ type: 'string', offset: 12, length: 3, value: 'v' },
|
||
{
|
||
type: 'array', offset: 17, length: 13, children: [
|
||
{ type: 'null', offset: 19, length: 4, value: null },
|
||
{ type: 'null', offset: 25, length: 4, value: null }
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
);
|
||
assertTree('{ "id": { "foo": { } } , }',
|
||
{
|
||
type: 'object', offset: 0, length: 27, children: [
|
||
{
|
||
type: 'property', offset: 3, length: 20, colonOffset: 7, children: [
|
||
{ type: 'string', offset: 3, length: 4, value: 'id' },
|
||
{
|
||
type: 'object', offset: 9, length: 14, children: [
|
||
{
|
||
type: 'property', offset: 11, length: 10, colonOffset: 16, children: [
|
||
{ type: 'string', offset: 11, length: 5, value: 'foo' },
|
||
{ type: 'object', offset: 18, length: 3, children: [] }
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
, [ParseErrorCode.PropertyNameExpected, ParseErrorCode.ValueExpected], { allowTrailingComma: false });
|
||
});
|
||
});
|