Skip to content

Commit

Permalink
more info in yarn pnp error messages (#2585)
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Oct 1, 2022
1 parent fdbea94 commit 81fa2ca
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 135 deletions.
27 changes: 25 additions & 2 deletions internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -14150,15 +14150,19 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
case *js_ast.EString:
// "hydrateRuntimeState(JSON.parse(<string literal>))"
source := logger.Source{KeyPath: p.source.KeyPath, Contents: helpers.UTF16ToString(a.Value)}
log := logger.NewStringInJSLog(p.log, &p.tracker, arg.Loc, source.Contents)
stringInJSTable := logger.GenerateStringInJSTable(p.source.Contents, arg.Loc, source.Contents)
log := logger.NewStringInJSLog(p.log, &p.tracker, stringInJSTable)
p.manifestForYarnPnP, _ = ParseJSON(log, source, JSONOptions{})
remapExprLocsInJSON(&p.manifestForYarnPnP, stringInJSTable)

case *js_ast.EIdentifier:
// "hydrateRuntimeState(JSON.parse(<identifier>))"
if data, ok := p.stringLocalsForYarnPnP[a.Ref]; ok {
source := logger.Source{KeyPath: p.source.KeyPath, Contents: helpers.UTF16ToString(data.value)}
log := logger.NewStringInJSLog(p.log, &p.tracker, data.loc, source.Contents)
stringInJSTable := logger.GenerateStringInJSTable(p.source.Contents, data.loc, source.Contents)
log := logger.NewStringInJSLog(p.log, &p.tracker, stringInJSTable)
p.manifestForYarnPnP, _ = ParseJSON(log, source, JSONOptions{})
remapExprLocsInJSON(&p.manifestForYarnPnP, stringInJSTable)
}
}
}
Expand Down Expand Up @@ -14521,6 +14525,25 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO
return expr, exprOut{}
}

func remapExprLocsInJSON(expr *js_ast.Expr, table []logger.StringInJSTableEntry) {
expr.Loc = logger.RemapStringInJSLoc(table, expr.Loc)

switch e := expr.Data.(type) {
case *js_ast.EArray:
e.CloseBracketLoc = logger.RemapStringInJSLoc(table, e.CloseBracketLoc)
for i := range e.Items {
remapExprLocsInJSON(&e.Items[i], table)
}

case *js_ast.EObject:
e.CloseBraceLoc = logger.RemapStringInJSLoc(table, e.CloseBraceLoc)
for i := range e.Properties {
remapExprLocsInJSON(&e.Properties[i].Key, table)
remapExprLocsInJSON(&e.Properties[i].ValueOrNil, table)
}
}
}

