| 🛟 Updated. 28d5985 k33g 4h ago | 1 | package syntax |
| 2 | |
| 3 | import "testing" |
| 4 | |
| 5 | // wantBash is wantLine for shell scripts. |
| 6 | func wantBash(t *testing.T, src string, line int, want ...string) { |
| 7 | t.Helper() |
| 8 | wantLine(t, LanguageBash, src, line, want...) |
| 9 | } |
| 10 | |
| 11 | func TestBashColoursKeywords(t *testing.T) { |
| 12 | wantBash(t, "if true; then", 0, |
| 13 | "if:keyword", "true:constant", ";:punctuation", "then:keyword") |
| 14 | wantBash(t, "for f in a; do", 0, |
| 15 | "for:keyword", "f:identifier", "in:keyword", "a:identifier", ";:punctuation", "do:keyword") |
| 16 | } |
| 17 | |
| 18 | func TestOnlyTheFirstWordOfALineIsTheCommand(t *testing.T) { |
| 19 | // An argument coloured as a command reads as a mistake in the script. |
| 20 | wantBash(t, "grep pattern file", 0, "grep:function", "pattern:identifier", "file:identifier") |
| 21 | } |
| 22 | |
| 23 | func TestBashColoursBuiltinsApartFromCommands(t *testing.T) { |
| 24 | wantBash(t, "echo hello", 0, "echo:builtin", "hello:identifier") |
| 25 | wantBash(t, "grep hello", 0, "grep:function", "hello:identifier") |
| 26 | } |
| 27 | |
| 28 | func TestBashColoursAComment(t *testing.T) { |
| 29 | wantBash(t, "x=1 # a note", 0, "x:identifier", "=:operator", "1:number", "# a note:comment") |
| 30 | } |
| 31 | |
| 32 | func TestBashColoursAnAssignment(t *testing.T) { |
| 33 | wantBash(t, "NAME=value", 0, "NAME:identifier", "=:operator", "value:identifier") |
| 34 | } |
| 35 | |
| 36 | func TestBashColoursALiteralString(t *testing.T) { |
| 37 | wantBash(t, "echo 'no $expansion here'", 0, "echo:builtin", "'no $expansion here':string") |
| 38 | } |
| 39 | |
| 40 | func TestABackslashDoesNotEscapeInABashLiteralString(t *testing.T) { |
| 41 | // The point of the single-quoted form: everything up to the next quote is |
| 42 | // literal, and the quote after a backslash still closes it. |
| 43 | wantBash(t, `echo 'a\'`, 0, "echo:builtin", `'a\':string`) |
| 44 | } |
| 45 | |
| 46 | func TestBashColoursExpansionsInsideADoubleQuotedString(t *testing.T) { |
| 47 | // "$HOME/bin" is a path with a variable in it, and which part is the |
| 48 | // variable is most of what a reader wants to see. |
| 49 | wantBash(t, `echo "$HOME/bin"`, 0, |
| 50 | "echo:builtin", `"`+":string", "$HOME:builtin", `/bin"`+":string") |
| 51 | } |
| 52 | |
| 53 | func TestBashColoursTheFormsOfExpansion(t *testing.T) { |
| 54 | wantBash(t, "echo $NAME", 0, "echo:builtin", "$NAME:builtin") |
| 55 | wantBash(t, "echo ${NAME:-x}", 0, "echo:builtin", "${NAME:-x}:builtin") |
| 56 | wantBash(t, "echo $(date)", 0, "echo:builtin", "$(date):builtin") |
| 57 | wantBash(t, "echo $1", 0, "echo:builtin", "$1:builtin") |
| 58 | wantBash(t, "echo $?", 0, "echo:builtin", "$?:builtin") |
| 59 | wantBash(t, "echo $@", 0, "echo:builtin", "$@:builtin") |
| 60 | } |
| 61 | |
| 62 | func TestANestedCommandSubstitutionIsOneSpan(t *testing.T) { |
| 63 | wantBash(t, "echo $(dirname $(pwd))", 0, "echo:builtin", "$(dirname $(pwd)):builtin") |
| 64 | } |
| 65 | |
| 66 | func TestAnUnclosedExpansionIsColouredToTheEndOfTheLine(t *testing.T) { |
| 67 | wantBash(t, "echo ${NAME", 0, "echo:builtin", "${NAME:builtin") |
| 68 | } |
| 69 | |
| 70 | func TestAnEscapedQuoteDoesNotEndADoubleQuotedString(t *testing.T) { |
| 71 | wantBash(t, `echo "he said \"no\""`, 0, "echo:builtin", `"he said \"no\"":string`) |
| 72 | } |
| 73 | |
| 74 | func TestBashColoursACommandWithDashedOptions(t *testing.T) { |
| 75 | // A hyphen belongs in a word here, or every flag would be an operator |
| 76 | // followed by a command. |
| 77 | wantBash(t, "set -eu", 0, "set:builtin", "-eu:identifier") |
| 78 | } |
| 79 | |
| 80 | func TestBashColoursAScriptTheProjectShips(t *testing.T) { |
| 81 | src := "#!/usr/bin/env bash\nset -euo pipefail\n\nTAG=\"${1:-latest}\"\necho \"building $TAG\"\n" |
| 82 | |
| 83 | wantBash(t, src, 0, "#!/usr/bin/env bash:comment") |
| 84 | wantBash(t, src, 1, "set:builtin", "-euo:identifier", "pipefail:identifier") |
| 85 | wantBash(t, src, 3, "TAG:identifier", "=:operator", `"`+":string", "${1:-latest}:builtin", `":string`) |
| 86 | wantBash(t, src, 4, "echo:builtin", `"building `+":string", "$TAG:builtin", `":string`) |
| 87 | } |
| 88 | |
| 89 | func TestBashGivesOneEntryPerLine(t *testing.T) { |
| 90 | if got := len(Highlight(LanguageBash, "a\nb\n\nc\n")); got != 5 { |
| 91 | t.Errorf("Highlight() returned %d lines for a 5-line document", got) |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | func TestBashSpansStayInsideTheirLine(t *testing.T) { |
| 96 | src := "#!/bin/sh\nif [ -n \"$1\" ]; then\n echo \"got $(basename \"$1\")\"\nfi\n" |
| 97 | assertSpansFit(t, LanguageBash, src) |
| 98 | } |