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