Skip to content

Commit aac8c89

Browse files
authored
Improve end tag sourcemaps (#578)
* fix(tsx): improve end tag sourcemaps * fix: use ""; instead of just ; * feat(tsx): improve shorthand mapping * feat(tsx): improve expression = mapping Co-authored-by: Nate Moore <[email protected]>
1 parent 7f43360 commit aac8c89

File tree

5 files changed

+165
-26
lines changed

5 files changed

+165
-26
lines changed

.changeset/cold-vans-happen.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@astrojs/compiler': patch
3+
---
4+
5+
Fix end tag sourcemappings for TSX mode

internal/parser_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package astro
2+
3+
import (
4+
"reflect"
5+
"strings"
6+
"testing"
7+
8+
"github.com/withastro/compiler/internal/loc"
9+
"github.com/withastro/compiler/internal/test_utils"
10+
)
11+
12+
type ParserLocTest struct {
13+
name string
14+
input string
15+
expected []int
16+
}
17+
18+
func TestParserLocation(t *testing.T) {
19+
Cases := []ParserLocTest{
20+
{
21+
"end tag I",
22+
`<div id="target"></div>`,
23+
[]int{1, 19},
24+
},
25+
{
26+
"end tag II",
27+
`<div class="TabBox">
28+
<div id="target" class="tab-bar">
29+
<div id="install-npm" class="active toggle"><h5>npm</h5></div>
30+
<div id="install-yarn" class="toggle"><h5>yarn</h5></div>
31+
</div>
32+
</div>`,
33+
[]int{23, 184},
34+
},
35+
{
36+
"end tag III",
37+
`<span id="target" class:list={["link pixel variant", className]} {style}>
38+
<a {href}>
39+
<span><slot /></span>
40+
</a>
41+
</span>
42+
`,
43+
[]int{1, 118},
44+
},
45+
{
46+
"end tag VI",
47+
`<HeadingWrapper id="target">
48+
<h2 class="heading"><UIString key="rightSidebar.community" /></h2>
49+
{
50+
hideOnLargerScreens && (
51+
<svg
52+
class="chevron"
53+
xmlns="http://www.w3.org/2000/svg"
54+
viewBox="0 1 16 16"
55+
width="16"
56+
height="16"
57+
aria-hidden="true"
58+
>
59+
<path
60+
fill-rule="evenodd"
61+
d="M6.22 3.22a.75.75 0 011.06 0l4.25 4.25a.75.75 0 010 1.06l-4.25 4.25a.75.75 0 01-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 010-1.06z"
62+
/>
63+
</svg>
64+
)
65+
}
66+
</HeadingWrapper>`,
67+
[]int{1, 492},
68+
},
69+
}
70+
71+
runParserLocTest(t, Cases)
72+
}
73+
74+
func runParserLocTest(t *testing.T, suite []ParserLocTest) {
75+
for _, tt := range suite {
76+
t.Run(tt.name, func(t *testing.T) {
77+
code := test_utils.Dedent(tt.input)
78+
79+
doc, err := Parse(strings.NewReader(code))
80+
81+
if err != nil {
82+
t.Error(err)
83+
}
84+
target := findTargetNode(doc)
85+
86+
locs := make([]loc.Loc, 0)
87+
for _, start := range tt.expected {
88+
locs = append(locs, loc.Loc{Start: start})
89+
}
90+
91+
if target == nil {
92+
t.Errorf("Loc = nil\nExpected = %v", locs)
93+
return
94+
}
95+
96+
if !reflect.DeepEqual(target.Loc, locs) {
97+
t.Errorf("Loc = %v\nExpected = %v", target.Loc, locs)
98+
}
99+
})
100+
}
101+
}
102+
103+
func walk(doc *Node, cb func(*Node)) {
104+
var f func(*Node)
105+
f = func(n *Node) {
106+
cb(n)
107+
for c := n.FirstChild; c != nil; c = c.NextSibling {
108+
f(c)
109+
}
110+
}
111+
f(doc)
112+
}
113+
114+
func findTargetNode(doc *Node) *Node {
115+
var target *Node = nil
116+
walk(doc, func(n *Node) {
117+
if target != nil {
118+
return
119+
}
120+
for _, attr := range n.Attr {
121+
if attr.Key == "id" && attr.Val == "target" {
122+
target = n
123+
return
124+
}
125+
}
126+
})
127+
return target
128+
}

internal/printer/print-to-tsx.go

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s>>`, props.Ident)
8282
buf := strings.TrimSpace(string(p.output))
8383
if len(buf) > 1 {
8484
char := rune(buf[len(buf)-1:][0])
85-
// If the existing buffer ends with a punctuation character, we need a `;`
85+
// If the existing buffer ends with any character other than ;, we need to add a `;`
8686
if char != ';' {
8787
p.addNilSourceMapping()
88-
p.print(";")
88+
p.print("\"\";")
8989
}
9090
}
9191
// We always need to start the body with `<Fragment>`
@@ -202,7 +202,7 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s>>`, props.Ident)
202202
p.print(`</Fragment>`)
203203
}
204204
}
205-
if len(n.Loc) == 2 {
205+
if len(n.Loc) > 1 {
206206
p.addSourceMapping(n.Loc[1])
207207
} else {
208208
p.addSourceMapping(n.Loc[0])
@@ -240,13 +240,14 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s>>`, props.Ident)
240240
continue
241241
}
242242
offset := 1
243-
if a.Type == astro.ShorthandAttribute {
244-
offset = 2
243+
if a.Type != astro.ShorthandAttribute {
244+
p.addSourceMapping(loc.Loc{Start: a.KeyLoc.Start - offset})
245245
}
246-
p.addSourceMapping(loc.Loc{Start: a.KeyLoc.Start - offset})
247246
p.print(" ")
248247
eqStart := a.KeyLoc.Start + strings.IndexRune(p.sourcetext[a.KeyLoc.Start:], '=')
249-
p.addSourceMapping(a.KeyLoc)
248+
if a.Type != astro.ShorthandAttribute {
249+
p.addSourceMapping(a.KeyLoc)
250+
}
250251
if a.Namespace != "" {
251252
p.print(a.Namespace)
252253
p.print(":")
@@ -257,9 +258,9 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s>>`, props.Ident)
257258
p.addSourceMapping(loc.Loc{Start: eqStart})
258259
p.print("=")
259260
if len(a.Val) > 0 {
260-
p.addSourceMapping(loc.Loc{Start: eqStart + 1})
261+
p.addSourceMapping(loc.Loc{Start: a.ValLoc.Start - 1})
261262
p.print(`"` + encodeDoubleQuote(a.Val) + `"`)
262-
endLoc = a.ValLoc.Start + len(a.Val)
263+
endLoc = a.ValLoc.Start + len(a.Val) + 1
263264
} else {
264265
p.addSourceMapping(loc.Loc{Start: a.ValLoc.Start - 1})
265266
p.print(`"`)
@@ -272,7 +273,7 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s>>`, props.Ident)
272273
endLoc = a.KeyLoc.Start + len(a.Key)
273274
case astro.ExpressionAttribute:
274275
p.print(a.Key)
275-
p.addSourceMapping(loc.Loc{Start: a.KeyLoc.Start - 1})
276+
p.addSourceMapping(loc.Loc{Start: a.KeyLoc.Start + len(a.Key)})
276277
p.print(`=`)
277278
p.addSourceMapping(loc.Loc{Start: eqStart + 1})
278279
p.print(`{`)
@@ -293,15 +294,15 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s>>`, props.Ident)
293294
if len(withoutComments) == 0 {
294295
return
295296
}
296-
p.print(a.Key)
297-
p.print(`=`)
298-
p.addSourceMapping(loc.Loc{Start: a.KeyLoc.Start - 1})
299-
p.print(`{`)
300297
p.addSourceMapping(a.KeyLoc)
298+
p.printf(a.Key)
299+
p.addSourceMapping(loc.Loc{Start: a.KeyLoc.Start - 1})
300+
p.printf("={")
301+
p.addSourceMapping(loc.Loc{Start: a.KeyLoc.Start})
301302
p.print(a.Key)
302303
p.addSourceMapping(loc.Loc{Start: a.KeyLoc.Start + len(a.Key)})
303-
p.print(`}`)
304-
endLoc = a.KeyLoc.Start + len(a.Key)
304+
p.print("}")
305+
endLoc = a.KeyLoc.Start + len(a.Key) + 1
305306
case astro.TemplateLiteralAttribute:
306307
p.print(a.Key)
307308
p.addSourceMapping(loc.Loc{Start: eqStart})
@@ -411,6 +412,11 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s>>`, props.Ident)
411412
// Render any child nodes
412413
for c := n.FirstChild; c != nil; c = c.NextSibling {
413414
renderTsx(p, c)
415+
if len(c.Loc) > 1 {
416+
endLoc = c.Loc[1].Start + len(c.Data) + 1
417+
} else if len(c.Loc) == 1 {
418+
endLoc = c.Loc[0].Start + len(c.Data)
419+
}
414420
}
415421
// Special case because of trailing expression close in scripts
416422
if n.DataAtom == atom.Script {
@@ -437,5 +443,5 @@ declare const Astro: Readonly<import('astro').AstroGlobal<%s>>`, props.Ident)
437443
p.addSourceMapping(loc.Loc{Start: endLoc})
438444
}
439445
p.print(">")
440-
p.addSourceMapping(loc.Loc{Start: endLoc})
446+
p.addSourceMapping(loc.Loc{Start: endLoc + 1})
441447
}

