Skip to content

Commit eec788d

Browse files
committed
fix: read the character actually beside a match for inline code
Signed-off-by: Joseph Kato <joseph@jdkato.io>
1 parent 590278e commit eec788d

3 files changed

Lines changed: 84 additions & 6 deletions

File tree

‎internal/core/location.go‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ func insideInlineMarkup(ctx string, fs []int) bool {
118118
return true
119119
}
120120

121+
// fs[1] is exclusive, so the character right beside the match is
122+
// ctx[fs[1]] -- the check above looks one further out and misses a
123+
// closing backtick sitting directly against the match, as in
124+
// `f(x) # word`. Only backticks: a hyphen there is usually a compound
125+
// word, and rejecting `well` in `well-known` would mislocate it.
126+
if fs[1] < len(ctx) && ctx[fs[1]] == '`' {
127+
return true
128+
}
129+
121130
return false
122131
}
123132

@@ -301,13 +310,8 @@ func initialPosition(ctx, txt string, a Alert, at int) (int, string) {
301310
// by ignoring these inline code spans.
302311
//
303312
// TODO: What about `scope: raw`?
304-
size := nlp.StrLen(ctx)
305313
for _, fs := range fsi {
306-
start := fs[0] - 1
307-
end := fs[1] + 1
308-
if start > 0 && (ctx[start] == '`' || ctx[start] == '-') {
309-
continue
310-
} else if end < size && (ctx[end] == '`' || ctx[end] == '-') {
314+
if insideInlineMarkup(ctx, fs) {
311315
continue
312316
}
313317
idx = fs[0]

‎internal/core/location_test.go‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,50 @@ func TestInitialPositionPunctAnchor(t *testing.T) {
2828
}
2929
}
3030

31+
// A match butted directly against a code span's delimiter is inside (or
32+
// beside) that span, and locating an alert there mislocates it -- the prose
33+
// occurrence the rule actually matched sits later in the line. fs[1] is
34+
// exclusive, so the character to inspect is ctx[fs[1]] itself. See #1132's
35+
// follow-up: `Inline `+"`sum(x) # ZQX`"+` and text ZQX after.`
36+
func TestInsideInlineMarkup(t *testing.T) {
37+
cases := []struct {
38+
name string
39+
ctx string
40+
fs []int
41+
want bool
42+
}{
43+
{"plain prose", "some ZQX here", []int{5, 8}, false},
44+
{"opening backtick beside match", "x `ZQX` y", []int{3, 6}, true},
45+
{"closing backtick against match", "x `f() # ZQX` and ZQX y",
46+
[]int{9, 12}, true},
47+
{"prose occurrence after a span", "x `f() # ZQX` and ZQX y",
48+
[]int{18, 21}, false},
49+
{"hyphenated compound is not markup", "a well-known fix",
50+
[]int{2, 6}, false},
51+
}
52+
53+
for _, c := range cases {
54+
t.Run(c.name, func(t *testing.T) {
55+
if got := insideInlineMarkup(c.ctx, c.fs); got != c.want {
56+
t.Errorf("insideInlineMarkup(%q, %v) = %v, want %v",
57+
c.ctx, c.fs, got, c.want)
58+
}
59+
})
60+
}
61+
}
62+
63+
// A match shadowed by an identical string inside a code span must be located
64+
// at its prose occurrence, not the span's.
65+
func TestInitialPositionSkipsCodeSpan(t *testing.T) {
66+
ctx := "Inline `sum(x) # ZQX` and text ZQX after."
67+
txt := "Inline ************ and text ZQX after."
68+
69+
pos, _ := initialPosition(ctx, txt, Alert{Match: "ZQX"}, -1)
70+
if pos != 32 {
71+
t.Errorf("pos = %d, want 32 (the prose occurrence)", pos)
72+
}
73+
}
74+
3175
func TestIsPunctOnly(t *testing.T) {
3276
cases := map[string]bool{
3377
",": true,

‎testdata/e2e/misc.yaml‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,33 @@ cases:
170170
test.md:17:25:Vale.Spelling:Did you really mean 'config'?
171171
test.md:23:78:Vale.Spelling:Did you really mean 'config'?
172172
test.md:23:85:Vale.Spelling:Did you really mean 'json'?
173+
174+
- name: shadowed-by-code-span
175+
about: a match whose text also appears inside an earlier code span must be
176+
reported at its prose occurrence, not the span's
177+
files:
178+
.vale.ini: |
179+
StylesPath = styles
180+
MinAlertLevel = suggestion
181+
182+
[*.md]
183+
BasedOnStyles = T
184+
styles/T/Tok.yml: |
185+
extends: existence
186+
message: "'%s' in text"
187+
level: error
188+
nonword: true
189+
raw:
190+
- ZQX
191+
test.md: |
192+
A first paragraph.
193+
194+
Inline `sum(x) # ZQX` and text ZQX after.
195+
196+
Both ZQX one and ZQX two.
197+
args: test.md
198+
exit: 1
199+
want: |
200+
test.md:3:32:T.Tok:'ZQX' in text
201+
test.md:5:6:T.Tok:'ZQX' in text
202+
test.md:5:18:T.Tok:'ZQX' in text

0 commit comments

Comments
 (0)