Skip to content

Commit 1014c4e

Browse files
Check nextEmbedded: '@pop' in action cases (issue#172061) (microsoft#172193)
1 parent 648281e commit 1014c4e

File tree

4 files changed

+96
-1
lines changed

4 files changed

+96
-1
lines changed

src/vs/editor/standalone/common/monarch/monarchCommon.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export interface IAction {
8080
// an action is either a group of actions
8181
group?: FuzzyAction[];
8282

83+
hasEmbeddedEndInCases?: boolean;
8384
// or a function that returns a fresh action
8485
test?: (id: string, matches: string[], state: string, eos: boolean) => FuzzyAction;
8586

src/vs/editor/standalone/common/monarch/monarchCompile.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* into a typed and checked ILexer definition.
99
*/
1010

11+
import { isString } from '../../../../base/common/types.js';
1112
import * as monarchCommon from './monarchCommon.js';
1213
import { IMonarchLanguage, IMonarchLanguageBracket } from './monarchTypes.js';
1314

@@ -337,6 +338,7 @@ function compileAction(lexer: monarchCommon.ILexerMin, ruleName: string, action:
337338
// build an array of test cases
338339
const cases: monarchCommon.IBranch[] = [];
339340

341+
let hasEmbeddedEndInCases = false;
340342
// for each case, push a test function and result value
341343
for (const tkey in action.cases) {
342344
if (action.cases.hasOwnProperty(tkey)) {
@@ -352,12 +354,17 @@ function compileAction(lexer: monarchCommon.ILexerMin, ruleName: string, action:
352354
else {
353355
cases.push(createGuard(lexer, ruleName, tkey, val)); // call separate function to avoid local variable capture
354356
}
357+
358+
if (!hasEmbeddedEndInCases) {
359+
hasEmbeddedEndInCases = !isString(val) && (val.hasEmbeddedEndInCases || ['@pop', '@popall'].includes(val.nextEmbedded || ''));
360+
}
355361
}
356362
}
357363

358364
// create a matching function
359365
const def = lexer.defaultToken;
360366
return {
367+
hasEmbeddedEndInCases,
361368
test: function (id, matches, state, eos) {
362369
for (const _case of cases) {
363370
const didmatch = (!_case.test || _case.test(id, matches, state, eos));

src/vs/editor/standalone/common/monarch/monarchLexer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ export class MonarchTokenizer extends Disposable implements languages.ITokenizat
514514
let hasEmbeddedPopRule = false;
515515

516516
for (const rule of rules) {
517-
if (!monarchCommon.isIAction(rule.action) || rule.action.nextEmbedded !== '@pop') {
517+
if (!monarchCommon.isIAction(rule.action) || !(rule.action.nextEmbedded === '@pop' || rule.action.hasEmbeddedEndInCases)) {
518518
continue;
519519
}
520520
hasEmbeddedPopRule = true;

src/vs/editor/standalone/test/browser/monarch.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,93 @@ suite('Monarch', () => {
112112
disposables.dispose();
113113
});
114114

115+
test('Test nextEmbedded: "@pop" in cases statement', () => {
116+
const disposables = new DisposableStore();
117+
const languageService = disposables.add(new LanguageService());
118+
const configurationService = new StandaloneConfigurationService(new NullLogService());
119+
disposables.add(languageService.registerLanguage({ id: 'sql' }));
120+
disposables.add(TokenizationRegistry.register('sql', disposables.add(createMonarchTokenizer(languageService, 'sql', {
121+
tokenizer: {
122+
root: [
123+
[/./, 'token']
124+
]
125+
}
126+
}, configurationService))));
127+
const SQL_QUERY_START = '(SELECT|INSERT|UPDATE|DELETE|CREATE|REPLACE|ALTER|WITH)';
128+
const tokenizer = disposables.add(createMonarchTokenizer(languageService, 'test1', {
129+
tokenizer: {
130+
root: [
131+
[`(\"\"\")${SQL_QUERY_START}`, [{ 'token': 'string.quote', }, { token: '@rematch', next: '@endStringWithSQL', nextEmbedded: 'sql', },]],
132+
[/(""")$/, [{ token: 'string.quote', next: '@maybeStringIsSQL', },]],
133+
],
134+
maybeStringIsSQL: [
135+
[/(.*)/, {
136+
cases: {
137+
[`${SQL_QUERY_START}\\b.*`]: { token: '@rematch', next: '@endStringWithSQL', nextEmbedded: 'sql', },
138+
'@default': { token: '@rematch', switchTo: '@endDblDocString', },
139+
}
140+
}],
141+
],
142+
endDblDocString: [
143+
['[^\']+', 'string'],
144+
['\\\\\'', 'string'],
145+
['\'\'\'', 'string', '@popall'],
146+
['\'', 'string']
147+
],
148+
endStringWithSQL: [[/"""/, {
149+
cases: {
150+
'"""': {
151+
cases: {
152+
'': { token: 'string.quote', next: '@popall', nextEmbedded: '@pop', }
153+
}
154+
},
155+
'@default': ''
156+
}
157+
}]],
158+
}
159+
}, configurationService));
160+
161+
const lines = [
162+
`mysql_query("""SELECT * FROM table_name WHERE ds = '<DATEID>'""")`,
163+
`mysql_query("""`,
164+
`SELECT *`,
165+
`FROM table_name`,
166+
`WHERE ds = '<DATEID>'`,
167+
`""")`,
168+
];
169+
170+
const actualTokens = getTokens(tokenizer, lines);
171+
172+
assert.deepStrictEqual(actualTokens, [
173+
[
174+
new Token(0, 'source.test1', 'test1'),
175+
new Token(12, 'string.quote.test1', 'test1'),
176+
new Token(15, 'token.sql', 'sql'),
177+
new Token(61, 'string.quote.test1', 'test1'),
178+
new Token(64, 'source.test1', 'test1')
179+
],
180+
[
181+
new Token(0, 'source.test1', 'test1'),
182+
new Token(12, 'string.quote.test1', 'test1')
183+
],
184+
[
185+
new Token(0, 'token.sql', 'sql')
186+
],
187+
[
188+
new Token(0, 'token.sql', 'sql')
189+
],
190+
[
191+
new Token(0, 'token.sql', 'sql')
192+
],
193+
[
194+
new Token(0, 'string.quote.test1', 'test1'),
195+
new Token(3, 'source.test1', 'test1')
196+
]
197+
]);
198+
disposables.dispose();
199+
});
200+
201+
115202
test('microsoft/monaco-editor#1235: Empty Line Handling', () => {
116203
const disposables = new DisposableStore();
117204
const configurationService = new StandaloneConfigurationService(new NullLogService());

0 commit comments

Comments
 (0)