func (p *parser) convertSymbolUseToCall(ref js_ast.Ref, isSingleNonSpreadArgCall bool) {
// Remove the normal symbol use
use := p.symbolUses[ref]
Expand Down
12 changes: 8 additions & 4 deletions internal/js_parser/json_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,12 @@ func (p *jsonParser) parseExpr() js_ast.Expr {
if p.lexer.HasNewlineBefore {
isSingleLine = false
}
closeBracketLoc := p.lexer.Loc()
p.lexer.Expect(js_lexer.TCloseBracket)
return js_ast.Expr{Loc: loc, Data: &js_ast.EArray{
Items: items,
IsSingleLine: isSingleLine,
Items: items,
IsSingleLine: isSingleLine,
CloseBracketLoc: closeBracketLoc,
}}

case js_lexer.TOpenBrace:
Expand Down Expand Up @@ -146,10 +148,12 @@ func (p *jsonParser) parseExpr() js_ast.Expr {
if p.lexer.HasNewlineBefore {
isSingleLine = false
}
closeBraceLoc := p.lexer.Loc()
p.lexer.Expect(js_lexer.TCloseBrace)
return js_ast.Expr{Loc: loc, Data: &js_ast.EObject{
Properties: properties,
IsSingleLine: isSingleLine,
Properties: properties,
IsSingleLine: isSingleLine,
CloseBraceLoc: closeBraceLoc,
}}

default:
Expand Down
209 changes: 114 additions & 95 deletions internal/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -1701,111 +1701,135 @@ func allowOverride(overrides map[MsgID]LogLevel, id MsgID, kind MsgKind) (MsgKin
return kind, true
}

// For Yarn PnP we sometimes parse JSON embedded in a JS string. This is a shim
// that remaps log message locations inside the embedded string literal into
// log messages in the actual JS file, which makes them easier to understand.
func NewStringInJSLog(log Log, outerTracker *LineColumnTracker, outerStringLiteralLoc Loc, innerContents string) Log {
type entry struct {
line int32
column int32
loc Loc
}

var table []entry
oldAddMsg := log.AddMsg
type StringInJSTableEntry struct {
innerLine int32
innerColumn int32
innerLoc Loc
outerLoc Loc
}

// For Yarn PnP we sometimes parse JSON embedded in a JS string. This generates
// a table that remaps locations inside the embedded JSON string literal into
// locations in the actual JS file, which makes them easier to understand.
func GenerateStringInJSTable(outerContents string, outerStringLiteralLoc Loc, innerContents string) (table []StringInJSTableEntry) {
i := int32(0)
n := int32(len(innerContents))
line := int32(1)
column := int32(0)
loc := Loc{Start: outerStringLiteralLoc.Start + 1}

for i < n {
// Ignore line continuations. A line continuation is not an escaped newline.
for {
if c, _ := utf8.DecodeRuneInString(outerContents[loc.Start:]); c != '\\' {
break
}
c, width := utf8.DecodeRuneInString(outerContents[loc.Start+1:])
switch c {
case '\n', '\r', '\u2028', '\u2029':
loc.Start += 1 + int32(width)
if c == '\r' && outerContents[loc.Start] == '\n' {
// Make sure Windows CRLF counts as a single newline
loc.Start++
}
continue
}
break
}

generateTable := func() {
i := 0
n := len(innerContents)
line := int32(1)
column := int32(0)
loc := Loc{Start: outerStringLiteralLoc.Start + 1}
outerContents := outerTracker.contents
c, width := utf8.DecodeRuneInString(innerContents[i:])

for i < n {
// Ignore line continuations. A line continuation is not an escaped newline.
for {
if c, _ := utf8.DecodeRuneInString(outerContents[loc.Start:]); c != '\\' {
break
}
c, width := utf8.DecodeRuneInString(outerContents[loc.Start+1:])
switch c {
case '\n', '\r', '\u2028', '\u2029':
loc.Start += 1 + int32(width)
if c == '\r' && outerContents[loc.Start] == '\n' {
// Make sure Windows CRLF counts as a single newline
loc.Start++
}
continue
}
break
// Compress the table using run-length encoding
table = append(table, StringInJSTableEntry{innerLine: line, innerColumn: column, innerLoc: Loc{Start: i}, outerLoc: loc})
if len(table) > 1 {
if last := table[len(table)-2]; line == last.innerLine && loc.Start-column == last.outerLoc.Start-last.innerColumn {
table = table[:len(table)-1]
}
}

c, width := utf8.DecodeRuneInString(innerContents[i:])
// Advance the inner line/column
switch c {
case '\n', '\r', '\u2028', '\u2029':
line++
column = 0

// Compress the table using run-length encoding
table = append(table, entry{line: line, column: column, loc: loc})
if len(table) > 1 {
if last := table[len(table)-2]; line == last.line && loc.Start-column == last.loc.Start-last.column {
table = table[:len(table)-1]
}
// Handle newlines on Windows
if c == '\r' && i+1 < n && innerContents[i+1] == '\n' {
i++
}

// Advance the inner line/column
default:
column += int32(width)
}
i += int32(width)

// Advance the outer loc, assuming the string syntax is already valid
c, width = utf8.DecodeRuneInString(outerContents[loc.Start:])
if c == '\r' && outerContents[loc.Start+1] == '\n' {
// Handle newlines on Windows in template literal strings
loc.Start += 2
} else if c != '\\' {
loc.Start += int32(width)
} else {
// Handle an escape sequence
c, width = utf8.DecodeRuneInString(outerContents[loc.Start+1:])
switch c {
case '\n', '\r', '\u2028', '\u2029':
line++
column = 0

// Handle newlines on Windows
if c == '\r' && i+1 < n && innerContents[i+1] == '\n' {
i++
case 'x':
// 2-digit hexadecimal
loc.Start += 1 + 2

case 'u':
loc.Start++
if outerContents[loc.Start] == '{' {
// Variable-length
for outerContents[loc.Start] != '}' {
loc.Start++
}
loc.Start++
} else {
// Fixed-length
loc.Start += 4
}

case '\n', '\r', '\u2028', '\u2029':
// This will be handled by the next iteration
break

default:
column += int32(width)
loc.Start += 1 + int32(width)
}
i += width

// Advance the outer loc, assuming the string syntax is already valid
c, width = utf8.DecodeRuneInString(outerContents[loc.Start:])
if c == '\r' && outerContents[loc.Start+1] == '\n' {
// Handle newlines on Windows in template literal strings
loc.Start += 2
} else if c != '\\' {
loc.Start += int32(width)
} else {
// Handle an escape sequence
c, width = utf8.DecodeRuneInString(outerContents[loc.Start+1:])
switch c {
case 'x':
// 2-digit hexadecimal
loc.Start += 1 + 2

case 'u':
loc.Start++
if outerContents[loc.Start] == '{' {
// Variable-length
for outerContents[loc.Start] != '}' {
loc.Start++
}
loc.Start++
} else {
// Fixed-length
loc.Start += 4
}
}
}

case '\n', '\r', '\u2028', '\u2029':
// This will be handled by the next iteration
break
return
}

default:
loc.Start += 1 + int32(width)
}
func RemapStringInJSLoc(table []StringInJSTableEntry, innerLoc Loc) Loc {
count := len(table)
index := 0

// Binary search to find the previous entry
for count > 0 {
step := count / 2
i := index + step
if i+1 < len(table) {
if entry := table[i+1]; entry.innerLoc.Start < innerLoc.Start {
index = i + 1
count -= step + 1
continue
}
}
count = step
}

entry := table[index]
entry.outerLoc.Start += innerLoc.Start - entry.innerLoc.Start // Undo run-length compression
return entry.outerLoc
}

func NewStringInJSLog(log Log, outerTracker *LineColumnTracker, table []StringInJSTableEntry) Log {
oldAddMsg := log.AddMsg

remapLineAndColumnToLoc := func(line int32, column int32) Loc {
count := len(table)
index := 0
Expand All @@ -1815,7 +1839,7 @@ func NewStringInJSLog(log Log, outerTracker *LineColumnTracker, outerStringLiter
step := count / 2
i := index + step
if i+1 < len(table) {
if entry := table[i+1]; entry.line < line || (entry.line == line && entry.column < column) {
if entry := table[i+1]; entry.innerLine < line || (entry.innerLine == line && entry.innerColumn < column) {
index = i + 1
count -= step + 1
continue
Expand All @@ -1825,20 +1849,15 @@ func NewStringInJSLog(log Log, outerTracker *LineColumnTracker, outerStringLiter
}

entry := table[index]
entry.loc.Start += column - entry.column // Undo run-length compression
return entry.loc
entry.outerLoc.Start += column - entry.innerColumn // Undo run-length compression
return entry.outerLoc
}

remapData := func(data MsgData) MsgData {
if data.Location == nil {
return data
}

// Generate and cache a lookup table to accelerate remappings
if table == nil {
generateTable()
}

// Generate a range in the outer source using the line/column/length in the inner source
r := Range{Loc: remapLineAndColumnToLoc(int32(data.Location.Line), int32(data.Location.Column))}
if data.Location.Length != 0 {
Expand Down
Loading

0 comments on commit 81fa2ca

Please sign in to comment.