| 🛟 Updated. 28d5985 k33g 17h ago | 1 | package syntax |
| 2 | |
| 3 | import "testing" |
| 4 | |
| 5 | // wantJS is wantLine for JavaScript. |
| 6 | func wantJS(t *testing.T, src string, line int, want ...string) { |
| 7 | t.Helper() |
| 8 | wantLine(t, LanguageJavaScript, src, line, want...) |
| 9 | } |
| 10 | |
| 11 | func TestJavaScriptColoursKeywords(t *testing.T) { |
| 12 | wantJS(t, "const x = 1", 0, |
| 13 | "const:keyword", "x:identifier", "=:operator", "1:number") |
| 14 | wantJS(t, "async function f() {}", 0, |
| 15 | "async:keyword", "function:keyword", "f:function", |
| 16 | "(:punctuation", "):punctuation", "{:punctuation", "}:punctuation") |
| 17 | } |
| 18 | |
| 19 | func TestJavaScriptColoursConstantsAndGlobals(t *testing.T) { |
| 20 | wantJS(t, "x = true", 0, "x:identifier", "=:operator", "true:constant") |
| 21 | wantJS(t, "x = null", 0, "x:identifier", "=:operator", "null:constant") |
| 22 | wantJS(t, "console.log(1)", 0, |
| 23 | "console:builtin", ".:punctuation", "log:function", |
| 24 | "(:punctuation", "1:number", "):punctuation") |
| 25 | } |
| 26 | |
| 27 | func TestANameBeforeAParenthesisIsACall(t *testing.T) { |
| 28 | wantJS(t, "doThing()", 0, "doThing:function", "(:punctuation", "):punctuation") |
| 29 | wantJS(t, "doThing", 0, "doThing:identifier") |
| 30 | } |
| 31 | |
| 32 | func TestJavaScriptColoursStrings(t *testing.T) { |
| 33 | wantJS(t, `x = "double"`, 0, "x:identifier", "=:operator", `"double":string`) |
| 34 | wantJS(t, "x = 'single'", 0, "x:identifier", "=:operator", "'single':string") |
| 35 | } |
| 36 | |
| 37 | func TestAnEscapedQuoteDoesNotEndAJavaScriptString(t *testing.T) { |
| 38 | wantJS(t, `x = "he said \"no\""`, 0, "x:identifier", "=:operator", `"he said \"no\"":string`) |
| 39 | } |
| 40 | |
| 41 | func TestAnUnterminatedJavaScriptStringIsColouredToTheEndOfTheLine(t *testing.T) { |
| 42 | wantJS(t, `x = "half written`, 0, "x:identifier", "=:operator", `"half written:string`) |
| 43 | } |
| 44 | |
| 45 | func TestJavaScriptColoursLineAndBlockComments(t *testing.T) { |
| 46 | wantJS(t, "x = 1 // a note", 0, |
| 47 | "x:identifier", "=:operator", "1:number", "// a note:comment") |
| 48 | wantJS(t, "x = /* inline */ 1", 0, |
| 49 | "x:identifier", "=:operator", "/* inline */:comment", "1:number") |
| 50 | } |
| 51 | |
| 52 | func TestABlockCommentCarriesAcrossLines(t *testing.T) { |
| 53 | src := "/* first\n second\n third */ x = 1" |
| 54 | |
| 55 | wantJS(t, src, 0, "/* first:comment") |
| 56 | wantJS(t, src, 1, " second:comment") |
| 57 | wantJS(t, src, 2, " third */:comment", "x:identifier", "=:operator", "1:number") |
| 58 | } |
| 59 | |
| 60 | func TestCodeInsideABlockCommentIsNotColoured(t *testing.T) { |
| 61 | src := "/*\nconst x = 1\n*/" |
| 62 | |
| 63 | wantJS(t, src, 1, "const x = 1:comment") |
| 64 | } |
| 65 | |
| 66 | func TestJavaScriptColoursTemplateLiterals(t *testing.T) { |
| 67 | wantJS(t, "x = `plain`", 0, "x:identifier", "=:operator", "`plain`:string") |
| 68 | wantJS(t, "x = `a ${b} c`", 0, "x:identifier", "=:operator", "`a ${b} c`:string") |
| 69 | } |
| 70 | |
| 71 | func TestATemplateLiteralCarriesAcrossLines(t *testing.T) { |
| 72 | src := "const html = `\n <p>hi</p>\n`\nconst x = 1" |
| 73 | |
| 74 | wantJS(t, src, 1, " <p>hi</p>:string") |
| 75 | wantJS(t, src, 2, "`:string") |
| 76 | // The line after it is code again. |
| 77 | wantJS(t, src, 3, "const:keyword", "x:identifier", "=:operator", "1:number") |
| 78 | } |
| 79 | |
| 80 | func TestJavaScriptColoursNumbers(t *testing.T) { |
| 81 | for _, value := range []string{"42", "3.14", "0x1f", "0b1010", "0o777", "1_000_000", "1e6", "10n"} { |
| 82 | t.Run(value, func(t *testing.T) { |
| 83 | got := spansOf(t, LanguageJavaScript, "x = "+value, 0) |
| 84 | if last := got[len(got)-1]; last != value+":number" { |
| 85 | t.Errorf("%q was coloured %q, want a number", value, last) |
| 86 | } |
| 87 | }) |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | func TestJavaScriptTakesOperatorsInRuns(t *testing.T) { |
| 92 | wantJS(t, "a === b", 0, "a:identifier", "===:operator", "b:identifier") |
| 93 | wantJS(t, "a ?? b", 0, "a:identifier", "??:operator", "b:identifier") |
| 94 | wantJS(t, "a => b", 0, "a:identifier", "=>:operator", "b:identifier") |
| 95 | } |
| 96 | |
| 97 | func TestARegexIsLeftAsOperatorsRatherThanGuessedAt(t *testing.T) { |
| 98 | // Telling /x/g from a division needs to know whether the previous token |
| 99 | // could end an expression. Getting it wrong colours the rest of the line |
| 100 | // as a string, which is worse than this. |
| 101 | got := spansOf(t, LanguageJavaScript, "x = /ab/g", 0) |
| 102 | for _, span := range got { |
| 103 | if len(span) > 7 && span[len(span)-7:] == ":string" { |
| 104 | t.Errorf("a regex was guessed at as a string: %v", got) |
| 105 | } |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | func TestJavaScriptGivesOneEntryPerLine(t *testing.T) { |
| 110 | if got := len(Highlight(LanguageJavaScript, "a\nb\n\nc\n")); got != 5 { |
| 111 | t.Errorf("Highlight() returned %d lines for a 5-line document", got) |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | func TestJavaScriptSpansStayInsideTheirLine(t *testing.T) { |
| 116 | src := "import { a } from 'b'\n/* c\nd */\nconst t = `x${y}z`\nconsole.log(0x1f)\n" |
| 117 | assertSpansFit(t, LanguageJavaScript, src) |
| 118 | } |