From 3d69f4e1860e7c2cbebfc92925e4696b171af15b Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 10 Jul 2023 15:23:12 -0500 Subject: [PATCH] Maintain whitespace before tag close (#822) * fix(#801): TSX preserves trailing spaces before tag close * chore: add changeset * feat: improve mapping for many spaces --- .changeset/seven-moons-design.md | 5 ++++ internal/printer/print-to-tsx.go | 20 ++++++++----- packages/compiler/test/tsx-sourcemaps/tags.ts | 15 ++++++++++ packages/compiler/test/tsx/basic.ts | 30 +++++++++++++++++++ 4 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 .changeset/seven-moons-design.md diff --git a/.changeset/seven-moons-design.md b/.changeset/seven-moons-design.md new file mode 100644 index 000000000..752b669e2 --- /dev/null +++ b/.changeset/seven-moons-design.md @@ -0,0 +1,5 @@ +--- +'@astrojs/compiler': patch +--- + +[TSX] maintain trailing whitespace before an element is closed, fixing TypeScript completion in some cases diff --git a/internal/printer/print-to-tsx.go b/internal/printer/print-to-tsx.go index 9ed5ceb02..e7e6156e7 100644 --- a/internal/printer/print-to-tsx.go +++ b/internal/printer/print-to-tsx.go @@ -264,6 +264,7 @@ declare const Astro: Readonly>`, props.Ident) p.print("<") p.addSourceMapping(loc.Loc{Start: n.Loc[0].Start}) p.print(n.Data) + p.addSourceMapping(loc.Loc{Start: n.Loc[0].Start + len(n.Data)}) invalidTSXAttributes := make([]Attribute, 0) endLoc := n.Loc[0].Start + len(n.Data) @@ -375,7 +376,6 @@ declare const Astro: Readonly>`, props.Ident) p.print(`:`) p.addSourceMapping(loc.Loc{Start: eqStart + 1}) p.print(`"` + encodeDoubleQuote(a.Val) + `"`) - endLoc = eqStart + 1 + len(a.Val) + 2 case astro.EmptyAttribute: p.print(a.Key) p.print(`"`) @@ -383,7 +383,6 @@ declare const Astro: Readonly>`, props.Ident) p.print(`:`) p.addSourceMapping(a.KeyLoc) p.print(`true`) - endLoc = a.KeyLoc.Start + len(a.Key) case astro.ExpressionAttribute: p.print(a.Key) p.print(`"`) @@ -394,7 +393,6 @@ declare const Astro: Readonly>`, props.Ident) p.printTextWithSourcemap(a.Val, loc.Loc{Start: eqStart + 2}) p.addSourceMapping(loc.Loc{Start: eqStart + 2 + len(a.Val)}) p.print(`)`) - endLoc = eqStart + len(a.Val) + 2 case astro.SpreadAttribute: // noop case astro.ShorthandAttribute: @@ -404,14 +402,12 @@ declare const Astro: Readonly>`, props.Ident) } p.addSourceMapping(a.KeyLoc) p.print(a.Key) - endLoc = a.KeyLoc.Start + len(a.Key) case astro.TemplateLiteralAttribute: p.addSourceMapping(a.KeyLoc) p.print(a.Key) p.print(`":`) p.addSourceMapping(a.ValLoc) p.print(fmt.Sprintf("`%s`", a.Val)) - endLoc = a.ValLoc.Start + len(a.Val) + 2 } if i == len(invalidTSXAttributes)-1 { p.addNilSourceMapping() @@ -425,7 +421,9 @@ declare const Astro: Readonly>`, props.Ident) endLoc = 0 } isSelfClosing := false + hasLeadingSpace := false tmpLoc := endLoc + leadingSpaceLoc := endLoc if len(p.sourcetext) > tmpLoc { for i := 0; i < len(p.sourcetext[tmpLoc:]); i++ { c := p.sourcetext[endLoc : endLoc+1][0] @@ -436,6 +434,10 @@ declare const Astro: Readonly>`, props.Ident) p.addSourceMapping(loc.Loc{Start: endLoc}) endLoc++ break + } else if unicode.IsSpace(rune(c)) { + hasLeadingSpace = true + leadingSpaceLoc = endLoc + endLoc++ } else { endLoc++ } @@ -444,13 +446,17 @@ declare const Astro: Readonly>`, props.Ident) endLoc++ } + if hasLeadingSpace { + p.addSourceMapping(loc.Loc{Start: leadingSpaceLoc}) + p.print(" ") + p.addSourceMapping(loc.Loc{Start: leadingSpaceLoc + 1}) + } + if voidElements[n.Data] && n.FirstChild == nil { p.print("/>") return } if isSelfClosing && n.FirstChild == nil { - p.addSourceMapping(loc.Loc{Start: endLoc - 1}) - p.print(" ") p.addSourceMapping(loc.Loc{Start: endLoc}) p.print("/>") return diff --git a/packages/compiler/test/tsx-sourcemaps/tags.ts b/packages/compiler/test/tsx-sourcemaps/tags.ts index b0679546e..1f83dd1b2 100644 --- a/packages/compiler/test/tsx-sourcemaps/tags.ts +++ b/packages/compiler/test/tsx-sourcemaps/tags.ts @@ -1,5 +1,7 @@ import { test } from 'uvu'; import * as assert from 'uvu/assert'; +import { convertToTSX } from '@astrojs/compiler'; +import { generatedPositionFor, TraceMap } from '@jridgewell/trace-mapping'; import { testTSXSourcemap } from '../utils'; test('tag close', async () => { @@ -14,4 +16,17 @@ test('tag close', async () => { }); }); +test('tag with spaces', async () => { + const input = ``; + const { map } = await convertToTSX(input, { sourcemap: 'both', filename: 'index.astro' }); + const tracer = new TraceMap(map); + + const generated = generatedPositionFor(tracer, { source: 'index.astro', line: 1, column: 14 }); + + assert.equal(generated, { + line: 2, + column: 9, + }); +}); + test.run(); diff --git a/packages/compiler/test/tsx/basic.ts b/packages/compiler/test/tsx/basic.ts index 57e3d6b62..e2df9c797 100644 --- a/packages/compiler/test/tsx/basic.ts +++ b/packages/compiler/test/tsx/basic.ts @@ -214,4 +214,34 @@ export default function __AstroComponent_(_props: Record): any {}\n assert.snapshot(code, output, `expected code to match snapshot`); }); +test('preserves spaces in tag', async () => { + const input = ''; + const output = ` + + +export default function __AstroComponent_(_props: Record): any {}\n`; + const { code } = await convertToTSX(input, { sourcemap: 'external' }); + assert.snapshot(code, output, `expected code to match snapshot`); +}); + +test('preserves spaces after attributes in tag', async () => { + const input = ''; + const output = ` + + +export default function __AstroComponent_(_props: Record): any {}\n`; + const { code } = await convertToTSX(input, { sourcemap: 'external' }); + assert.snapshot(code, output, `expected code to match snapshot`); +}); + +test('preserves spaces in tag', async () => { + const input = ' + +export default function __AstroComponent_(_props: Record): any {}\n`; + const { code } = await convertToTSX(input, { sourcemap: 'external' }); + assert.snapshot(code, output, `expected code to match snapshot`); +}); + test.run();