From 685da96efadee149dc1c869112ac768dfe95ad63 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Thu, 22 Apr 2021 18:57:38 +0200 Subject: [PATCH 01/10] Add test for svelte --- tests/frameworks/svelte/formatted.pug | 17 +++++++++++++++++ tests/frameworks/svelte/svelte.test.ts | 22 ++++++++++++++++++++++ tests/frameworks/svelte/unformatted.pug | 17 +++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 tests/frameworks/svelte/formatted.pug create mode 100644 tests/frameworks/svelte/svelte.test.ts create mode 100644 tests/frameworks/svelte/unformatted.pug diff --git a/tests/frameworks/svelte/formatted.pug b/tests/frameworks/svelte/formatted.pug new file mode 100644 index 00000000..b472232a --- /dev/null +++ b/tests/frameworks/svelte/formatted.pug @@ -0,0 +1,17 @@ +main + h1 Hello { name }! + p + | Visit the + a(href="https://svelte.dev/tutorial") + | Svelte tutorial + | to learn how to build Svelte + | apps. + p + +each('cats as cat') + a(href="{ cat.id }") { cat.name } + | !{ ' ' } + + p + +if('!user.loggedIn') + button(on:click="{ toggle }") + | Log out diff --git a/tests/frameworks/svelte/svelte.test.ts b/tests/frameworks/svelte/svelte.test.ts new file mode 100644 index 00000000..df868a1e --- /dev/null +++ b/tests/frameworks/svelte/svelte.test.ts @@ -0,0 +1,22 @@ +import { readFileSync } from 'fs'; +import { resolve } from 'path'; +import { format } from 'prettier'; +import { plugin } from './../../../src/index'; + +describe('Frameworks', () => { + describe('Svelte', () => { + test('should format svelte', () => { + const expected: string = readFileSync(resolve(__dirname, 'formatted.pug'), 'utf8'); + const code: string = readFileSync(resolve(__dirname, 'unformatted.pug'), 'utf8'); + const actual: string = format(code, { + parser: 'pug', + plugins: [plugin], + + // @ts-expect-error + pugFramework: 'svelte' + }); + + expect(actual).toBe(expected); + }); + }); +}); diff --git a/tests/frameworks/svelte/unformatted.pug b/tests/frameworks/svelte/unformatted.pug new file mode 100644 index 00000000..9cc33df3 --- /dev/null +++ b/tests/frameworks/svelte/unformatted.pug @@ -0,0 +1,17 @@ +main + h1 Hello { name}! + p + | Visit the + a(href='https://svelte.dev/tutorial') + | Svelte tutorial + | to learn how to build Svelte + | apps. + p + +each(' cats as cat') + a(href='{ cat.id }') { cat.name } + | !{ ' ' } + + p + +if('!user.loggedIn') + button(on:click='{ toggle}') + | Log out From c34ac24a07635b4e92426fdd48139fba83154e01 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Wed, 28 Apr 2021 20:56:03 +0200 Subject: [PATCH 02/10] Handle curly brackets in text for svelte --- src/printer.ts | 64 +++++++++++++++++++++++++++++++++++++++++++-- src/utils/common.ts | 7 +++-- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/printer.ts b/src/printer.ts index 3387c258..37b3c812 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -393,6 +393,7 @@ export class PugPrinter { private formatText(text: string): string { let result: string = ''; while (text) { + // Find double curly brackets const start: number = text.indexOf('{{'); if (start !== -1) { result += text.slice(0, start); @@ -487,8 +488,67 @@ export class PugPrinter { text = ''; } } else { - result += text; - text = ''; + // Find single curly brackets for svelte + const start2: number = text.indexOf('{'); + if (this.options.pugFramework === 'svelte' && start2 !== -1) { + result += text.slice(0, start2); + text = text.slice(start2 + 1); + const end2: number = text.indexOf('}'); + if (end2 !== -1) { + let code: string = text.slice(0, end2); + try { + // Index of primary quote + const q1: number = code.indexOf(this.quotes); + // Index of secondary (other) quote + const q2: number = code.indexOf(this.otherQuotes); + // Index of backtick + const qb: number = code.indexOf('`'); + if (q1 >= 0 && q2 >= 0 && q2 > q1 && (qb < 0 || q1 < qb)) { + logger.log({ + code, + quotes: this.quotes, + otherQuotes: this.otherQuotes, + q1, + q2, + qb + }); + logger.warn( + 'The following expression could not be formatted correctly. Please try to fix it yourself and if there is a problem, please open a bug issue:', + code + ); + result += handleBracketSpacing(this.options.pugBracketSpacing, code); + text = text.slice(end2 + 1); + continue; + } else { + code = this.frameworkFormat(code); + } + } catch (error: unknown) { + logger.warn('[PugPrinter:formatText]: ', error); + try { + code = format(code, { + parser: 'babel', + ...this.codeInterpolationOptions, + semi: false + }); + if (code[0] === ';') { + code = code.slice(1); + } + } catch (error: unknown) { + logger.warn(error); + } + } + code = unwrapLineFeeds(code); + result += handleBracketSpacing(this.options.pugBracketSpacing, code, ['{', '}']); + text = text.slice(end2 + 1); + } else { + result += '{'; + result += text; + text = ''; + } + } else { + result += text; + text = ''; + } } } return result; diff --git a/src/utils/common.ts b/src/utils/common.ts index 2bbb1e6e..f1df71fd 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -152,10 +152,13 @@ export function isMultilineInterpolation(val: string): boolean { * * @param bracketSpacing Specifies whether or not to insert spaces before and after the code. * @param code Code that is enclosed in brackets. + * @param param2 Brackets. + * @param param2."0" Opening brackets. + * @param param2."1" Closing brackets. * @returns The handled string. */ -export function handleBracketSpacing(bracketSpacing: boolean, code: string): string { - return bracketSpacing ? `{{ ${code} }}` : `{{${code}}}`; +export function handleBracketSpacing(bracketSpacing: boolean, code: string, [opening, closing] = ['{{', '}}']): string { + return bracketSpacing ? `${opening} ${code} ${closing}` : `${opening}${code}${closing}`; } /** From f67d9bf7110c206fd8c1a86858656088452266cc Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Wed, 28 Apr 2021 21:09:21 +0200 Subject: [PATCH 03/10] Handle svelte interpolation in tag attributes --- src/printer.ts | 20 ++++++++++++++++++++ src/utils/svelte.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/utils/svelte.ts diff --git a/src/printer.ts b/src/printer.ts index 37b3c812..4e4ce27b 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -79,6 +79,7 @@ import { previousTagToken, unwrapLineFeeds } from './utils/common'; +import { isSvelteInterpolation } from './utils/svelte'; import { isVueEventBinding, isVueExpression, isVueVForWithOf, isVueVOnExpression } from './utils/vue'; const logger: Logger = createLogger(console); @@ -610,6 +611,23 @@ export class PugPrinter { return this.quoteString(val); } + private formatSvelteInterpolation(val: string): string { + val = val.slice(1, -1); // Remove quotes + val = val.slice(1, -1); // Remove braces + val = val.trim(); + if (val.includes(`\\${this.otherQuotes}`)) { + logger.warn( + 'The following expression could not be formatted correctly. Please try to fix it yourself and if there is a problem, please open a bug issue:', + val + ); + } else { + val = format(val, { parser: '__ng_interpolation', ...this.codeInterpolationOptions }); + val = unwrapLineFeeds(val); + } + val = handleBracketSpacing(this.options.pugBracketSpacing, val, ['{', '}']); + return this.quoteString(val); + } + //#endregion // ######## ####### ## ## ######## ## ## ######## ######## ####### ###### ######## ###### ###### ####### ######## ###### @@ -869,6 +887,8 @@ export class PugPrinter { val = this.formatAngularDirective(val); } else if (isAngularInterpolation(val)) { val = this.formatAngularInterpolation(val); + } else if (isSvelteInterpolation(val)) { + val = this.formatSvelteInterpolation(val); } else if (isStyleAttribute(token.name, token.val)) { val = this.formatStyleAttribute(val); } else if (isQuoted(val)) { diff --git a/src/utils/svelte.ts b/src/utils/svelte.ts new file mode 100644 index 00000000..8a9cc570 --- /dev/null +++ b/src/utils/svelte.ts @@ -0,0 +1,26 @@ +/** + * Indicates whether the attribute value is a Svelte interpolation. + * + * --- + * + * Example interpolation: + * ``` + * a(href="{ cat.id }") + * ``` + * + * In this case `val` is `"{ cat.id }"`. + * + * --- + * + * @param val Value of tag attribute. + * @returns `true` if `val` passes the svelte interpolation check, otherwise `false`. + */ +export function isSvelteInterpolation(val: string): boolean { + return ( + val.length >= 3 && + ((val[0] === '"' && val[val.length - 1] === '"') || (val[0] === "'" && val[val.length - 1] === "'")) && + val[1] === '{' && + val[val.length - 2] === '}' && + !val.includes('{', 2) + ); +} From f4da05a48020e684a58523e62e27bd2ae8af47e2 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Wed, 28 Apr 2021 21:21:48 +0200 Subject: [PATCH 04/10] Remove space from test-case for now --- tests/frameworks/svelte/unformatted.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/frameworks/svelte/unformatted.pug b/tests/frameworks/svelte/unformatted.pug index 9cc33df3..d45b2e39 100644 --- a/tests/frameworks/svelte/unformatted.pug +++ b/tests/frameworks/svelte/unformatted.pug @@ -7,7 +7,7 @@ main | to learn how to build Svelte | apps. p - +each(' cats as cat') + +each('cats as cat') a(href='{ cat.id }') { cat.name } | !{ ' ' } From b98ecea09ab0e5345f9790bc3743f21f3b121ddf Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Wed, 28 Apr 2021 21:49:05 +0200 Subject: [PATCH 05/10] Use bracketSpacing for interpolated-code tokens --- src/printer.ts | 2 +- tests/conditionals/formatted.pug | 4 ++-- tests/inheritance/blocks/formatted.pug | 2 +- tests/issues/issue-27/formatted.pug | 4 ++-- tests/issues/issue-34/formatted.pug | 2 +- tests/issues/issue-40/formatted.pug | 2 +- tests/issues/issue-49/formatted.pug | 2 +- tests/issues/issue-52/formatted.pug | 4 ++-- tests/issues/issue-59/formatted.pug | 16 ++++++++-------- tests/issues/issue-89/formatted.pug | 2 +- .../pragma/insert-pragma/insert-pragma.test.ts | 18 ++++++++++++++---- tests/pug-tests/case-blocks.formatted.pug | 2 +- tests/pug-tests/case.formatted.pug | 4 ++-- tests/pug-tests/code.iteration.formatted.pug | 4 ++-- tests/pug-tests/each.else.formatted.pug | 6 +++--- tests/pug-tests/escape-test.formatted.pug | 2 +- tests/pug-tests/html.formatted.pug | 2 +- .../interpolation.escape.formatted.pug | 2 +- tests/pug-tests/pipeless-tag.formatted.pug | 2 +- tests/pug-tests/regression.784.formatted.pug | 2 +- tests/spaces/formatted.pug | 2 +- 21 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/printer.ts b/src/printer.ts index 4e4ce27b..321890f2 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -1210,7 +1210,7 @@ export class PugPrinter { break; } result += token.mustEscape ? '#' : '!'; - result += `{${token.val}}`; + result += handleBracketSpacing(this.options.pugBracketSpacing, token.val.trim(), ['{', '}']); return result; } diff --git a/tests/conditionals/formatted.pug b/tests/conditionals/formatted.pug index f8a1c7dd..2142d2d6 100644 --- a/tests/conditionals/formatted.pug +++ b/tests/conditionals/formatted.pug @@ -14,7 +14,7 @@ p.description User has no description unless user.isAnonymous - p You're logged in as #{user.name} + p You're logged in as #{ user.name } if !user.isAnonymous - p You're logged in as #{user.name} + p You're logged in as #{ user.name } diff --git a/tests/inheritance/blocks/formatted.pug b/tests/inheritance/blocks/formatted.pug index cd2992e2..588a604d 100644 --- a/tests/inheritance/blocks/formatted.pug +++ b/tests/inheritance/blocks/formatted.pug @@ -1,7 +1,7 @@ //- layout.pug html head - title My Site - #{title} + title My Site - #{ title } block scripts script(src="/jquery.js") body diff --git a/tests/issues/issue-27/formatted.pug b/tests/issues/issue-27/formatted.pug index 184725a8..f7f94058 100644 --- a/tests/issues/issue-27/formatted.pug +++ b/tests/issues/issue-27/formatted.pug @@ -1,3 +1,3 @@ -tag(key="value") #{identifier} +tag(key="value") #{ identifier } tag - | #{identifier} + | #{ identifier } diff --git a/tests/issues/issue-34/formatted.pug b/tests/issues/issue-34/formatted.pug index 0c9d0d24..58c44240 100644 --- a/tests/issues/issue-34/formatted.pug +++ b/tests/issues/issue-34/formatted.pug @@ -7,4 +7,4 @@ ul case friends when 0: p you have no friends when 1: p you have a friend - default: p you have #{friends} friends + default: p you have #{ friends } friends diff --git a/tests/issues/issue-40/formatted.pug b/tests/issues/issue-40/formatted.pug index abe737ea..f73e81cb 100644 --- a/tests/issues/issue-40/formatted.pug +++ b/tests/issues/issue-40/formatted.pug @@ -1,4 +1,4 @@ - const el = 'div'; - const test = 'hello'; #{el}.test - | !{text} + | !{ text } diff --git a/tests/issues/issue-49/formatted.pug b/tests/issues/issue-49/formatted.pug index 9309820e..541648ae 100644 --- a/tests/issues/issue-49/formatted.pug +++ b/tests/issues/issue-49/formatted.pug @@ -1,4 +1,4 @@ doctype html html body - h1#header #{title} + h1#header #{ title } diff --git a/tests/issues/issue-52/formatted.pug b/tests/issues/issue-52/formatted.pug index fb553c04..53bfbaae 100644 --- a/tests/issues/issue-52/formatted.pug +++ b/tests/issues/issue-52/formatted.pug @@ -1,8 +1,8 @@ - const variableWithHtmlString = ''; div - | !{variableWithHtmlString} + | !{ variableWithHtmlString } div a(href="") h6 A - | !{veranstaltung.renderedPresseText()} + | !{ veranstaltung.renderedPresseText() } diff --git a/tests/issues/issue-59/formatted.pug b/tests/issues/issue-59/formatted.pug index 4e95d1e5..f33813f0 100644 --- a/tests/issues/issue-59/formatted.pug +++ b/tests/issues/issue-59/formatted.pug @@ -3,23 +3,23 @@ h2 ID \#{{ id }} h2 \#{{ id }} -h2 ID #{'#{{ id }}'} -h2 #{'#{{ id }}'} +h2 ID #{ '#{{ id }}' } +h2 #{ '#{{ id }}' } -h2 ID #{id} -h2 #{id} +h2 ID #{ id } +h2 #{ id } h2 ID #[b id] h2 #[b id] -h2 ID #[b #{id}] -h2 #[b #{id}] +h2 ID #[b #{ id }] +h2 #[b #{ id }] h2 ID \#{id} h2 \#{id} h2 ID \#[b id] h2 \#[b id] -h2 ID \#[b #{id}] +h2 ID \#[b #{ id }] h2 ID #[b \#{id}] h2 ID \#[b \#{id}] -h2 \#[b #{id}] +h2 \#[b #{ id }] h2 #[b \#{id}] h2 \#[b \#{id}] diff --git a/tests/issues/issue-89/formatted.pug b/tests/issues/issue-89/formatted.pug index c6f1096e..986d78ea 100644 --- a/tests/issues/issue-89/formatted.pug +++ b/tests/issues/issue-89/formatted.pug @@ -47,4 +47,4 @@ div Lorem1 #[code Lorem2]. #[code Lorem3]. - #{Lorem4} + #{ Lorem4 } diff --git a/tests/pragma/insert-pragma/insert-pragma.test.ts b/tests/pragma/insert-pragma/insert-pragma.test.ts index 936af481..30ea1382 100644 --- a/tests/pragma/insert-pragma/insert-pragma.test.ts +++ b/tests/pragma/insert-pragma/insert-pragma.test.ts @@ -7,22 +7,32 @@ describe('Pragma', () => { test('should insert pragma if option is set', () => { const expected: string = readFileSync(resolve(__dirname, 'with-pragma.pug'), 'utf8'); const code: string = readFileSync(resolve(__dirname, 'no-pragma.pug'), 'utf8'); - const actual: string = format(code, { parser: 'pug', plugins: [plugin], insertPragma: true }); + const actual: string = format(code, { + parser: 'pug', + plugins: [plugin], + insertPragma: true, + bracketSpacing: false + }); expect(actual).toBe(expected); }); test('should not insert multiple pragma if option is set', () => { const expected: string = readFileSync(resolve(__dirname, 'with-pragma.pug'), 'utf8'); const code: string = readFileSync(resolve(__dirname, 'no-pragma.pug'), 'utf8'); - let actual: string = format(code, { parser: 'pug', plugins: [plugin], insertPragma: true }); - actual = format(actual, { parser: 'pug', plugins: [plugin], insertPragma: true }); + let actual: string = format(code, { + parser: 'pug', + plugins: [plugin], + insertPragma: true, + bracketSpacing: false + }); + actual = format(actual, { parser: 'pug', plugins: [plugin], insertPragma: true, bracketSpacing: false }); expect(actual).toBe(expected); }); test('should not insert pragma if option is not set', () => { const expected: string = readFileSync(resolve(__dirname, 'no-pragma.pug'), 'utf8'); const code: string = readFileSync(resolve(__dirname, 'no-pragma.pug'), 'utf8'); - const actual: string = format(code, { parser: 'pug', plugins: [plugin] }); + const actual: string = format(code, { parser: 'pug', plugins: [plugin], bracketSpacing: false }); expect(actual).toBe(expected); }); diff --git a/tests/pug-tests/case-blocks.formatted.pug b/tests/pug-tests/case-blocks.formatted.pug index 7a6d8cb5..39729cc2 100644 --- a/tests/pug-tests/case-blocks.formatted.pug +++ b/tests/pug-tests/case-blocks.formatted.pug @@ -7,4 +7,4 @@ html when 1 p you have a friend default - p you have #{friends} friends + p you have #{ friends } friends diff --git a/tests/pug-tests/case.formatted.pug b/tests/pug-tests/case.formatted.pug index c5080a73..60380793 100644 --- a/tests/pug-tests/case.formatted.pug +++ b/tests/pug-tests/case.formatted.pug @@ -4,14 +4,14 @@ html case friends when 0: p you have no friends when 1: p you have a friend - default: p you have #{friends} friends + default: p you have #{ friends } friends - var friends = 0; case friends when 0 when 1 p you have very few friends default - p you have #{friends} friends + p you have #{ friends } friends - var friend = 'Tim:G'; case friend diff --git a/tests/pug-tests/code.iteration.formatted.pug b/tests/pug-tests/code.iteration.formatted.pug index 3dbe4a31..3c597c4a 100644 --- a/tests/pug-tests/code.iteration.formatted.pug +++ b/tests/pug-tests/code.iteration.formatted.pug @@ -25,10 +25,10 @@ ul ul each l in letters each n in nums - li #{n}: #{l} + li #{ n }: #{ l } - var count = 1; - var counter = function() { return [count++, count++, count++] } ul each n in counter() - li #{n} + li #{ n } diff --git a/tests/pug-tests/each.else.formatted.pug b/tests/pug-tests/each.else.formatted.pug index 566a9ca3..c0c5becb 100644 --- a/tests/pug-tests/each.else.formatted.pug +++ b/tests/pug-tests/each.else.formatted.pug @@ -19,7 +19,7 @@ if users ul each val, key in user - li #{key}: #{val} + li #{ key }: #{ val } else li user has no details! @@ -27,7 +27,7 @@ ul ul each prop, key in user - li #{key}: #{val} + li #{ key }: #{ val } else li user has no details! @@ -36,6 +36,6 @@ ul ul each val, key in user - li #{key}: #{val} + li #{ key }: #{ val } else li user has no details! diff --git a/tests/pug-tests/escape-test.formatted.pug b/tests/pug-tests/escape-test.formatted.pug index d752a48e..c684cf41 100644 --- a/tests/pug-tests/escape-test.formatted.pug +++ b/tests/pug-tests/escape-test.formatted.pug @@ -5,4 +5,4 @@ html body textarea - var txt = ''; - | #{txt} + | #{ txt } diff --git a/tests/pug-tests/html.formatted.pug b/tests/pug-tests/html.formatted.pug index c521c08f..ae7175cc 100644 --- a/tests/pug-tests/html.formatted.pug +++ b/tests/pug-tests/html.formatted.pug @@ -6,7 +6,7 @@ li baz - + p You can embed html as well. diff --git a/tests/pug-tests/interpolation.escape.formatted.pug b/tests/pug-tests/interpolation.escape.formatted.pug index ae514ed0..f274f035 100644 --- a/tests/pug-tests/interpolation.escape.formatted.pug +++ b/tests/pug-tests/interpolation.escape.formatted.pug @@ -3,4 +3,4 @@ foo | some | \#{text} | here - | My ID #{"is {" + id + "}"} + | My ID #{ "is {" + id + "}" } diff --git a/tests/pug-tests/pipeless-tag.formatted.pug b/tests/pug-tests/pipeless-tag.formatted.pug index d521da42..27f35936 100644 --- a/tests/pug-tests/pipeless-tag.formatted.pug +++ b/tests/pug-tests/pipeless-tag.formatted.pug @@ -1,3 +1,3 @@ pre. what - is #{'going'} #[| #{'on'}] + is #{ 'going' } #[| #{ 'on' }] diff --git a/tests/pug-tests/regression.784.formatted.pug b/tests/pug-tests/regression.784.formatted.pug index 04b78431..8cf24a8a 100644 --- a/tests/pug-tests/regression.784.formatted.pug +++ b/tests/pug-tests/regression.784.formatted.pug @@ -1,2 +1,2 @@ - var url = 'http://www.google.com'; -.url #{url.replace('http://', '').replace(/^www\./, '')} +.url #{ url.replace('http://', '').replace(/^www\./, '') } diff --git a/tests/spaces/formatted.pug b/tests/spaces/formatted.pug index f736e815..0d7d936c 100644 --- a/tests/spaces/formatted.pug +++ b/tests/spaces/formatted.pug @@ -1,5 +1,5 @@ //- template.pug -p #{name}'s Pug source code! +p #{ name }'s Pug source code! v-btn( name="save", From 2074e55671a404ae2651592c2cba5743c21ba37e Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Sat, 1 May 2021 17:13:29 +0200 Subject: [PATCH 06/10] Extract a detectDangerousQuoteCombination for readability --- src/printer.ts | 45 +++++++++++++++------------------------------ src/utils/common.ts | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/printer.ts b/src/printer.ts index 321890f2..8f23f3cc 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -69,6 +69,7 @@ import type { PugFramework } from './options/pug-framework'; import type { PugIdNotation } from './options/pug-id-notation'; import { isAngularAction, isAngularBinding, isAngularDirective, isAngularInterpolation } from './utils/angular'; import { + detectDangerousQuoteCombination, detectFramework, handleBracketSpacing, isMultilineInterpolation, @@ -403,21 +404,13 @@ export class PugPrinter { if (end !== -1) { let code: string = text.slice(0, end); try { - // Index of primary quote - const q1: number = code.indexOf(this.quotes); - // Index of secondary (other) quote - const q2: number = code.indexOf(this.otherQuotes); - // Index of backtick - const qb: number = code.indexOf('`'); - if (q1 >= 0 && q2 >= 0 && q2 > q1 && (qb < 0 || q1 < qb)) { - logger.log({ - code, - quotes: this.quotes, - otherQuotes: this.otherQuotes, - q1, - q2, - qb - }); + const dangerousQuoteCombination: boolean = detectDangerousQuoteCombination( + code, + this.quotes, + this.otherQuotes, + logger + ); + if (dangerousQuoteCombination) { logger.warn( 'The following expression could not be formatted correctly. Please try to fix it yourself and if there is a problem, please open a bug issue:', code @@ -498,21 +491,13 @@ export class PugPrinter { if (end2 !== -1) { let code: string = text.slice(0, end2); try { - // Index of primary quote - const q1: number = code.indexOf(this.quotes); - // Index of secondary (other) quote - const q2: number = code.indexOf(this.otherQuotes); - // Index of backtick - const qb: number = code.indexOf('`'); - if (q1 >= 0 && q2 >= 0 && q2 > q1 && (qb < 0 || q1 < qb)) { - logger.log({ - code, - quotes: this.quotes, - otherQuotes: this.otherQuotes, - q1, - q2, - qb - }); + const dangerousQuoteCombination: boolean = detectDangerousQuoteCombination( + code, + this.quotes, + this.otherQuotes, + logger + ); + if (dangerousQuoteCombination) { logger.warn( 'The following expression could not be formatted correctly. Please try to fix it yourself and if there is a problem, please open a bug issue:', code diff --git a/src/utils/common.ts b/src/utils/common.ts index f1df71fd..60b96239 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,4 +1,5 @@ import type { AttributeToken, TagToken, Token } from 'pug-lexer'; +import type { Logger } from '../logger'; import type { PugFramework } from '../options/pug-framework'; /** @@ -196,6 +197,27 @@ export function makeString( return enclosingQuote + newContent + enclosingQuote; } +export function detectDangerousQuoteCombination( + code: string, + quotes: "'" | '"', + otherQuotes: "'" | '"', + logger: Logger +): boolean { + // Index of primary quote + const q1: number = code.indexOf(quotes); + // Index of secondary (other) quote + const q2: number = code.indexOf(otherQuotes); + // Index of backtick + const qb: number = code.indexOf('`'); + + if (q1 >= 0 && q2 >= 0 && q2 > q1 && (qb < 0 || q1 < qb)) { + logger.log({ code, quotes, otherQuotes, q1, q2, qb }); + return true; + } + + return false; +} + /** * Try to detect used framework within the project by reading `process.env.npm_package_*`. * From 1a1e0912c6ce88f3e80c014611881d81eed6eb61 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Sat, 1 May 2021 17:25:46 +0200 Subject: [PATCH 07/10] Merge functions into a combined formatFrameworkInterpolation --- src/printer.ts | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/printer.ts b/src/printer.ts index 8f23f3cc..f947b95e 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -579,9 +579,13 @@ export class PugPrinter { return this.formatDelegatePrettier(val, '__ng_directive'); } - private formatAngularInterpolation(val: string): string { + private formatFrameworkInterpolation( + val: string, + parser: '__ng_interpolation', // TODO: may be changed to allow a special parser for svelte + [opening, closing]: ['{{', '}}'] | ['{', '}'] + ): string { val = val.slice(1, -1); // Remove quotes - val = val.slice(2, -2); // Remove braces + val = val.slice(opening.length, -closing.length); // Remove braces val = val.trim(); if (val.includes(`\\${this.otherQuotes}`)) { logger.warn( @@ -589,28 +593,19 @@ export class PugPrinter { val ); } else { - val = format(val, { parser: '__ng_interpolation', ...this.codeInterpolationOptions }); + val = format(val, { parser, ...this.codeInterpolationOptions }); val = unwrapLineFeeds(val); } - val = handleBracketSpacing(this.options.pugBracketSpacing, val); + val = handleBracketSpacing(this.options.pugBracketSpacing, val, [opening, closing]); return this.quoteString(val); } + private formatAngularInterpolation(val: string): string { + return this.formatFrameworkInterpolation(val, '__ng_interpolation', ['{{', '}}']); + } + private formatSvelteInterpolation(val: string): string { - val = val.slice(1, -1); // Remove quotes - val = val.slice(1, -1); // Remove braces - val = val.trim(); - if (val.includes(`\\${this.otherQuotes}`)) { - logger.warn( - 'The following expression could not be formatted correctly. Please try to fix it yourself and if there is a problem, please open a bug issue:', - val - ); - } else { - val = format(val, { parser: '__ng_interpolation', ...this.codeInterpolationOptions }); - val = unwrapLineFeeds(val); - } - val = handleBracketSpacing(this.options.pugBracketSpacing, val, ['{', '}']); - return this.quoteString(val); + return this.formatFrameworkInterpolation(val, '__ng_interpolation', ['{', '}']); } //#endregion From df2a297277bf1184c25ccbaa4a201fbf6d310358 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Sat, 1 May 2021 17:52:28 +0200 Subject: [PATCH 08/10] Simplify is_Interpolation functions --- src/utils/angular.ts | 10 +++++----- src/utils/common.ts | 4 ++++ src/utils/svelte.ts | 10 +++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/utils/angular.ts b/src/utils/angular.ts index 4d98f832..c77585ba 100644 --- a/src/utils/angular.ts +++ b/src/utils/angular.ts @@ -1,3 +1,5 @@ +import { isQuoted, isWrappedWith } from './common'; + /** * Indicates whether the attribute name is an Angular binding. * @@ -81,11 +83,9 @@ export function isAngularDirective(name: string): boolean { export function isAngularInterpolation(val: string): boolean { return ( val.length >= 5 && - ((val[0] === '"' && val[val.length - 1] === '"') || (val[0] === "'" && val[val.length - 1] === "'")) && - val[1] === '{' && - val[2] === '{' && - val[val.length - 2] === '}' && - val[val.length - 3] === '}' && + isQuoted(val) && + isWrappedWith(val, '{', '}', 1) && + isWrappedWith(val, '{', '}', 2) && !val.includes('{{', 3) ); } diff --git a/src/utils/common.ts b/src/utils/common.ts index 60b96239..5f262700 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -83,6 +83,10 @@ export function isStyleAttribute(name: string, val: string): boolean { return name === 'style' && isQuoted(val); } +export function isWrappedWith(val: string, start: string, end: string, offset: number = 0): boolean { + return val.startsWith(start, offset) && val.endsWith(end, val.length - offset); +} + /** * Indicates whether the value is surrounded by quotes. * diff --git a/src/utils/svelte.ts b/src/utils/svelte.ts index 8a9cc570..afde1cc3 100644 --- a/src/utils/svelte.ts +++ b/src/utils/svelte.ts @@ -1,3 +1,5 @@ +import { isQuoted, isWrappedWith } from './common'; + /** * Indicates whether the attribute value is a Svelte interpolation. * @@ -16,11 +18,5 @@ * @returns `true` if `val` passes the svelte interpolation check, otherwise `false`. */ export function isSvelteInterpolation(val: string): boolean { - return ( - val.length >= 3 && - ((val[0] === '"' && val[val.length - 1] === '"') || (val[0] === "'" && val[val.length - 1] === "'")) && - val[1] === '{' && - val[val.length - 2] === '}' && - !val.includes('{', 2) - ); + return val.length >= 3 && isQuoted(val) && isWrappedWith(val, '{', '}', 1) && !val.includes('{', 2); } From daedd2af72c08ecd01e58176241de0a543485f24 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Sat, 1 May 2021 18:09:20 +0200 Subject: [PATCH 09/10] Apply suggestion Co-authored-by: ST-DDT --- src/utils/angular.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/utils/angular.ts b/src/utils/angular.ts index c77585ba..3f84842b 100644 --- a/src/utils/angular.ts +++ b/src/utils/angular.ts @@ -81,11 +81,5 @@ export function isAngularDirective(name: string): boolean { * @returns `true` if `val` passes the angular interpolation check, otherwise `false`. */ export function isAngularInterpolation(val: string): boolean { - return ( - val.length >= 5 && - isQuoted(val) && - isWrappedWith(val, '{', '}', 1) && - isWrappedWith(val, '{', '}', 2) && - !val.includes('{{', 3) - ); + return val.length >= 5 && isQuoted(val) && isWrappedWith(val, '{{', '}}', 1) && !val.includes('{{', 3); } From 55b0485c53cfd1e35d1d5dc13480a63afd2364f1 Mon Sep 17 00:00:00 2001 From: Shinigami92 Date: Sat, 1 May 2021 18:12:05 +0200 Subject: [PATCH 10/10] Add JSDoc --- src/utils/common.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/utils/common.ts b/src/utils/common.ts index 5f262700..0e76f29c 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -83,6 +83,15 @@ export function isStyleAttribute(name: string, val: string): boolean { return name === 'style' && isQuoted(val); } +/** + * Indicates whether the value is surrounded by the `start` and `end` parameters. + * + * @param val Value of a tag attribute. + * @param start The left hand side of the wrapping. + * @param end The right hand side of the wrapping. + * @param offset The offset from left and right where to search from. + * @returns Whether the value is wrapped wit start and end from the offset or not. + */ export function isWrappedWith(val: string, start: string, end: string, offset: number = 0): boolean { return val.startsWith(start, offset) && val.endsWith(end, val.length - offset); } @@ -201,6 +210,15 @@ export function makeString( return enclosingQuote + newContent + enclosingQuote; } +/** + * See [issue #9](https://github.com/prettier/plugin-pug/issues/9) for more details. + * + * @param code Code that is checked. + * @param quotes Quotes. + * @param otherQuotes Opposite of quotes. + * @param logger A logger. + * @returns Whether dangerous quote combinations where detected or not. + */ export function detectDangerousQuoteCombination( code: string, quotes: "'" | '"',