From 2a8247aae691f9da6bce1812e747e57efb923cd4 Mon Sep 17 00:00:00 2001 From: Blake Miner Date: Tue, 29 Sep 2026 11:40:31 -0400 Subject: [PATCH 1/2] Fix line width calculation with useTabs When `useTabs` was enabled, each tab was counted as a single column when computing the current line length after an indent, outdent or newline. Deeply nested lines were therefore considered shorter than they are and attributes were not wrapped. Count every indentation level as `pugTabWidth` columns regardless of whether tabs or spaces are used. Fixes #268 Co-Authored-By: Claude Opus 5.5 --- src/printer.ts | 21 ++++++++++++++----- tests/issues/issue-268/formatted-nested.pug | 13 ++++++++++++ tests/issues/issue-268/formatted.pug | 16 +++++++++++--- tests/issues/issue-268/issue-268.test.ts | 12 +++++++++++ tests/issues/issue-268/unformatted-nested.pug | 5 +++++ 5 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 tests/issues/issue-268/formatted-nested.pug create mode 100644 tests/issues/issue-268/unformatted-nested.pug diff --git a/src/printer.ts b/src/printer.ts index e5e210be..65c9e90b 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -393,6 +393,19 @@ export class PugPrinter { return results.join(''); } + /** + * Visual width of the indentation for the given level. + * + * A tab is counted as `pugTabWidth` columns, so that line length calculations + * are the same regardless of `pugUseTabs`. + * + * @param indentLevel The indentation level. + * @returns The visual width in columns. + */ + private indentWidth(indentLevel: number): number { + return indentLevel * this.options.pugTabWidth; + } + private getNextToken(): Token | null { this.currentIndex++; return this.tokens[this.currentIndex] ?? null; @@ -1309,7 +1322,7 @@ export class PugPrinter { private indent(token: IndentToken): string { const result: string = `\n${this.indentString.repeat(this.indentLevel)}`; this.indentLevel++; - this.currentLineLength = result.length - 1 + 1 + this.options.pugTabWidth; // -1 for \n, +1 for non zero based + this.currentLineLength = 1 + this.indentWidth(this.indentLevel); // +1 for non zero based logger.debug( 'indent', { @@ -1334,8 +1347,7 @@ export class PugPrinter { } this.indentLevel--; - this.currentLineLength = - 1 + this.indentString.repeat(this.indentLevel).length; // -1 for \n, +1 for non zero based + this.currentLineLength = 1 + this.indentWidth(this.indentLevel); // +1 for non zero based logger.debug( 'outdent', { result, indentLevel: this.indentLevel }, @@ -1639,8 +1651,7 @@ export class PugPrinter { } result += '\n'; - this.currentLineLength = - 1 + this.indentString.repeat(this.indentLevel).length; // -1 for \n, +1 for non zero based + this.currentLineLength = 1 + this.indentWidth(this.indentLevel); // +1 for non zero based logger.debug( 'newline', { result, indentLevel: this.indentLevel }, diff --git a/tests/issues/issue-268/formatted-nested.pug b/tests/issues/issue-268/formatted-nested.pug new file mode 100644 index 00000000..ca0a1a59 --- /dev/null +++ b/tests/issues/issue-268/formatted-nested.pug @@ -0,0 +1,13 @@ +p + input#some-very-long-id-for-example-sake( + type="input", + placeeholder="example" + ) + div + input#name( + type="text", + name="name", + placeholder="Name *", + required + ) + input#name(type="text", name="name", placeholder="Name *", required) diff --git a/tests/issues/issue-268/formatted.pug b/tests/issues/issue-268/formatted.pug index 920e8524..13d72b9a 100644 --- a/tests/issues/issue-268/formatted.pug +++ b/tests/issues/issue-268/formatted.pug @@ -10,7 +10,11 @@ html color="red" ). There is surely more I could write here - .short-again(style="display: none", color="red", font="Sans Serif") + .short-again( + style="display: none", + color="red", + font="Sans Serif" + ) | I could surely more I could write here .hi(data-wrap="false") This should be wrapped @@ -51,6 +55,12 @@ html .col-12 ul.actions li - input.primary(type="submit", value="Send Message") + input.primary( + type="submit", + value="Send Message" + ) li - input(type="reset", value="Reset Form") + input( + type="reset", + value="Reset Form" + ) diff --git a/tests/issues/issue-268/issue-268.test.ts b/tests/issues/issue-268/issue-268.test.ts index 1532ec74..036c6b22 100644 --- a/tests/issues/issue-268/issue-268.test.ts +++ b/tests/issues/issue-268/issue-268.test.ts @@ -11,4 +11,16 @@ describe('Issues', () => { }); expect(actual).toBe(expected); }); + + it('should consider tabWidth for every indentation level when using useTabs', async () => { + const { actual, expected } = await compareFiles(import.meta.url, { + source: 'unformatted-nested.pug', + target: 'formatted-nested.pug', + formatOptions: { + useTabs: true, + tabWidth: 8, + }, + }); + expect(actual).toBe(expected); + }); }); diff --git a/tests/issues/issue-268/unformatted-nested.pug b/tests/issues/issue-268/unformatted-nested.pug new file mode 100644 index 00000000..06eeb1a2 --- /dev/null +++ b/tests/issues/issue-268/unformatted-nested.pug @@ -0,0 +1,5 @@ +p + input#some-very-long-id-for-example-sake(type="input", placeeholder="example") + div + input#name(type="text", name="name", placeholder="Name *", required) + input#name(type="text", name="name", placeholder="Name *", required) From b9b0451af79ca60d9cdb4476b61644cf85b0a8fb Mon Sep 17 00:00:00 2001 From: Blake Miner Date: Tue, 29 Sep 2026 12:10:28 -0400 Subject: [PATCH 2/2] Wrap pipeless script and style content at printWidth Content of `script` and `style` blocks was formatted with the code interpolation options, which use a `printWidth` of 9000, so long lines were never wrapped. Format it with `printWidth` minus the indentation width of the block instead, which also respects `useTabs` and `tabWidth` Co-Authored-By: Claude Opus 5.5 --- src/printer.ts | 4 ++++ .../issue-268/formatted-script-spaces.pug | 22 +++++++++++++++++ .../issue-268/formatted-script-tabs.pug | 22 +++++++++++++++++ tests/issues/issue-268/issue-268.test.ts | 24 +++++++++++++++++++ tests/issues/issue-268/unformatted-script.pug | 12 ++++++++++ 5 files changed, 84 insertions(+) create mode 100644 tests/issues/issue-268/formatted-script-spaces.pug create mode 100644 tests/issues/issue-268/formatted-script-tabs.pug create mode 100644 tests/issues/issue-268/unformatted-script.pug diff --git a/src/printer.ts b/src/printer.ts index 65c9e90b..90d68938 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -2045,6 +2045,10 @@ export class PugPrinter { result = await format(rawText, { parser, ...this.codeInterpolationOptions, + // The content will be indented one level deeper than the tag + printWidth: + this.options.pugPrintWidth - + this.indentWidth(this.indentLevel + 1), }); } catch (error: unknown) { if (!usedInterpolatedCode) { diff --git a/tests/issues/issue-268/formatted-script-spaces.pug b/tests/issues/issue-268/formatted-script-spaces.pug new file mode 100644 index 00000000..27e2595d --- /dev/null +++ b/tests/issues/issue-268/formatted-script-spaces.pug @@ -0,0 +1,22 @@ +html + head + script. + const config = { + apiEndpoint: "https://example.com/api/v1", + timeout: 5000, + retries: 3, + }; + function initialize(options) { + return fetchData(options.apiEndpoint, { + timeout: options.timeout, + }); + } + track("page_view", { path: location.pathname }); + body + main + section + div + script. + track("page_view", { + path: location.pathname, + }); diff --git a/tests/issues/issue-268/formatted-script-tabs.pug b/tests/issues/issue-268/formatted-script-tabs.pug new file mode 100644 index 00000000..19e268e4 --- /dev/null +++ b/tests/issues/issue-268/formatted-script-tabs.pug @@ -0,0 +1,22 @@ +html + head + script. + const config = { + apiEndpoint: "https://example.com/api/v1", + timeout: 5000, + retries: 3, + }; + function initialize(options) { + return fetchData(options.apiEndpoint, { + timeout: options.timeout, + }); + } + track("page_view", { path: location.pathname }); + body + main + section + div + script. + track("page_view", { + path: location.pathname, + }); diff --git a/tests/issues/issue-268/issue-268.test.ts b/tests/issues/issue-268/issue-268.test.ts index 036c6b22..171f4bb4 100644 --- a/tests/issues/issue-268/issue-268.test.ts +++ b/tests/issues/issue-268/issue-268.test.ts @@ -23,4 +23,28 @@ describe('Issues', () => { }); expect(actual).toBe(expected); }); + + it('should wrap script content considering tabWidth when using useTabs', async () => { + const { actual, expected } = await compareFiles(import.meta.url, { + source: 'unformatted-script.pug', + target: 'formatted-script-tabs.pug', + formatOptions: { + useTabs: true, + tabWidth: 8, + }, + }); + expect(actual).toBe(expected); + }); + + it('should wrap script content considering indentation when using spaces', async () => { + const { actual, expected } = await compareFiles(import.meta.url, { + source: 'unformatted-script.pug', + target: 'formatted-script-spaces.pug', + formatOptions: { + useTabs: false, + tabWidth: 8, + }, + }); + expect(actual).toBe(expected); + }); }); diff --git a/tests/issues/issue-268/unformatted-script.pug b/tests/issues/issue-268/unformatted-script.pug new file mode 100644 index 00000000..920e20f9 --- /dev/null +++ b/tests/issues/issue-268/unformatted-script.pug @@ -0,0 +1,12 @@ +html + head + script. + const config = { apiEndpoint: 'https://example.com/api/v1', timeout: 5000, retries: 3 }; + function initialize(options) { return fetchData(options.apiEndpoint, { timeout: options.timeout }); } + track('page_view', { path: location.pathname }); + body + main + section + div + script. + track('page_view', { path: location.pathname });