add a clang format style

This commit is contained in:
Evan Wallace
2026-08-09 12:36:05 -04:00
parent 214a9951fe
commit b5d4d79d6b
10 changed files with 430 additions and 334 deletions
+2
View File
@@ -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:
+5 -7
View File
@@ -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)
+294
View File
@@ -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()
}
+76
View File
@@ -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))
}
}
}
+23 -323
View File
@@ -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
View File
@@ -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'
+1
View File
@@ -241,6 +241,7 @@ type LogStyle uint8
const (
LogStyleDefault LogStyle = iota
LogStyleClang
LogStyleVisualStudio
)
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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`)
},
}