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) }