diff --git a/.vscode/launch.json b/.vscode/launch.json index 45d81df38..2b9cf04c2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -158,19 +158,25 @@ "skipFiles": ["/**", "**/node_modules/**"], "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", "args": ["run", "--no-color", "--no-coverage", "--no-watch"], - "smartStep": true, "console": "integratedTerminal", + "smartStep": true, }, { "name": "Vitest: Run Selected File", "type": "node", "request": "launch", "autoAttachChildProcesses": true, - "skipFiles": ["/**", "**/node_modules/**"], + "skipFiles": ["/**", "**/node_modules/**", "!**/node_modules/vscode-*/**"], "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", "args": ["run", "${relativeFile}"], - "smartStep": true, "console": "integratedTerminal", + "smartStep": true, + "sourceMaps": true, + "outFiles": [ + // cs: surprisingly, it makes a significant difference whether the "outFiles" property is not mentioned at all or an empty array is given here; + // this setup seems to work best here, cross check with 'uri-utils.test.ts', for example + // 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 + ] } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 9af395294..e03f399e3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,7 +11,10 @@ "javascript", "typescript" ], - "vitest.enable": true, + "vitest.configSearchPatternExclude": "{**/node_modules/**,**/dist/**,**/generated/**,**/templates/**,**/examples/hello*/**,**/.*/**,**/*.d.ts}", + "vitest.debugExclude": [ + "/**", "**/node_modules/**", "!**/node_modules/vscode-uri/**" + ], "[json]": { "editor.defaultFormatter": "vscode.json-language-features" }, diff --git a/packages/langium/src/generate/node-processor.ts b/packages/langium/src/generate/node-processor.ts index 8eccd8553..1a7434d27 100644 --- a/packages/langium/src/generate/node-processor.ts +++ b/packages/langium/src/generate/node-processor.ts @@ -94,11 +94,12 @@ class Context { this.length -= this.lines[this.currentLineNumber].join('').length; this.lines[this.currentLineNumber] = []; this.pendingIndent = true; + this.recentNonImmediateIndents.length = 0; } addNewLine() { - this.pendingIndent = true; this.lines.push([]); + this.pendingIndent = true; this.recentNonImmediateIndents.length = 0; } @@ -228,20 +229,20 @@ function hasContent(node: GeneratorNode | string, ctx: Context): boolean { function processStringNode(node: string, context: Context) { if (node) { - if (context.pendingIndent) { - handlePendingIndent(context, false); - } + handlePendingIndent(context, false); context.append(node); } } function handlePendingIndent(ctx: Context, endOfLine: boolean) { - let indent = ''; - for (const indentNode of ctx.relevantIndents.filter(e => e.indentEmptyLines || !endOfLine)) { - indent += indentNode.indentation ?? ctx.defaultIndentation; + if (ctx.pendingIndent) { + let indent = ''; + for (const indentNode of ctx.relevantIndents.filter(e => e.indentEmptyLines || !endOfLine)) { + indent += indentNode.indentation ?? ctx.defaultIndentation; + } + ctx.append(indent, true); + ctx.pendingIndent = false; } - ctx.append(indent, true); - ctx.pendingIndent = false; } function processCompositeNode(node: CompositeGeneratorNode, context: Context) { @@ -288,9 +289,7 @@ function processNewLineNode(node: NewLineNode, context: Context) { if (node.ifNotEmpty && !hasNonWhitespace(context.currentLineContent)) { context.resetCurrentLine(); } else { - if (context.pendingIndent) { - handlePendingIndent(context, true); - } + handlePendingIndent(context, true); context.append(node.lineDelimiter); context.addNewLine(); } diff --git a/packages/langium/test/generate/node.test.ts b/packages/langium/test/generate/node.test.ts index d67fa0851..8d59ec61b 100644 --- a/packages/langium/test/generate/node.test.ts +++ b/packages/langium/test/generate/node.test.ts @@ -172,6 +172,22 @@ describe('indentation', () => { expect(process(comp, '\t')).toBe(`No indent${EOL}\tIndent {${EOL}\t}${EOL}`); }); + test('should indent nested template starting with a new line with \'ifNotEmpty\', with \'indentImmediately: false\'', () => { + const comp = new CompositeGeneratorNode(); + comp.append('No indent', NL); + comp.indent({ + indentImmediately: false, + indentedChildren: [ + '\t', + NLEmpty, + 'Indented', + NL, + 'Indented', + NL + ] + }); + expect(process(comp, '\t')).toBe(`No indent${EOL}\tIndented${EOL}\tIndented${EOL}`); + }); }); describe('composite', () => { diff --git a/packages/langium/test/generate/template-node.test.ts b/packages/langium/test/generate/template-node.test.ts index 6203cbd6f..41bb592fd 100644 --- a/packages/langium/test/generate/template-node.test.ts +++ b/packages/langium/test/generate/template-node.test.ts @@ -832,9 +832,47 @@ describe('Multiple nested substitution templates', () => { generated text! `); }); + + test('Nested substitution of with indented nested template starting with an _undefined_ line', () => { + expect( + toString(n` + begin: + ${n` + ${undefined} + ${nestedNode} + `} + `) + ).toBe( + s` + begin: + More + generated text! + ` + ); + }); + + test('Nested substitution of with indented nested template starting with an _empty string_ line', () => { + expect( + toString(n` + begin: + ${n` + ${''} + ${nestedNode} + `} + `) + ).toBe( + s` + begin: + ${/* 's' automatically trims the empty lines completely, so insert */''} + More + generated text! + `.replace('', '') + ); + }); + }); -describe('Embedded forEach loops', () => { +describe('Joining lists', () => { test('ForEach loop with empty iterable', () => { const node = n` Data: @@ -1083,6 +1121,18 @@ describe('Embedded forEach loops', () => { b `); }); + + test('Nested ForEach loop with empty iterable followed by an indented line', () => { + const node = n` + ${joinToNode([], { appendNewLineIfNotEmpty: true})} + a + `; + const text = toString(node); + expect(text).toBe(s` + a + `); + }); + }); describe('Appending templates to existing nodes', () => {