package js_parser import ( "fmt" "strings" "testing" "github.com/evanw/esbuild/internal/ast" "github.com/evanw/esbuild/internal/js_ast" "github.com/evanw/esbuild/internal/js_printer" "github.com/evanw/esbuild/internal/logger" "github.com/evanw/esbuild/internal/test" ) func expectParseErrorJSON(t *testing.T, contents string, expected string) { t.Helper() t.Run(contents, func(t *testing.T) { t.Helper() log := logger.NewDeferLog(logger.DeferLogNoVerboseOrDebug, nil) ParseJSON(log, test.SourceForTest(contents), JSONOptions{}) msgs := log.Done() var text strings.Builder for _, msg := range msgs { text.WriteString(msg.String(logger.OutputOptions{}, logger.TerminalInfo{})) } test.AssertEqualWithDiff(t, text.String(), expected) }) } // Note: The input is parsed as JSON but printed as JS. This means the printed // code may not be valid JSON. That's ok because esbuild always outputs JS // bundles, not JSON bundles. func expectPrintedJSON(t *testing.T, contents string, expected string) { t.Helper() expectPrintedJSONWithWarning(t, contents, "", expected) } func expectPrintedJSONWithWarning(t *testing.T, contents string, warning string, expected string) { t.Helper() t.Run(contents, func(t *testing.T) { t.Helper() log := logger.NewDeferLog(logger.DeferLogNoVerboseOrDebug, nil) expr, ok := ParseJSON(log, test.SourceForTest(contents), JSONOptions{}) msgs := log.Done() var text strings.Builder for _, msg := range msgs { text.WriteString(msg.String(logger.OutputOptions{}, logger.TerminalInfo{})) } test.AssertEqualWithDiff(t, text.String(), warning) if !ok { t.Fatal("Parse error") } // Insert this expression into a statement tree := js_ast.AST{ Parts: []js_ast.Part{{Stmts: []js_ast.Stmt{{Data: &js_ast.SExpr{Value: expr}}}}}, } js := js_printer.Print(tree, ast.SymbolMap{}, nil, js_printer.Options{ MinifyWhitespace: true, }).JS // Remove the trailing semicolon if n := len(js); n > 1 && js[n-1] == ';' { js = js[:n-1] } test.AssertEqualWithDiff(t, string(js), expected) }) } func TestJSONAtom(t *testing.T) { expectPrintedJSON(t, "false", "false") expectPrintedJSON(t, "true", "true") expectPrintedJSON(t, "null", "null") expectParseErrorJSON(t, "undefined", ": ERROR: Unexpected \"undefined\" in JSON\n") } func TestJSONString(t *testing.T) { expectPrintedJSON(t, "\"x\"", "\"x\"") expectParseErrorJSON(t, "'x'", ": ERROR: JSON strings must use double quotes\n") expectParseErrorJSON(t, "`x`", ": ERROR: Unexpected \"`x`\" in JSON\n") // Newlines expectPrintedJSON(t, "\"\u2028\"", "\"\\u2028\"") expectPrintedJSON(t, "\"\u2029\"", "\"\\u2029\"") expectParseErrorJSON(t, "\"\r\"", ": ERROR: Unterminated string literal\n") expectParseErrorJSON(t, "\"\n\"", ": ERROR: Unterminated string literal\n") // Control characters for c := 0; c < 0x20; c++ { if c != '\r' && c != '\n' { expectParseErrorJSON(t, fmt.Sprintf("\"%c\"", c), fmt.Sprintf(": ERROR: Syntax error \"\\x%02X\"\n", c)) } } // Valid escapes expectPrintedJSON(t, "\"\\\"\"", "'\"'") expectPrintedJSON(t, "\"\\\\\"", "\"\\\\\"") expectPrintedJSON(t, "\"\\/\"", "\"/\"") expectPrintedJSON(t, "\"\\b\"", "\"\\b\"") expectPrintedJSON(t, "\"\\f\"", "\"\\f\"") expectPrintedJSON(t, "\"\\n\"", "\"\\n\"") expectPrintedJSON(t, "\"\\r\"", "\"\\r\"") expectPrintedJSON(t, "\"\\t\"", "\"\t\"") expectPrintedJSON(t, "\"\\u0000\"", "\"\\0\"") expectPrintedJSON(t, "\"\\u0078\"", "\"x\"") expectPrintedJSON(t, "\"\\u1234\"", "\"\u1234\"") expectPrintedJSON(t, "\"\\uD800\"", "\"\\uD800\"") expectPrintedJSON(t, "\"\\uDC00\"", "\"\\uDC00\"") // Invalid escapes expectParseErrorJSON(t, "\"\\", ": ERROR: Unterminated string literal\n") expectParseErrorJSON(t, "\"\\0\"", ": ERROR: Syntax error \"0\"\n") expectParseErrorJSON(t, "\"\\1\"", ": ERROR: Syntax error \"1\"\n") expectParseErrorJSON(t, "\"\\'\"", ": ERROR: Syntax error \"'\"\n") expectParseErrorJSON(t, "\"\\a\"", ": ERROR: Syntax error \"a\"\n") expectParseErrorJSON(t, "\"\\v\"", ": ERROR: Syntax error \"v\"\n") expectParseErrorJSON(t, "\"\\\n\"", ": ERROR: Syntax error \"\\x0A\"\n") expectParseErrorJSON(t, "\"\\x78\"", ": ERROR: Syntax error \"x\"\n") expectParseErrorJSON(t, "\"\\u{1234}\"", ": ERROR: Syntax error \"{\"\n") expectParseErrorJSON(t, "\"\\uG\"", ": ERROR: Syntax error \"G\"\n") expectParseErrorJSON(t, "\"\\uDG\"", ": ERROR: Syntax error \"G\"\n") expectParseErrorJSON(t, "\"\\uDEG\"", ": ERROR: Syntax error \"G\"\n") expectParseErrorJSON(t, "\"\\uDEFG\"", ": ERROR: Syntax error \"G\"\n") expectParseErrorJSON(t, "\"\\u\"", ": ERROR: Syntax error '\"'\n") expectParseErrorJSON(t, "\"\\uD\"", ": ERROR: Syntax error '\"'\n") expectParseErrorJSON(t, "\"\\uDE\"", ": ERROR: Syntax error '\"'\n") expectParseErrorJSON(t, "\"\\uDEF\"", ": ERROR: Syntax error '\"'\n") } func TestJSONNumber(t *testing.T) { expectPrintedJSON(t, "0", "0") expectPrintedJSON(t, "-0", "-0") expectPrintedJSON(t, "123", "123") expectPrintedJSON(t, "123.456", "123.456") expectPrintedJSON(t, "123e20", "123e20") expectPrintedJSON(t, "123e-20", "123e-20") expectParseErrorJSON(t, "123.", ": ERROR: Unexpected \"123.\" in JSON\n") expectParseErrorJSON(t, "-123.", ": ERROR: Unexpected \"123.\" in JSON\n") expectParseErrorJSON(t, ".123", ": ERROR: Unexpected \".123\" in JSON\n") expectParseErrorJSON(t, "-.123", ": ERROR: Unexpected \".123\" in JSON\n") expectParseErrorJSON(t, "NaN", ": ERROR: Unexpected \"NaN\" in JSON\n") expectParseErrorJSON(t, "Infinity", ": ERROR: Unexpected \"Infinity\" in JSON\n") expectParseErrorJSON(t, "-Infinity", ": ERROR: Unexpected \"-\" in JSON\n") expectParseErrorJSON(t, "+1", ": ERROR: Unexpected \"+\" in JSON\n") expectParseErrorJSON(t, "- 1", ": ERROR: Unexpected \"-\" in JSON\n") expectParseErrorJSON(t, "01", ": ERROR: Unexpected \"01\" in JSON\n") expectParseErrorJSON(t, "0b1", ": ERROR: Unexpected \"0b1\" in JSON\n") expectParseErrorJSON(t, "0o1", ": ERROR: Unexpected \"0o1\" in JSON\n") expectParseErrorJSON(t, "0x1", ": ERROR: Unexpected \"0x1\" in JSON\n") expectParseErrorJSON(t, "0n", ": ERROR: Unexpected \"0n\" in JSON\n") expectParseErrorJSON(t, "-01", ": ERROR: Unexpected \"01\" in JSON\n") expectParseErrorJSON(t, "-0b1", ": ERROR: Unexpected \"0b1\" in JSON\n") expectParseErrorJSON(t, "-0o1", ": ERROR: Unexpected \"0o1\" in JSON\n") expectParseErrorJSON(t, "-0x1", ": ERROR: Unexpected \"0x1\" in JSON\n") expectParseErrorJSON(t, "-0n", ": ERROR: Expected number in JSON but found \"0n\"\n") expectParseErrorJSON(t, "1_2", ": ERROR: Unexpected \"1_2\" in JSON\n") expectParseErrorJSON(t, "1.e2", ": ERROR: Unexpected \"1.e2\" in JSON\n") } func TestJSONObject(t *testing.T) { expectPrintedJSON(t, "{\"x\":0}", "({x:0})") expectPrintedJSON(t, "{\"x\":0,\"y\":1}", "({x:0,y:1})") expectPrintedJSONWithWarning(t, "{\"x\":0,\"x\":1}", ": WARNING: Duplicate key \"x\" in object literal\n: NOTE: The original key \"x\" is here:\n", "({x:0,x:1})") expectParseErrorJSON(t, "{\"x\":0,}", ": ERROR: JSON does not support trailing commas\n") expectParseErrorJSON(t, "{x:0}", ": ERROR: Expected string in JSON but found \"x\"\n") expectParseErrorJSON(t, "{1:0}", ": ERROR: Expected string in JSON but found \"1\"\n") expectParseErrorJSON(t, "{[\"x\"]:0}", ": ERROR: Expected string in JSON but found \"[\"\n") } func TestJSONArray(t *testing.T) { expectPrintedJSON(t, "[]", "[]") expectPrintedJSON(t, "[1]", "[1]") expectPrintedJSON(t, "[1,2]", "[1,2]") expectParseErrorJSON(t, "[,]", ": ERROR: Unexpected \",\" in JSON\n") expectParseErrorJSON(t, "[,1]", ": ERROR: Unexpected \",\" in JSON\n") expectParseErrorJSON(t, "[1,]", ": ERROR: JSON does not support trailing commas\n") expectParseErrorJSON(t, "[1,,2]", ": ERROR: Unexpected \",\" in JSON\n") } func TestJSONInvalid(t *testing.T) { expectParseErrorJSON(t, "({\"x\":0})", ": ERROR: Unexpected \"(\" in JSON\n") expectParseErrorJSON(t, "{\"x\":(0)}", ": ERROR: Unexpected \"(\" in JSON\n") expectParseErrorJSON(t, "#!/usr/bin/env node\n{}", ": ERROR: Unexpected \"#!/usr/bin/env node\" in JSON\n") expectParseErrorJSON(t, "{\"x\":0}{\"y\":1}", ": ERROR: Expected end of file in JSON but found \"{\"\n") } func TestJSONComments(t *testing.T) { expectParseErrorJSON(t, "/*comment*/{}", ": ERROR: JSON does not support comments\n") expectParseErrorJSON(t, "//comment\n{}", ": ERROR: JSON does not support comments\n") expectParseErrorJSON(t, "{/*comment*/}", ": ERROR: JSON does not support comments\n") expectParseErrorJSON(t, "{//comment\n}", ": ERROR: JSON does not support comments\n") expectParseErrorJSON(t, "{}/*comment*/", ": ERROR: JSON does not support comments\n") expectParseErrorJSON(t, "{}//comment\n", ": ERROR: JSON does not support comments\n") }