diff --git a/.changeset/chilled-starfishes-search.md b/.changeset/chilled-starfishes-search.md new file mode 100644 index 000000000..56d23aee3 --- /dev/null +++ b/.changeset/chilled-starfishes-search.md @@ -0,0 +1,5 @@ +--- +'@astrojs/compiler': patch +--- + +Fixes an issue where unterminated quoted attributes caused the compiler to crash diff --git a/internal/printer/print-to-json.go b/internal/printer/print-to-json.go index f9308e555..3367633d1 100644 --- a/internal/printer/print-to-json.go +++ b/internal/printer/print-to-json.go @@ -230,7 +230,7 @@ func renderNode(p *printer, parent *ASTNode, n *Node, opts t.ParseOptions) { } position := attrPositionAt(p, &attr, opts) raw := "" - if attr.Type == QuotedAttribute { + if attr.Type == QuotedAttribute || attr.Type == TemplateLiteralAttribute { start := attr.ValLoc.Start - 1 end := attr.ValLoc.Start + len(attr.Val) diff --git a/internal/printer/printer_test.go b/internal/printer/printer_test.go index e37d7af1e..ed8d40d88 100644 --- a/internal/printer/printer_test.go +++ b/internal/printer/printer_test.go @@ -3342,6 +3342,21 @@ const c = '\'' source: `

Hello world!

`, want: []ASTNode{{Type: "element", Name: "html", Children: []ASTNode{{Type: "element", Name: "body", Children: []ASTNode{{Type: "element", Name: "h1", Children: []ASTNode{{Type: "text", Value: "Hello world!"}}}, {Type: "element", Name: "style"}}}}}}, }, + { + name: "element with unterminated double quote attribute", + source: `
`, + want: []ASTNode{{Type: "element", Name: "main", Attributes: []ASTNode{{Type: "attribute", Kind: "template-literal", Name: "id", Value: "gotcha", Raw: "`gotcha"}}}}, + }, } for _, tt := range tests { diff --git a/internal/token.go b/internal/token.go index afae341bc..ef2ea3fa2 100644 --- a/internal/token.go +++ b/internal/token.go @@ -1448,6 +1448,29 @@ func (z *Tokenizer) readTagAttrVal() { for { c := z.readByte() if z.err != nil { + if z.err == io.EOF { + // rescan, closing any potentially unterminated quoted attribute values + for i := z.pendingAttr[1].Start; i < z.raw.End; i++ { + c := z.buf[i] + if unicode.IsSpace(rune(c)) || c == '/' || c == '>' { + z.pendingAttr[1].End = i + break + } + if i == z.raw.End-1 { + z.pendingAttr[1].End = i + break + } + } + z.handler.AppendError(&loc.ErrorWithRange{ + Code: loc.ERROR_UNTERMINATED_STRING, + Text: `Unterminated quoted attribute`, + Range: loc.Range{ + Loc: loc.Loc{Start: z.data.Start}, + Len: z.raw.End, + }, + }) + return + } z.pendingAttr[1].End = z.raw.End return } @@ -1463,7 +1486,18 @@ func (z *Tokenizer) readTagAttrVal() { c := z.readByte() if z.err != nil { if z.err == io.EOF { - z.pendingAttr[1].End = z.pendingAttr[1].Start + // rescan, closing any potentially unterminated attribute values + for i := z.pendingAttr[1].Start; i < z.raw.End; i++ { + c := z.buf[i] + if unicode.IsSpace(rune(c)) || c == '/' || c == '>' { + z.pendingAttr[1].End = i + break + } + if i == z.raw.End-1 { + z.pendingAttr[1].End = i + break + } + } z.handler.AppendError(&loc.ErrorWithRange{ Code: loc.ERROR_UNTERMINATED_STRING, Text: `Unterminated template literal attribute`, diff --git a/packages/compiler/test/tsx-sourcemaps/unfinished-literal.ts b/packages/compiler/test/tsx-sourcemaps/unfinished-literal.ts index 803bcbdc5..22d015005 100644 --- a/packages/compiler/test/tsx-sourcemaps/unfinished-literal.ts +++ b/packages/compiler/test/tsx-sourcemaps/unfinished-literal.ts @@ -2,10 +2,9 @@ import { test } from 'uvu'; import * as assert from 'uvu/assert'; import { convertToTSX } from '@astrojs/compiler'; -const input = `
-`; - test('does not panic on unfinished template literal attribute', async () => { + const input = `
+ `; let error = 0; try { const output = await convertToTSX(input, { filename: 'index.astro', sourcemap: 'inline' }); @@ -16,3 +15,29 @@ test('does not panic on unfinished template literal attribute', async () => { assert.equal(error, 0, `compiler should not have panicked`); }); + +test('does not panic on unfinished double quoted attribute', async () => { + const input = `
{ + const input = `