mirror of
https://github.com/hanzoai/esbuild.git
synced 2026-09-24 16:58:50 +00:00
add a clang format style
This commit is contained in:
@@ -1249,6 +1249,8 @@ func (service *serviceType) handleFormatMessagesRequest(id uint32, request map[s
|
||||
switch logStyle {
|
||||
case "default":
|
||||
options.LogStyle = api.LogStyleDefault
|
||||
case "clang":
|
||||
options.LogStyle = api.LogStyleClang
|
||||
case "visualstudio":
|
||||
options.LogStyle = api.LogStyleVisualStudio
|
||||
default:
|
||||
|
||||
@@ -47,13 +47,6 @@ const (
|
||||
LevelSilent
|
||||
)
|
||||
|
||||
type LogStyle int8
|
||||
|
||||
const (
|
||||
StyleDefault LogStyle = iota
|
||||
StyleVisualStudio
|
||||
)
|
||||
|
||||
type MsgKind uint8
|
||||
|
||||
const (
|
||||
@@ -858,6 +851,8 @@ func OutputOptionsForArgs(osArgs []string) OutputOptions {
|
||||
|
||||
case "--log-style=default":
|
||||
options.LogStyle = StyleDefault
|
||||
case "--log-level=clang":
|
||||
options.LogStyle = StyleClang
|
||||
case "--log-level=visualstudio":
|
||||
options.LogStyle = StyleVisualStudio
|
||||
}
|
||||
@@ -1222,6 +1217,9 @@ type OutputOptions struct {
|
||||
|
||||
func (msg Msg) String(options OutputOptions, terminalInfo TerminalInfo) string {
|
||||
switch options.LogStyle {
|
||||
case StyleClang:
|
||||
return msgToStringClang(msg, options, terminalInfo)
|
||||
|
||||
case StyleVisualStudio:
|
||||
return msgToStringVisualStudio(msg, options, terminalInfo)
|
||||
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type LogStyle int8
|
||||
|
||||
const (
|
||||
StyleDefault LogStyle = iota
|
||||
StyleClang
|
||||
StyleVisualStudio
|
||||
)
|
||||
|
||||
func linkifyText(text string, underline string, reset string) string {
|
||||
if underline == "" {
|
||||
return text
|
||||
}
|
||||
|
||||
https := strings.Index(text, "https://")
|
||||
if https == -1 {
|
||||
return text
|
||||
}
|
||||
|
||||
sb := strings.Builder{}
|
||||
for {
|
||||
https := strings.Index(text, "https://")
|
||||
if https == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
end := strings.IndexByte(text[https:], ' ')
|
||||
if end == -1 {
|
||||
end = len(text)
|
||||
} else {
|
||||
end += https
|
||||
}
|
||||
|
||||
// Remove trailing punctuation
|
||||
if end > https {
|
||||
switch text[end-1] {
|
||||
case '.', ',', '?', '!', ')', ']', '}':
|
||||
end--
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString(text[:https])
|
||||
sb.WriteString(underline)
|
||||
sb.WriteString(text[https:end])
|
||||
sb.WriteString(reset)
|
||||
text = text[end:]
|
||||
}
|
||||
|
||||
sb.WriteString(text)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func wrapWordsInString(text string, width int) []string {
|
||||
runs := []string{}
|
||||
|
||||
outer:
|
||||
for text != "" {
|
||||
i := 0
|
||||
x := 0
|
||||
wordEndI := 0
|
||||
|
||||
// Skip over any leading spaces
|
||||
for i < len(text) && text[i] == ' ' {
|
||||
i++
|
||||
x++
|
||||
}
|
||||
|
||||
// Find out how many words will fit in this run
|
||||
for i < len(text) {
|
||||
oldWordEndI := wordEndI
|
||||
wordStartI := i
|
||||
|
||||
// Find the end of the word
|
||||
for i < len(text) {
|
||||
c, width := utf8.DecodeRuneInString(text[i:])
|
||||
if c == ' ' {
|
||||
break
|
||||
}
|
||||
i += width
|
||||
x += 1 // Naively assume that each unicode code point is a single column
|
||||
}
|
||||
wordEndI = i
|
||||
|
||||
// Split into a new run if this isn't the first word in the run and the end is past the width
|
||||
if wordStartI > 0 && x > width {
|
||||
runs = append(runs, text[:oldWordEndI])
|
||||
text = text[wordStartI:]
|
||||
continue outer
|
||||
}
|
||||
|
||||
// Skip over any spaces after the word
|
||||
for i < len(text) && text[i] == ' ' {
|
||||
i++
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, this is the last run (i.e. everything fits)
|
||||
break
|
||||
}
|
||||
|
||||
// Remove any trailing spaces on the last run
|
||||
for len(text) > 0 && text[len(text)-1] == ' ' {
|
||||
text = text[:len(text)-1]
|
||||
}
|
||||
runs = append(runs, text)
|
||||
return runs
|
||||
}
|
||||
|
||||
type formattedLineSpan struct {
|
||||
firstLineBefore string
|
||||
firstLineMarked string
|
||||
firstLineAfter string
|
||||
afterFirstLine string
|
||||
marker string
|
||||
indent string
|
||||
}
|
||||
|
||||
func formatLineSpan(lineText string, line int, column int, length int, widthLimit int) formattedLineSpan {
|
||||
endOfFirstLine := len(lineText)
|
||||
|
||||
// Note: This uses "IndexByte" because Go implements this with SIMD, which
|
||||
// can matter a lot for really long lines. Some people pass huge >100mb
|
||||
// minified files as line text for the log message.
|
||||
if i := strings.IndexByte(lineText, '\n'); i >= 0 {
|
||||
endOfFirstLine = i
|
||||
}
|
||||
|
||||
firstLine := lineText[:endOfFirstLine]
|
||||
afterFirstLine := lineText[endOfFirstLine:]
|
||||
if afterFirstLine != "" && !strings.HasSuffix(afterFirstLine, "\n") {
|
||||
afterFirstLine += "\n"
|
||||
}
|
||||
|
||||
// Clamp values in range
|
||||
if line < 0 {
|
||||
line = 0
|
||||
}
|
||||
if column < 0 {
|
||||
column = 0
|
||||
}
|
||||
if length < 0 {
|
||||
length = 0
|
||||
}
|
||||
if column > endOfFirstLine {
|
||||
column = endOfFirstLine
|
||||
}
|
||||
if length > endOfFirstLine-column {
|
||||
length = endOfFirstLine - column
|
||||
}
|
||||
|
||||
spacesPerTab := 2
|
||||
lineText = renderTabStops(firstLine, spacesPerTab)
|
||||
textUpToLoc := renderTabStops(firstLine[:column], spacesPerTab)
|
||||
markerStart := len(textUpToLoc)
|
||||
markerEnd := markerStart
|
||||
indent := strings.Repeat(" ", estimateWidthInTerminal(textUpToLoc))
|
||||
marker := "^"
|
||||
|
||||
// Extend markers to cover the full range of the error
|
||||
if length > 0 {
|
||||
markerEnd = len(renderTabStops(firstLine[:column+length], spacesPerTab))
|
||||
}
|
||||
|
||||
// Clip the marker to the bounds of the line
|
||||
if markerStart > len(lineText) {
|
||||
markerStart = len(lineText)
|
||||
}
|
||||
if markerEnd > len(lineText) {
|
||||
markerEnd = len(lineText)
|
||||
}
|
||||
if markerEnd < markerStart {
|
||||
markerEnd = markerStart
|
||||
}
|
||||
|
||||
// Trim the line to fit the terminal width
|
||||
width := widthLimit
|
||||
if width < 1 {
|
||||
width = 1
|
||||
}
|
||||
if column == endOfFirstLine {
|
||||
// If the marker is at the very end of the line, the marker will be a "^"
|
||||
// character that extends one column past the end of the line. In this case
|
||||
// we should reserve a column at the end so the marker doesn't wrap.
|
||||
width -= 1
|
||||
}
|
||||
if len(lineText) > width {
|
||||
// Try to center the error
|
||||
sliceStart := (markerStart + markerEnd - width) / 2
|
||||
if sliceStart > markerStart-width/5 {
|
||||
sliceStart = markerStart - width/5
|
||||
}
|
||||
if sliceStart < 0 {
|
||||
sliceStart = 0
|
||||
}
|
||||
if sliceStart > len(lineText)-width {
|
||||
sliceStart = len(lineText) - width
|
||||
}
|
||||
sliceEnd := sliceStart + width
|
||||
|
||||
// Slice the line
|
||||
slicedLine := lineText[sliceStart:sliceEnd]
|
||||
markerStart -= sliceStart
|
||||
markerEnd -= sliceStart
|
||||
if markerStart < 0 {
|
||||
markerStart = 0
|
||||
}
|
||||
if markerEnd > len(slicedLine) {
|
||||
markerEnd = len(slicedLine)
|
||||
}
|
||||
|
||||
// Truncate the ends with "..."
|
||||
if len(slicedLine) > 3 && sliceStart > 0 {
|
||||
slicedLine = "..." + slicedLine[3:]
|
||||
if markerStart < 3 {
|
||||
markerStart = 3
|
||||
}
|
||||
}
|
||||
if len(slicedLine) > 3 && sliceEnd < len(lineText) {
|
||||
slicedLine = slicedLine[:len(slicedLine)-3] + "..."
|
||||
if markerEnd > len(slicedLine)-3 {
|
||||
markerEnd = len(slicedLine) - 3
|
||||
}
|
||||
if markerEnd < markerStart {
|
||||
markerEnd = markerStart
|
||||
}
|
||||
}
|
||||
|
||||
// Now we can compute the indent
|
||||
lineText = slicedLine
|
||||
indent = strings.Repeat(" ", estimateWidthInTerminal(lineText[:markerStart]))
|
||||
}
|
||||
|
||||
// If marker is still multi-character after clipping, make the marker wider
|
||||
if markerEnd-markerStart > 1 {
|
||||
marker = strings.Repeat("~", estimateWidthInTerminal(lineText[markerStart:markerEnd]))
|
||||
}
|
||||
|
||||
return formattedLineSpan{
|
||||
firstLineBefore: lineText[:markerStart],
|
||||
firstLineMarked: lineText[markerStart:markerEnd],
|
||||
firstLineAfter: lineText[markerEnd:],
|
||||
afterFirstLine: afterFirstLine,
|
||||
marker: marker,
|
||||
indent: indent,
|
||||
}
|
||||
}
|
||||
|
||||
// Estimate the number of columns this string will take when printed
|
||||
func estimateWidthInTerminal(text string) int {
|
||||
// For now just assume each code point is one column. This is wrong but is
|
||||
// less wrong than assuming each code unit is one column.
|
||||
width := 0
|
||||
for text != "" {
|
||||
c, size := utf8.DecodeRuneInString(text)
|
||||
text = text[size:]
|
||||
|
||||
// Ignore the Zero Width No-Break Space character (UTF-8 BOM)
|
||||
if c != 0xFEFF {
|
||||
width++
|
||||
}
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
func renderTabStops(withTabs string, spacesPerTab int) string {
|
||||
if !strings.ContainsRune(withTabs, '\t') {
|
||||
return withTabs
|
||||
}
|
||||
|
||||
withoutTabs := strings.Builder{}
|
||||
count := 0
|
||||
|
||||
for _, c := range withTabs {
|
||||
if c == '\t' {
|
||||
spaces := spacesPerTab - count%spacesPerTab
|
||||
for i := 0; i < spaces; i++ {
|
||||
withoutTabs.WriteRune(' ')
|
||||
count++
|
||||
}
|
||||
} else {
|
||||
withoutTabs.WriteRune(c)
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return withoutTabs.String()
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// This log style is intended to help integrate esbuild with tools that expect
|
||||
// clang's output style. More information:
|
||||
//
|
||||
// - https://clang.llvm.org/docs/UsersManual.html#formatting-of-diagnostics
|
||||
// - https://clang.llvm.org/diagnostics.html
|
||||
func msgToStringClang(msg Msg, options OutputOptions, terminalInfo TerminalInfo) string {
|
||||
var text strings.Builder
|
||||
writeMsgDataClang(&text, msg.ID, msg.Data, options, terminalInfo, msg.Kind)
|
||||
for _, note := range msg.Notes {
|
||||
if note.Location != nil {
|
||||
writeMsgDataClang(&text, MsgID_None, note, options, terminalInfo, Note)
|
||||
}
|
||||
}
|
||||
return text.String()
|
||||
}
|
||||
|
||||
func writeMsgDataClang(text *strings.Builder, id MsgID, data MsgData, options OutputOptions, terminalInfo TerminalInfo, kind MsgKind) {
|
||||
var colors Colors
|
||||
if terminalInfo.UseColorEscapes {
|
||||
colors = TerminalColors
|
||||
}
|
||||
text.WriteString(colors.Bold)
|
||||
|
||||
if loc := data.Location; loc != nil {
|
||||
text.WriteString(loc.File.Select(options.PathStyle))
|
||||
|
||||
// Note: Need to adjust the column from 0-based to 1-based
|
||||
if loc.Line > 0 {
|
||||
text.WriteString(fmt.Sprintf(":%d:%d", loc.Line, loc.Column+1))
|
||||
}
|
||||
text.WriteString(": ")
|
||||
}
|
||||
|
||||
var kindColor string
|
||||
var kindText string
|
||||
switch kind {
|
||||
case Error:
|
||||
kindColor, kindText = colors.Red, "error"
|
||||
case Note:
|
||||
kindColor, kindText = colors.Dim, "note"
|
||||
default:
|
||||
kindColor, kindText = colors.Magenta, "warning"
|
||||
}
|
||||
|
||||
msgID := MsgIDToString(id)
|
||||
if msgID != "" {
|
||||
msgID = fmt.Sprintf(" [%s]", msgID)
|
||||
}
|
||||
|
||||
text.WriteString(fmt.Sprintf("%s%s:%s %s%s%s%s\n",
|
||||
kindColor, kindText, colors.Reset, colors.Bold, data.Text, msgID, colors.Reset))
|
||||
|
||||
if loc := data.Location; loc != nil && loc.Line > 0 {
|
||||
widthLimit := terminalInfo.Width
|
||||
if widthLimit < 1 {
|
||||
widthLimit = defaultTerminalWidth
|
||||
}
|
||||
|
||||
span := formatLineSpan(loc.LineText, loc.Line, loc.Column, loc.Length, widthLimit)
|
||||
text.WriteString(fmt.Sprintf("%s%s%s%s%s%s\n%s%s%s%s\n",
|
||||
colors.Dim, span.firstLineBefore, colors.Green, span.firstLineMarked, colors.Dim, span.firstLineAfter,
|
||||
span.indent, colors.Green, span.marker, colors.Reset,
|
||||
))
|
||||
|
||||
if loc.Suggestion != "" {
|
||||
text.WriteString(fmt.Sprintf("%s%s%s%s\n", colors.Green, span.indent, loc.Suggestion, colors.Reset))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package logger
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func msgToStringDefault(msg Msg, options OutputOptions, terminalInfo TerminalInfo) string {
|
||||
@@ -60,24 +59,32 @@ func msgString(includeSource bool, pathStyle PathStyle, terminalInfo TerminalInf
|
||||
|
||||
location := ""
|
||||
|
||||
if data.Location != nil {
|
||||
maxMargin := len(fmt.Sprintf("%d", data.Location.Line))
|
||||
d := detailStruct(data, pathStyle, terminalInfo, maxMargin)
|
||||
if loc := data.Location; loc != nil {
|
||||
maxMargin := len(fmt.Sprintf("%d", loc.Line))
|
||||
margin := marginWithLineText(maxMargin, loc.Line)
|
||||
widthLimit := terminalInfo.Width
|
||||
if widthLimit < 1 {
|
||||
widthLimit = defaultTerminalWidth
|
||||
}
|
||||
widthLimit -= maxMargin + extraMarginChars
|
||||
|
||||
if d.Suggestion != "" {
|
||||
location = fmt.Sprintf("\n %s:%d:%d:\n%s%s%s%s%s%s\n%s%s%s%s%s\n%s%s%s%s%s\n%s",
|
||||
d.Path, d.Line, d.Column,
|
||||
colors.Dim, d.SourceBefore, colors.Green, d.SourceMarked, colors.Dim, d.SourceAfter,
|
||||
emptyMarginText(maxMargin, false), d.Indent, colors.Green, d.Marker, colors.Dim,
|
||||
emptyMarginText(maxMargin, true), d.Indent, colors.Green, d.Suggestion, colors.Reset,
|
||||
d.ContentAfter,
|
||||
span := formatLineSpan(loc.LineText, loc.Line, loc.Column, loc.Length, widthLimit)
|
||||
path := loc.File.Select(pathStyle)
|
||||
|
||||
if loc.Suggestion != "" {
|
||||
location = fmt.Sprintf("\n %s:%d:%d:\n%s%s%s%s%s%s%s\n%s%s%s%s%s\n%s%s%s%s%s\n%s",
|
||||
path, loc.Line, loc.Column,
|
||||
colors.Dim, margin, span.firstLineBefore, colors.Green, span.firstLineMarked, colors.Dim, span.firstLineAfter,
|
||||
emptyMarginText(maxMargin, false), span.indent, colors.Green, span.marker, colors.Dim,
|
||||
emptyMarginText(maxMargin, true), span.indent, colors.Green, loc.Suggestion, colors.Reset,
|
||||
span.afterFirstLine,
|
||||
)
|
||||
} else {
|
||||
location = fmt.Sprintf("\n %s:%d:%d:\n%s%s%s%s%s%s\n%s%s%s%s%s\n%s",
|
||||
d.Path, d.Line, d.Column,
|
||||
colors.Dim, d.SourceBefore, colors.Green, d.SourceMarked, colors.Dim, d.SourceAfter,
|
||||
emptyMarginText(maxMargin, true), d.Indent, colors.Green, d.Marker, colors.Reset,
|
||||
d.ContentAfter,
|
||||
location = fmt.Sprintf("\n %s:%d:%d:\n%s%s%s%s%s%s%s\n%s%s%s%s%s\n%s",
|
||||
path, loc.Line, loc.Column,
|
||||
colors.Dim, margin, span.firstLineBefore, colors.Green, span.firstLineMarked, colors.Dim, span.firstLineAfter,
|
||||
emptyMarginText(maxMargin, true), span.indent, colors.Green, span.marker, colors.Reset,
|
||||
span.afterFirstLine,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -151,310 +158,3 @@ func msgString(includeSource bool, pathStyle PathStyle, terminalInfo TerminalInf
|
||||
location,
|
||||
)
|
||||
}
|
||||
|
||||
func linkifyText(text string, underline string, reset string) string {
|
||||
if underline == "" {
|
||||
return text
|
||||
}
|
||||
|
||||
https := strings.Index(text, "https://")
|
||||
if https == -1 {
|
||||
return text
|
||||
}
|
||||
|
||||
sb := strings.Builder{}
|
||||
for {
|
||||
https := strings.Index(text, "https://")
|
||||
if https == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
end := strings.IndexByte(text[https:], ' ')
|
||||
if end == -1 {
|
||||
end = len(text)
|
||||
} else {
|
||||
end += https
|
||||
}
|
||||
|
||||
// Remove trailing punctuation
|
||||
if end > https {
|
||||
switch text[end-1] {
|
||||
case '.', ',', '?', '!', ')', ']', '}':
|
||||
end--
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString(text[:https])
|
||||
sb.WriteString(underline)
|
||||
sb.WriteString(text[https:end])
|
||||
sb.WriteString(reset)
|
||||
text = text[end:]
|
||||
}
|
||||
|
||||
sb.WriteString(text)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func wrapWordsInString(text string, width int) []string {
|
||||
runs := []string{}
|
||||
|
||||
outer:
|
||||
for text != "" {
|
||||
i := 0
|
||||
x := 0
|
||||
wordEndI := 0
|
||||
|
||||
// Skip over any leading spaces
|
||||
for i < len(text) && text[i] == ' ' {
|
||||
i++
|
||||
x++
|
||||
}
|
||||
|
||||
// Find out how many words will fit in this run
|
||||
for i < len(text) {
|
||||
oldWordEndI := wordEndI
|
||||
wordStartI := i
|
||||
|
||||
// Find the end of the word
|
||||
for i < len(text) {
|
||||
c, width := utf8.DecodeRuneInString(text[i:])
|
||||
if c == ' ' {
|
||||
break
|
||||
}
|
||||
i += width
|
||||
x += 1 // Naively assume that each unicode code point is a single column
|
||||
}
|
||||
wordEndI = i
|
||||
|
||||
// Split into a new run if this isn't the first word in the run and the end is past the width
|
||||
if wordStartI > 0 && x > width {
|
||||
runs = append(runs, text[:oldWordEndI])
|
||||
text = text[wordStartI:]
|
||||
continue outer
|
||||
}
|
||||
|
||||
// Skip over any spaces after the word
|
||||
for i < len(text) && text[i] == ' ' {
|
||||
i++
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, this is the last run (i.e. everything fits)
|
||||
break
|
||||
}
|
||||
|
||||
// Remove any trailing spaces on the last run
|
||||
for len(text) > 0 && text[len(text)-1] == ' ' {
|
||||
text = text[:len(text)-1]
|
||||
}
|
||||
runs = append(runs, text)
|
||||
return runs
|
||||
}
|
||||
|
||||
type msgDetail struct {
|
||||
SourceBefore string
|
||||
SourceMarked string
|
||||
SourceAfter string
|
||||
|
||||
Indent string
|
||||
Marker string
|
||||
Suggestion string
|
||||
|
||||
ContentAfter string
|
||||
|
||||
Path string
|
||||
Line int
|
||||
Column int
|
||||
}
|
||||
|
||||
func detailStruct(data MsgData, pathStyle PathStyle, terminalInfo TerminalInfo, maxMargin int) msgDetail {
|
||||
// Only highlight the first line of the line text
|
||||
loc := *data.Location
|
||||
endOfFirstLine := len(loc.LineText)
|
||||
|
||||
// Note: This uses "IndexByte" because Go implements this with SIMD, which
|
||||
// can matter a lot for really long lines. Some people pass huge >100mb
|
||||
// minified files as line text for the log message.
|
||||
if i := strings.IndexByte(loc.LineText, '\n'); i >= 0 {
|
||||
endOfFirstLine = i
|
||||
}
|
||||
|
||||
firstLine := loc.LineText[:endOfFirstLine]
|
||||
afterFirstLine := loc.LineText[endOfFirstLine:]
|
||||
if afterFirstLine != "" && !strings.HasSuffix(afterFirstLine, "\n") {
|
||||
afterFirstLine += "\n"
|
||||
}
|
||||
|
||||
// Clamp values in range
|
||||
if loc.Line < 0 {
|
||||
loc.Line = 0
|
||||
}
|
||||
if loc.Column < 0 {
|
||||
loc.Column = 0
|
||||
}
|
||||
if loc.Length < 0 {
|
||||
loc.Length = 0
|
||||
}
|
||||
if loc.Column > endOfFirstLine {
|
||||
loc.Column = endOfFirstLine
|
||||
}
|
||||
if loc.Length > endOfFirstLine-loc.Column {
|
||||
loc.Length = endOfFirstLine - loc.Column
|
||||
}
|
||||
|
||||
spacesPerTab := 2
|
||||
lineText := renderTabStops(firstLine, spacesPerTab)
|
||||
textUpToLoc := renderTabStops(firstLine[:loc.Column], spacesPerTab)
|
||||
markerStart := len(textUpToLoc)
|
||||
markerEnd := markerStart
|
||||
indent := strings.Repeat(" ", estimateWidthInTerminal(textUpToLoc))
|
||||
marker := "^"
|
||||
|
||||
// Extend markers to cover the full range of the error
|
||||
if loc.Length > 0 {
|
||||
markerEnd = len(renderTabStops(firstLine[:loc.Column+loc.Length], spacesPerTab))
|
||||
}
|
||||
|
||||
// Clip the marker to the bounds of the line
|
||||
if markerStart > len(lineText) {
|
||||
markerStart = len(lineText)
|
||||
}
|
||||
if markerEnd > len(lineText) {
|
||||
markerEnd = len(lineText)
|
||||
}
|
||||
if markerEnd < markerStart {
|
||||
markerEnd = markerStart
|
||||
}
|
||||
|
||||
// Trim the line to fit the terminal width
|
||||
width := terminalInfo.Width
|
||||
if width < 1 {
|
||||
width = defaultTerminalWidth
|
||||
}
|
||||
width -= maxMargin + extraMarginChars
|
||||
if width < 1 {
|
||||
width = 1
|
||||
}
|
||||
if loc.Column == endOfFirstLine {
|
||||
// If the marker is at the very end of the line, the marker will be a "^"
|
||||
// character that extends one column past the end of the line. In this case
|
||||
// we should reserve a column at the end so the marker doesn't wrap.
|
||||
width -= 1
|
||||
}
|
||||
if len(lineText) > width {
|
||||
// Try to center the error
|
||||
sliceStart := (markerStart + markerEnd - width) / 2
|
||||
if sliceStart > markerStart-width/5 {
|
||||
sliceStart = markerStart - width/5
|
||||
}
|
||||
if sliceStart < 0 {
|
||||
sliceStart = 0
|
||||
}
|
||||
if sliceStart > len(lineText)-width {
|
||||
sliceStart = len(lineText) - width
|
||||
}
|
||||
sliceEnd := sliceStart + width
|
||||
|
||||
// Slice the line
|
||||
slicedLine := lineText[sliceStart:sliceEnd]
|
||||
markerStart -= sliceStart
|
||||
markerEnd -= sliceStart
|
||||
if markerStart < 0 {
|
||||
markerStart = 0
|
||||
}
|
||||
if markerEnd > len(slicedLine) {
|
||||
markerEnd = len(slicedLine)
|
||||
}
|
||||
|
||||
// Truncate the ends with "..."
|
||||
if len(slicedLine) > 3 && sliceStart > 0 {
|
||||
slicedLine = "..." + slicedLine[3:]
|
||||
if markerStart < 3 {
|
||||
markerStart = 3
|
||||
}
|
||||
}
|
||||
if len(slicedLine) > 3 && sliceEnd < len(lineText) {
|
||||
slicedLine = slicedLine[:len(slicedLine)-3] + "..."
|
||||
if markerEnd > len(slicedLine)-3 {
|
||||
markerEnd = len(slicedLine) - 3
|
||||
}
|
||||
if markerEnd < markerStart {
|
||||
markerEnd = markerStart
|
||||
}
|
||||
}
|
||||
|
||||
// Now we can compute the indent
|
||||
lineText = slicedLine
|
||||
indent = strings.Repeat(" ", estimateWidthInTerminal(lineText[:markerStart]))
|
||||
}
|
||||
|
||||
// If marker is still multi-character after clipping, make the marker wider
|
||||
if markerEnd-markerStart > 1 {
|
||||
marker = strings.Repeat("~", estimateWidthInTerminal(lineText[markerStart:markerEnd]))
|
||||
}
|
||||
|
||||
// Put a margin before the marker indent
|
||||
margin := marginWithLineText(maxMargin, loc.Line)
|
||||
|
||||
return msgDetail{
|
||||
Path: loc.File.Select(pathStyle),
|
||||
|
||||
// Note: We want to deliberately print the unclamped line and column, as it
|
||||
// may come from another tool that either didn't set "LineText" at all or
|
||||
// at least didn't calculate the column number correctly.
|
||||
Line: data.Location.Line,
|
||||
Column: data.Location.Column,
|
||||
|
||||
SourceBefore: margin + lineText[:markerStart],
|
||||
SourceMarked: lineText[markerStart:markerEnd],
|
||||
SourceAfter: lineText[markerEnd:],
|
||||
|
||||
Indent: indent,
|
||||
Marker: marker,
|
||||
Suggestion: loc.Suggestion,
|
||||
|
||||
ContentAfter: afterFirstLine,
|
||||
}
|
||||
}
|
||||
|
||||
// Estimate the number of columns this string will take when printed
|
||||
func estimateWidthInTerminal(text string) int {
|
||||
// For now just assume each code point is one column. This is wrong but is
|
||||
// less wrong than assuming each code unit is one column.
|
||||
width := 0
|
||||
for text != "" {
|
||||
c, size := utf8.DecodeRuneInString(text)
|
||||
text = text[size:]
|
||||
|
||||
// Ignore the Zero Width No-Break Space character (UTF-8 BOM)
|
||||
if c != 0xFEFF {
|
||||
width++
|
||||
}
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
func renderTabStops(withTabs string, spacesPerTab int) string {
|
||||
if !strings.ContainsRune(withTabs, '\t') {
|
||||
return withTabs
|
||||
}
|
||||
|
||||
withoutTabs := strings.Builder{}
|
||||
count := 0
|
||||
|
||||
for _, c := range withTabs {
|
||||
if c == '\t' {
|
||||
spaces := spacesPerTab - count%spacesPerTab
|
||||
for i := 0; i < spaces; i++ {
|
||||
withoutTabs.WriteRune(' ')
|
||||
count++
|
||||
}
|
||||
} else {
|
||||
withoutTabs.WriteRune(c)
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return withoutTabs.String()
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ export type Platform = 'browser' | 'node' | 'neutral'
|
||||
export type Format = 'iife' | 'cjs' | 'esm'
|
||||
export type Loader = 'base64' | 'binary' | 'copy' | 'css' | 'dataurl' | 'default' | 'empty' | 'file' | 'js' | 'json' | 'jsx' | 'local-css' | 'text' | 'ts' | 'tsx'
|
||||
export type LogLevel = 'verbose' | 'debug' | 'info' | 'warning' | 'error' | 'silent'
|
||||
export type LogStyle = 'default' | 'visualstudio'
|
||||
export type LogStyle = 'default' | 'clang' | 'visualstudio'
|
||||
export type Charset = 'ascii' | 'utf8'
|
||||
export type Drop = 'console' | 'debugger'
|
||||
export type AbsPaths = 'code' | 'log' | 'metafile'
|
||||
|
||||
@@ -241,6 +241,7 @@ type LogStyle uint8
|
||||
|
||||
const (
|
||||
LogStyleDefault LogStyle = iota
|
||||
LogStyleClang
|
||||
LogStyleVisualStudio
|
||||
)
|
||||
|
||||
|
||||
@@ -209,6 +209,8 @@ func validateLogStyle(value LogStyle) logger.LogStyle {
|
||||
switch value {
|
||||
case LogStyleDefault:
|
||||
return logger.StyleDefault
|
||||
case LogStyleClang:
|
||||
return logger.StyleClang
|
||||
case LogStyleVisualStudio:
|
||||
return logger.StyleVisualStudio
|
||||
default:
|
||||
|
||||
+3
-1
@@ -1581,12 +1581,14 @@ func parseLogStyle(value string, arg string) (api.LogStyle, *cli_helpers.ErrorWi
|
||||
switch value {
|
||||
case "default":
|
||||
return api.LogStyleDefault, nil
|
||||
case "clang":
|
||||
return api.LogStyleClang, nil
|
||||
case "visualstudio":
|
||||
return api.LogStyleVisualStudio, nil
|
||||
default:
|
||||
return api.LogStyleDefault, cli_helpers.MakeErrorWithNote(
|
||||
fmt.Sprintf("Invalid value %q in %q", value, arg),
|
||||
"Valid values are \"default\" or \"visualstudio\".",
|
||||
"Valid values are \"default\", \"clang\", or \"visualstudio\".",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+23
-2
@@ -7618,13 +7618,15 @@ let formatTests = {
|
||||
{ text: 'This is an error' },
|
||||
{ text: 'Another error', location: { file: 'file.js' } },
|
||||
{ text: 'Error with line and column', location: { file: 'file.js', line: 3, column: 6 } },
|
||||
{ text: 'Error with source code', location: { file: 'file.js', line: 3, column: 6, length: 5, lineText: 'foo = 1 + 2;' } },
|
||||
], {
|
||||
kind: 'error',
|
||||
})
|
||||
assert.strictEqual(messages.length, 3)
|
||||
assert.strictEqual(messages.length, 4)
|
||||
assert.strictEqual(messages[0], `${errorIcon} [ERROR] This is an error\n\n`)
|
||||
assert.strictEqual(messages[1], `${errorIcon} [ERROR] Another error\n\n file.js:0:0:\n 0 │ \n ╵ ^\n\n`)
|
||||
assert.strictEqual(messages[2], `${errorIcon} [ERROR] Error with line and column\n\n file.js:3:6:\n 3 │ \n ╵ ^\n\n`)
|
||||
assert.strictEqual(messages[3], `${errorIcon} [ERROR] Error with source code\n\n file.js:3:6:\n 3 │ foo = 1 + 2;\n ╵ ~~~~~\n\n`)
|
||||
},
|
||||
|
||||
async formatMessagesVisualStudio({ esbuild }) {
|
||||
@@ -7632,14 +7634,33 @@ let formatTests = {
|
||||
{ text: 'This is an error' },
|
||||
{ text: 'Another error', location: { file: 'file.js' } },
|
||||
{ text: 'Error with line and column', location: { file: 'file.js', line: 3, column: 6 } },
|
||||
{ text: 'Error with source code', location: { file: 'file.js', line: 3, column: 6, length: 5, lineText: 'foo = 1 + 2;' } },
|
||||
], {
|
||||
kind: 'error',
|
||||
logStyle: 'visualstudio',
|
||||
})
|
||||
assert.strictEqual(messages.length, 3)
|
||||
assert.strictEqual(messages.length, 4)
|
||||
assert.strictEqual(messages[0], `esbuild: error ES0000: This is an error\n`)
|
||||
assert.strictEqual(messages[1], `file.js: error ES0000: Another error\n`)
|
||||
assert.strictEqual(messages[2], `file.js(3,7): error ES0000: Error with line and column\n`)
|
||||
assert.strictEqual(messages[3], `file.js(3,7): error ES0000: Error with source code\n`)
|
||||
},
|
||||
|
||||
async formatMessagesClang({ esbuild }) {
|
||||
const messages = await esbuild.formatMessages([
|
||||
{ text: 'This is an error' },
|
||||
{ text: 'Another error', location: { file: 'file.js' } },
|
||||
{ text: 'Error with line and column', location: { file: 'file.js', line: 3, column: 6 } },
|
||||
{ text: 'Error with source code', location: { file: 'file.js', line: 3, column: 6, length: 5, lineText: 'foo = 1 + 2;' } },
|
||||
], {
|
||||
kind: 'error',
|
||||
logStyle: 'clang',
|
||||
})
|
||||
assert.strictEqual(messages.length, 4)
|
||||
assert.strictEqual(messages[0], `error: This is an error\n`)
|
||||
assert.strictEqual(messages[1], `file.js: error: Another error\n`)
|
||||
assert.strictEqual(messages[2], `file.js:3:7: error: Error with line and column\n\n^\n`)
|
||||
assert.strictEqual(messages[3], `file.js:3:7: error: Error with source code\nfoo = 1 + 2;\n ~~~~~\n`)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user