Repository navigation
Expand file tree
/
Copy pathdocs_test.go
More file actions
130 lines (124 loc) · 3.42 KB
/
Copy pathdocs_test.go
File metadata and controls
130 lines (124 loc) · 3.42 KB
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package nodkey
import (
"os"
"path/filepath"
"strings"
"testing"
)
// TestDocsShellLinesHaveNoTrailingComment checks the shell blocks of the
// docs (issue #92). Interactive zsh does not treat "#" as the start of a
// comment unless the option interactivecomments is on, and it is off by
// default. A reader who pastes "cmd # note" into zsh passes "#" and the
// words after it to cmd. So a note goes on its own line above the
// command. zsh may print "command not found: #" for such a line, but no
// command gets extra words.
func TestDocsShellLinesHaveNoTrailingComment(t *testing.T) {
var files []string
for _, pattern := range []string{"*.md", "docs/*.md"} {
found, err := filepath.Glob(pattern)
if err != nil {
t.Fatal(err)
}
files = append(files, found...)
}
if len(files) < 3 {
t.Fatalf("found only %d Markdown files: run the test from the root of the module", len(files))
}
for _, f := range files {
data, err := os.ReadFile(f) //nolint:gosec // G304: a Markdown file of the repository, from a fixed glob.
if err != nil {
t.Fatal(err)
}
for _, bad := range trailingShellComments(string(data)) {
t.Errorf("%s:%d: a shell line ends in a # comment; put the note on its own line above the command (issue #92): %s",
f, bad.line, bad.text)
}
}
}
// TestTrailingShellComments checks the finder itself, so that the test
// above cannot pass because the finder sees nothing.
func TestTrailingShellComments(t *testing.T) {
doc := strings.Join([]string{
"text # not code",
"```sh",
"# a note on its own line",
"read -rs TOKEN # paste the token",
`echo "a # in quotes" 'and # here'`,
"curl https://example.com/#anchor",
"echo $# ${#x}",
"```",
"```toml",
"key = 1 # TOML comment",
"```",
" ```bash",
" VERSION=0.2.0 # the version",
" ```",
"```",
"plain block # not shell",
"```",
}, "\n")
got := trailingShellComments(doc)
want := []int{4, 13}
if len(got) != len(want) {
t.Fatalf("found %v, want lines %v", got, want)
}
for i := range want {
if got[i].line != want[i] {
t.Errorf("finding %d is on line %d, want %d", i, got[i].line, want[i])
}
}
}
type shellLine struct {
line int
text string
}
// trailingShellComments returns the lines of the sh, bash, shell, zsh and
// console blocks of a Markdown text that have a "#" comment after a
// command.
func trailingShellComments(doc string) []shellLine {
var out []shellLine
inBlock, isShell := false, false
for i, line := range strings.Split(doc, "\n") {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "```") {
if inBlock {
inBlock, isShell = false, false
continue
}
inBlock = true
switch strings.TrimSpace(strings.TrimPrefix(trimmed, "```")) {
case "sh", "bash", "shell", "zsh", "console":
isShell = true
}
continue
}
if isShell && hasTrailingComment(trimmed) {
out = append(out, shellLine{line: i + 1, text: trimmed})
}
}
return out
}
// hasTrailingComment reports whether a shell line has a word that starts
// with "#" after some other word, outside quotes.
func hasTrailingComment(line string) bool {
var quote rune
prev := ' '
seenWord := false
for _, r := range line {
switch {
case quote != 0:
if r == quote {
quote = 0
}
case r == '\'' || r == '"':
quote = r
seenWord = true
case r == '#' && (prev == ' ' || prev == '\t'):
return seenWord
case r != ' ' && r != '\t':
seenWord = true
}
prev = r
}
return false
}