Skip to content

Commit fa8371b

Browse files
langium generate: fixed a hidden bug in 'node-processor.ts' of Langium's 'generate' facility (#1814)
* added some test cases * extended 'Vitest: Run Selected File' launch config * brought config of the Vitest VSCode extension closer to the Vitest setup in 'vite.config.mts'
1 parent 4a985ce commit fa8371b

File tree

5 files changed

+91
-17
lines changed

5 files changed

+91
-17
lines changed

.vscode/launch.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,19 +158,25 @@
158158
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
159159
"program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
160160
"args": ["run", "--no-color", "--no-coverage", "--no-watch"],
161-
"smartStep": true,
162161
"console": "integratedTerminal",
162+
"smartStep": true,
163163
},
164164
{
165165
"name": "Vitest: Run Selected File",
166166
"type": "node",
167167
"request": "launch",
168168
"autoAttachChildProcesses": true,
169-
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
169+
"skipFiles": ["<node_internals>/**", "**/node_modules/**", "!**/node_modules/vscode-*/**"],
170170
"program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
171171
"args": ["run", "${relativeFile}"],
172-
"smartStep": true,
173172
"console": "integratedTerminal",
173+
"smartStep": true,
174+
"sourceMaps": true,
175+
"outFiles": [
176+
// cs: surprisingly, it makes a significant difference whether the "outFiles" property is not mentioned at all or an empty array is given here;
177+
// this setup seems to work best here, cross check with 'uri-utils.test.ts', for example
178+
// my assumption is that vitest now relies on it's on the fly generated source maps plus those being available in the folder of the particular js modules, like in case of external libraries
179+
]
174180
}
175181
]
176182
}

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
"javascript",
1212
"typescript"
1313
],
14-
"vitest.enable": true,
14+
"vitest.configSearchPatternExclude": "{**/node_modules/**,**/dist/**,**/generated/**,**/templates/**,**/examples/hello*/**,**/.*/**,**/*.d.ts}",
15+
"vitest.debugExclude": [
16+
"<node_internals>/**", "**/node_modules/**", "!**/node_modules/vscode-uri/**"
17+
],
1518
"[json]": {
1619
"editor.defaultFormatter": "vscode.json-language-features"
1720
},

packages/langium/src/generate/node-processor.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,12 @@ class Context {
9494
this.length -= this.lines[this.currentLineNumber].join('').length;
9595
this.lines[this.currentLineNumber] = [];
9696
this.pendingIndent = true;
97+
this.recentNonImmediateIndents.length = 0;
9798
}
9899

99100
addNewLine() {
100-
this.pendingIndent = true;
101101
this.lines.push([]);
102+
this.pendingIndent = true;
102103
this.recentNonImmediateIndents.length = 0;
103104
}
104105

@@ -228,20 +229,20 @@ function hasContent(node: GeneratorNode | string, ctx: Context): boolean {
228229

229230
function processStringNode(node: string, context: Context) {
230231
if (node) {
231-
if (context.pendingIndent) {
232-
handlePendingIndent(context, false);
233-
}
232+
handlePendingIndent(context, false);
234233
context.append(node);
235234
}
236235
}
237236

238237
function handlePendingIndent(ctx: Context, endOfLine: boolean) {
239-
let indent = '';
240-
for (const indentNode of ctx.relevantIndents.filter(e => e.indentEmptyLines || !endOfLine)) {
241-
indent += indentNode.indentation ?? ctx.defaultIndentation;
238+
if (ctx.pendingIndent) {
239+
let indent = '';
240+
for (const indentNode of ctx.relevantIndents.filter(e => e.indentEmptyLines || !endOfLine)) {
241+
indent += indentNode.indentation ?? ctx.defaultIndentation;
242+
}
243+
ctx.append(indent, true);
244+
ctx.pendingIndent = false;
242245
}
243-
ctx.append(indent, true);
244-
ctx.pendingIndent = false;
245246
}
246247

247248
function processCompositeNode(node: CompositeGeneratorNode, context: Context) {
@@ -288,9 +289,7 @@ function processNewLineNode(node: NewLineNode, context: Context) {
288289
if (node.ifNotEmpty && !hasNonWhitespace(context.currentLineContent)) {
289290
context.resetCurrentLine();
290291
} else {
291-
if (context.pendingIndent) {
292-
handlePendingIndent(context, true);
293-
}
292+
handlePendingIndent(context, true);
294293
context.append(node.lineDelimiter);
295294
context.addNewLine();
296295
}

packages/langium/test/generate/node.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,22 @@ describe('indentation', () => {
172172
expect(process(comp, '\t')).toBe(`No indent${EOL}\tIndent {${EOL}\t}${EOL}`);
173173
});
174174

175+
test('should indent nested template starting with a new line with \'ifNotEmpty\', with \'indentImmediately: false\'', () => {
176+
const comp = new CompositeGeneratorNode();
177+
comp.append('No indent', NL);
178+
comp.indent({
179+
indentImmediately: false,
180+
indentedChildren: [
181+
'\t',
182+
NLEmpty,
183+
'Indented',
184+
NL,
185+
'Indented',
186+
NL
187+
]
188+
});
189+
expect(process(comp, '\t')).toBe(`No indent${EOL}\tIndented${EOL}\tIndented${EOL}`);
190+
});
175191
});
176192

177193
describe('composite', () => {

packages/langium/test/generate/template-node.test.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -832,9 +832,47 @@ describe('Multiple nested substitution templates', () => {
832832
generated text!
833833
`);
834834
});
835+
836+
test('Nested substitution of with indented nested template starting with an _undefined_ line', () => {
837+
expect(
838+
toString(n`
839+
begin:
840+
${n`
841+
${undefined}
842+
${nestedNode}
843+
`}
844+
`)
845+
).toBe(
846+
s`
847+
begin:
848+
More
849+
generated text!
850+
`
851+
);
852+
});
853+
854+
test('Nested substitution of with indented nested template starting with an _empty string_ line', () => {
855+
expect(
856+
toString(n`
857+
begin:
858+
${n`
859+
${''}
860+
${nestedNode}
861+
`}
862+
`)
863+
).toBe(
864+
s`
865+
begin:
866+
${/* 's' automatically trims the empty lines completely, so insert */'<DUMMY>'}
867+
More
868+
generated text!
869+
`.replace('<DUMMY>', '')
870+
);
871+
});
872+
835873
});
836874

837-
describe('Embedded forEach loops', () => {
875+
describe('Joining lists', () => {
838876
test('ForEach loop with empty iterable', () => {
839877
const node = n`
840878
Data:
@@ -1083,6 +1121,18 @@ describe('Embedded forEach loops', () => {
10831121
b
10841122
`);
10851123
});
1124+
1125+
test('Nested ForEach loop with empty iterable followed by an indented line', () => {
1126+
const node = n`
1127+
${joinToNode([], { appendNewLineIfNotEmpty: true})}
1128+
a
1129+
`;
1130+
const text = toString(node);
1131+
expect(text).toBe(s`
1132+
a
1133+
`);
1134+
});
1135+
10861136
});
10871137

10881138
describe('Appending templates to existing nodes', () => {

0 commit comments

Comments
 (0)