Path: blob/main/src/vs/platform/contextkey/test/common/parser.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/4import assert from 'assert';5import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';6import { Parser } from '../../common/contextkey.js';78function parseToStr(input: string): string {9const parser = new Parser();1011const prints: string[] = [];1213const print = (...ss: string[]) => { ss.forEach(s => prints.push(s)); };1415const expr = parser.parse(input);16if (expr === undefined) {17if (parser.lexingErrors.length > 0) {18print('Lexing errors:', '\n\n');19parser.lexingErrors.forEach(lexingError => print(`Unexpected token '${lexingError.lexeme}' at offset ${lexingError.offset}. ${lexingError.additionalInfo}`, '\n'));20}2122if (parser.parsingErrors.length > 0) {23if (parser.lexingErrors.length > 0) { print('\n --- \n'); }24print('Parsing errors:', '\n\n');25parser.parsingErrors.forEach(parsingError => print(`Unexpected '${parsingError.lexeme}' at offset ${parsingError.offset}.`, '\n'));26}2728} else {29print(expr.serialize());30}3132return prints.join('');33}3435suite('Context Key Parser', () => {3637ensureNoDisposablesAreLeakedInTestSuite();3839test(' foo', () => {40const input = ' foo';41assert.deepStrictEqual(parseToStr(input), "foo");42});4344test('!foo', () => {45const input = '!foo';46assert.deepStrictEqual(parseToStr(input), "!foo");47});4849test('foo =~ /bar/', () => {50const input = 'foo =~ /bar/';51assert.deepStrictEqual(parseToStr(input), "foo =~ /bar/");52});5354test(`foo || (foo =~ /bar/ && baz)`, () => {55const input = `foo || (foo =~ /bar/ && baz)`;56assert.deepStrictEqual(parseToStr(input), "foo || baz && foo =~ /bar/");57});5859test('foo || (foo =~ /bar/ || baz)', () => {60const input = 'foo || (foo =~ /bar/ || baz)';61assert.deepStrictEqual(parseToStr(input), "baz || foo || foo =~ /bar/");62});6364test(`(foo || bar) && (jee || jar)`, () => {65const input = `(foo || bar) && (jee || jar)`;66assert.deepStrictEqual(parseToStr(input), "bar && jar || bar && jee || foo && jar || foo && jee");67});6869test('foo && foo =~ /zee/i', () => {70const input = 'foo && foo =~ /zee/i';71assert.deepStrictEqual(parseToStr(input), "foo && foo =~ /zee/i");72});7374test('foo.bar==enabled', () => {75const input = 'foo.bar==enabled';76assert.deepStrictEqual(parseToStr(input), "foo.bar == 'enabled'");77});7879test(`foo.bar == 'enabled'`, () => {80const input = `foo.bar == 'enabled'`;81assert.deepStrictEqual(parseToStr(input), `foo.bar == 'enabled'`);82});8384test('foo.bar:zed==completed - equality with no space', () => {85const input = 'foo.bar:zed==completed';86assert.deepStrictEqual(parseToStr(input), "foo.bar:zed == 'completed'");87});8889test('a && b || c', () => {90const input = 'a && b || c';91assert.deepStrictEqual(parseToStr(input), "c || a && b");92});9394test('fooBar && baz.jar && fee.bee<K-loo+1>', () => {95const input = 'fooBar && baz.jar && fee.bee<K-loo+1>';96assert.deepStrictEqual(parseToStr(input), "baz.jar && fee.bee<K-loo+1> && fooBar");97});9899test('foo.barBaz<C-r> < 2', () => {100const input = 'foo.barBaz<C-r> < 2';101assert.deepStrictEqual(parseToStr(input), `foo.barBaz<C-r> < 2`);102});103104test('foo.bar >= -1', () => {105const input = 'foo.bar >= -1';106assert.deepStrictEqual(parseToStr(input), "foo.bar >= -1");107});108109test(`key contains  : view == vsc-packages-activitybar-folders && vsc-packages-folders-loaded`, () => {110const input = `view == vsc-packages-activitybar-folders && vsc-packages-folders-loaded`;111assert.deepStrictEqual(parseToStr(input), "vsc-packages-folders-loaded && view == 'vsc-packages-activitybar-folders'");112});113114test('foo.bar <= -1', () => {115const input = 'foo.bar <= -1';116assert.deepStrictEqual(parseToStr(input), `foo.bar <= -1`);117});118119test('!cmake:hideBuildCommand \u0026\u0026 cmake:enableFullFeatureSet', () => {120const input = '!cmake:hideBuildCommand \u0026\u0026 cmake:enableFullFeatureSet';121assert.deepStrictEqual(parseToStr(input), "cmake:enableFullFeatureSet && !cmake:hideBuildCommand");122});123124test('!(foo && bar)', () => {125const input = '!(foo && bar)';126assert.deepStrictEqual(parseToStr(input), "!bar || !foo");127});128129test('!(foo && bar || boar) || deer', () => {130const input = '!(foo && bar || boar) || deer';131assert.deepStrictEqual(parseToStr(input), "deer || !bar && !boar || !boar && !foo");132});133134test(`!(!foo)`, () => {135const input = `!(!foo)`;136assert.deepStrictEqual(parseToStr(input), "foo");137});138139suite('controversial', () => {140/*141new parser KEEPS old one's behavior:142143old parser output: { key: 'debugState', op: '==', value: '"stopped"' }144new parser output: { key: 'debugState', op: '==', value: '"stopped"' }145146TODO@ulugbekna: we should consider breaking old parser's behavior, and not take double quotes as part of the `value` because that's not what user expects.147*/148test(`debugState == "stopped"`, () => {149const input = `debugState == "stopped"`;150assert.deepStrictEqual(parseToStr(input), "debugState == '\"stopped\"'");151});152153/*154new parser BREAKS old one's behavior:155156old parser output: { key: 'viewItem', op: '==', value: 'VSCode WorkSpace' }157new parser output: { key: 'viewItem', op: '==', value: 'VSCode' }158159TODO@ulugbekna: since this's breaking, we can have hacky code that tries detecting such cases and replicate old parser's behavior.160*/161test(` viewItem == VSCode WorkSpace`, () => {162const input = ` viewItem == VSCode WorkSpace`;163assert.deepStrictEqual(parseToStr(input), "Parsing errors:\n\nUnexpected 'WorkSpace' at offset 20.\n");164});165166167});168169suite('regex', () => {170171test(`resource =~ //foo/(barr|door/(Foo-Bar%20Templates|Soo%20Looo)|Web%20Site%Jjj%20Llll)(/.*)*$/`, () => {172const input = `resource =~ //foo/(barr|door/(Foo-Bar%20Templates|Soo%20Looo)|Web%20Site%Jjj%20Llll)(/.*)*$/`;173assert.deepStrictEqual(parseToStr(input), "resource =~ /\\/foo\\/(barr|door\\/(Foo-Bar%20Templates|Soo%20Looo)|Web%20Site%Jjj%20Llll)(\\/.*)*$/");174});175176test(`resource =~ /((/scratch/(?!update)(.*)/)|((/src/).*/)).*$/`, () => {177const input = `resource =~ /((/scratch/(?!update)(.*)/)|((/src/).*/)).*$/`;178assert.deepStrictEqual(parseToStr(input), "resource =~ /((\\/scratch\\/(?!update)(.*)\\/)|((\\/src\\/).*\\/)).*$/");179});180181test(`resourcePath =~ /\.md(\.yml|\.txt)*$/giym`, () => {182const input = `resourcePath =~ /\.md(\.yml|\.txt)*$/giym`;183assert.deepStrictEqual(parseToStr(input), "resourcePath =~ /.md(.yml|.txt)*$/im");184});185186});187188suite('error handling', () => {189190test(`/foo`, () => {191const input = `/foo`;192assert.deepStrictEqual(parseToStr(input), "Lexing errors:\n\nUnexpected token '/foo' at offset 0. Did you forget to escape the '/' (slash) character? Put two backslashes before it to escape, e.g., '\\\\/'.\n\n --- \nParsing errors:\n\nUnexpected '/foo' at offset 0.\n");193});194195test(`!b == 'true'`, () => {196const input = `!b == 'true'`;197assert.deepStrictEqual(parseToStr(input), "Parsing errors:\n\nUnexpected '==' at offset 3.\n");198});199200test('!foo && in bar', () => {201const input = '!foo && in bar';202assert.deepStrictEqual(parseToStr(input), "Parsing errors:\n\nUnexpected 'in' at offset 9.\n");203});204205test('vim<c-r> == 1 && vim<2<=3', () => {206const input = 'vim<c-r> == 1 && vim<2<=3';207assert.deepStrictEqual(parseToStr(input), "Lexing errors:\n\nUnexpected token '=' at offset 23. Did you mean == or =~?\n\n --- \nParsing errors:\n\nUnexpected '=' at offset 23.\n"); // FIXME208});209210test(`foo && 'bar`, () => {211const input = `foo && 'bar`;212assert.deepStrictEqual(parseToStr(input), "Lexing errors:\n\nUnexpected token ''bar' at offset 7. Did you forget to open or close the quote?\n\n --- \nParsing errors:\n\nUnexpected ''bar' at offset 7.\n");213});214215test(`config.foo && &&bar =~ /^foo$|^bar-foo$|^joo$|^jar$/ && !foo`, () => {216const input = `config.foo && &&bar =~ /^foo$|^bar-foo$|^joo$|^jar$/ && !foo`;217assert.deepStrictEqual(parseToStr(input), "Parsing errors:\n\nUnexpected '&&' at offset 15.\n");218});219220test(`!foo == 'test'`, () => {221const input = `!foo == 'test'`;222assert.deepStrictEqual(parseToStr(input), "Parsing errors:\n\nUnexpected '==' at offset 5.\n");223});224225test(`!!foo`, function () {226const input = `!!foo`;227assert.deepStrictEqual(parseToStr(input), "Parsing errors:\n\nUnexpected '!' at offset 1.\n");228});229230});231232});233234235