package api_test import ( "testing" "github.com/evanw/esbuild/internal/test" "github.com/evanw/esbuild/pkg/api" ) func TestFormatMessages(t *testing.T) { check := func(name string, opts api.FormatMessagesOptions, msg api.Message, expected string) { t.Helper() t.Run(name, func(t *testing.T) { test.AssertEqualWithDiff(t, api.FormatMessages([]api.Message{msg}, opts)[0], expected) }) } check("Error", api.FormatMessagesOptions{Kind: api.ErrorMessage}, api.Message{Text: "This is a test"}, "✘ [ERROR] This is a test\n\n") check("Warning", api.FormatMessagesOptions{Kind: api.WarningMessage}, api.Message{Text: "This is a test"}, "▲ [WARNING] This is a test\n\n") check("Basic location", api.FormatMessagesOptions{}, api.Message{Text: "This is a test", Location: &api.Location{ File: "some file.js", Line: 100, Column: 5, // 0-based Length: 3, LineText: "this.foo();", Suggestion: "bar", }}, `✘ [ERROR] This is a test some file.js:100:5: 100 │ this.foo(); │ ~~~ ╵ bar `, ) check("Unicode location", api.FormatMessagesOptions{ Kind: api.WarningMessage, }, api.Message{Text: "This is a test", Location: &api.Location{ File: "some file.js", Line: 100, Column: 17, // In UTF-8 bytes Length: 10, // In UTF-8 bytes LineText: "𝓉𝒽𝒾𝓈.𝒻ℴℴ();", Suggestion: "𝒷𝒶𝓇", }}, `▲ [WARNING] This is a test some file.js:100:17: 100 │ 𝓉𝒽𝒾𝓈.𝒻ℴℴ(); │ ~~~ ╵ 𝒷𝒶𝓇 `, ) check("Tab stop rendering", api.FormatMessagesOptions{ Kind: api.WarningMessage, }, api.Message{Text: "This is a test", Location: &api.Location{ File: "some file.js", Line: 100, Column: 6, Length: 4, LineText: "0\t1\t23\t45\t678", }}, `▲ [WARNING] This is a test some file.js:100:6: 100 │ 0 1 23 45 678 ╵ ~~~~~~ `, ) check("Truncated location tail, zero length", api.FormatMessagesOptions{ TerminalWidth: 32, }, api.Message{Text: "This is a test", Location: &api.Location{ File: "some file.js", Line: 100, Column: 3, Length: 0, LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI", }}, `✘ [ERROR] This is a test some file.js:100:3: 100 │ 012345678 abcdefg... ╵ ^ `, ) check("Truncated location tail, nonzero length", api.FormatMessagesOptions{ TerminalWidth: 32, }, api.Message{Text: "This is a test", Location: &api.Location{ File: "some file.js", Line: 100, Column: 3, Length: 6, LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI", }}, `✘ [ERROR] This is a test some file.js:100:3: 100 │ 012345678 abcdefg... ╵ ~~~~~~ `, ) check("Truncated location tail, truncated length", api.FormatMessagesOptions{ TerminalWidth: 32, }, api.Message{Text: "This is a test", Location: &api.Location{ File: "some file.js", Line: 100, Column: 3, Length: 100, LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI", }}, `✘ [ERROR] This is a test some file.js:100:3: 100 │ 012345678 abcdefg... ╵ ~~~~~~~~~~~~~~ `, ) check("Truncated location head, zero length", api.FormatMessagesOptions{ TerminalWidth: 32, }, api.Message{Text: "This is a test", Location: &api.Location{ File: "some file.js", Line: 100, Column: 200, Length: 0, LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI", }}, `✘ [ERROR] This is a test some file.js:100:59: 100 │ ...defghi ABCDEFGHI ╵ ^ `, ) check("Truncated location head, nonzero length", api.FormatMessagesOptions{ TerminalWidth: 32, }, api.Message{Text: "This is a test", Location: &api.Location{ File: "some file.js", Line: 100, Column: 50, Length: 200, LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI", }}, `✘ [ERROR] This is a test some file.js:100:50: 100 │ ...cdefghi ABCDEFGHI ╵ ~~~~~~~~~ `, ) check("Truncated location head and tail, truncated length", api.FormatMessagesOptions{ TerminalWidth: 32, }, api.Message{Text: "This is a test", Location: &api.Location{ File: "some file.js", Line: 100, Column: 30, Length: 30, LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI", }}, `✘ [ERROR] This is a test some file.js:100:30: 100 │ ... 012345678 abc... ╵ ~~~~~~~~~~~~~ `, ) check("Truncated location head and tail, non-truncated length", api.FormatMessagesOptions{ TerminalWidth: 32, }, api.Message{Text: "This is a test", Location: &api.Location{ File: "some file.js", Line: 100, Column: 30, Length: 9, LineText: "012345678 abcdefghi ABCDEFGHI 012345678 abcdefghi ABCDEFGHI", }}, `✘ [ERROR] This is a test some file.js:100:30: 100 │ ...HI 012345678 a... ╵ ~~~~~~~~~ `, ) check("Multi-line line text", api.FormatMessagesOptions{}, api.Message{Text: "ReferenceError: Cannot access 'foo' before initialization", Location: &api.Location{ File: "some file.js", Line: 100, Column: 2, LineText: ` foo(); at ModuleJob.run (node:internal/modules/esm/module_job:185:25) at async Promise.all (index 0) at async ESMLoader.import (node:internal/modules/esm/loader:281:24) at async loadESM (node:internal/process/esm_loader:88:5) at async handleMainPromise (node:internal/modules/run_main:65:12)`, }}, `✘ [ERROR] ReferenceError: Cannot access 'foo' before initialization some file.js:100:2: 100 │ foo(); ╵ ^ at ModuleJob.run (node:internal/modules/esm/module_job:185:25) at async Promise.all (index 0) at async ESMLoader.import (node:internal/modules/esm/loader:281:24) at async loadESM (node:internal/process/esm_loader:88:5) at async handleMainPromise (node:internal/modules/run_main:65:12) `, ) check("Note formatting", api.FormatMessagesOptions{ TerminalWidth: 40, }, api.Message{ Text: "Why would you do this?", Location: &api.Location{ File: "some file.js", Line: 1, Column: 10, Length: 16, LineText: "let ten = +([+!+[]]+[+[]]);", }, Notes: []api.Note{{ Text: "This is 1:", Location: &api.Location{ File: "some file.js", Line: 1, Column: 12, Length: 7, LineText: "let ten = +([+!+[]]+[+[]]);", Suggestion: "'1'", }, }, { Text: "This is 0:", Location: &api.Location{ File: "some file.js", Line: 1, Column: 20, Length: 5, LineText: "let ten = +([+!+[]]+[+[]]);", Suggestion: "'0'", }, }, { Text: "The number 0 is created by +[], where [] is the empty array and + is the unary plus, " + "used to convert the right side to a numeric value. The number 1 is formed as +!+[], where " + "the boolean value true is converted into the numeric value 1 by the prepended plus sign.", }}, }, `✘ [ERROR] Why would you do this? some file.js:1:10: 1 │ let ten = +([+!+[]]+[+[]]); ╵ ~~~~~~~~~~~~~~~~ This is 1: some file.js:1:12: 1 │ let ten = +([+!+[]]+[+[]]); │ ~~~~~~~ ╵ '1' This is 0: some file.js:1:20: 1 │ let ten = +([+!+[]]+[+[]]); │ ~~~~~ ╵ '0' The number 0 is created by +[], where [] is the empty array and + is the unary plus, used to convert the right side to a numeric value. The number 1 is formed as +!+[], where the boolean value true is converted into the numeric value 1 by the prepended plus sign. `, ) }