packages/compiler/test/tsx/basic.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ console.log("hello")
6767
const output = `
6868
console.log("hello")
6969
70-
;<Fragment>
70+
"";<Fragment>
7171
{hello}
7272
7373
</Fragment>
@@ -87,7 +87,7 @@ const { hello } = Astro.props
8787
const output = `
8888
const { hello } = Astro.props
8989
90-
;<Fragment>
90+
"";<Fragment>
9191
<div class={hello}></div>
9292
9393
</Fragment>

packages/compiler/test/tsx/props.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ interface Props {}
4343
const output = `${PREFIX}
4444
interface Props {}
4545
46-
;<Fragment>
46+
"";<Fragment>
4747
<div></div>
4848
4949
</Fragment>
@@ -123,7 +123,7 @@ type Props = {}
123123
const output = `${PREFIX}
124124
type Props = {}
125125
126-
;<Fragment>
126+
"";<Fragment>
127127
<div></div>
128128
129129
</Fragment>
@@ -143,7 +143,7 @@ interface Props<T> {}
143143
const output = `${PREFIX}
144144
interface Props<T> {}
145145
146-
;<Fragment>
146+
"";<Fragment>
147147
<div></div>
148148
149149
</Fragment>
@@ -163,7 +163,7 @@ interface Props<T extends Other<{ [key: string]: any }>> {}
163163
const output = `${PREFIX}
164164
interface Props<T extends Other<{ [key: string]: any }>> {}
165165
166-
;<Fragment>
166+
"";<Fragment>
167167
<div></div>
168168
169169
</Fragment>
@@ -183,7 +183,7 @@ interface Props<T extends { [key: string]: any }, P extends string ? { [key: str
183183
const output = `${PREFIX}
184184
interface Props<T extends { [key: string]: any }, P extends string ? { [key: string]: any }: never> {}
185185
186-
;<Fragment>
186+
"";<Fragment>
187187
<div></div>
188188
189189
</Fragment>
@@ -203,7 +203,7 @@ interface Props<T extends Something<false> ? A : B, P extends string ? { [key: s
203203
const output = `${PREFIX}
204204
interface Props<T extends Something<false> ? A : B, P extends string ? { [key: string]: any }: never> {}
205205
206-
;<Fragment>
206+
"";<Fragment>
207207
<div></div>
208208
209209
</Fragment>
@@ -227,7 +227,7 @@ interface Props<Tag extends keyof JSX.IntrinsicElements> extends HTMLAttributes<
227227
as?: Tag;
228228
}
229229
230-
;<Fragment>
230+
"";<Fragment>
231231
<div></div>
232232
233233
</Fragment>

0 commit comments

Comments
 (0)