Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions internal/export/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,18 +244,18 @@ func inGeneratedRange(line int, ranges []lint.LineRange) bool {
// because such PIs sit inside a pair's body range and are skipped
// here.
func stripDirectives(f *lint.File, directives []directiveStrip) []byte {
stripLines := map[int]bool{}
bodyLines := map[int]bool{}
stripLines := map[int]struct{}{}
bodyLines := map[int]struct{}{}

for _, d := range directives {
pairs, _ := gensection.FindMarkerPairs(f, d.name, d.ruleID, d.ruleName)
for _, p := range pairs {
for line := p.StartLine; line < p.ContentFrom; line++ {
stripLines[line] = true
stripLines[line] = struct{}{}
}
stripLines[p.EndLine] = true
stripLines[p.EndLine] = struct{}{}
for line := p.ContentFrom; line <= p.ContentTo; line++ {
bodyLines[line] = true
bodyLines[line] = struct{}{}
}
}
}
Expand All @@ -276,7 +276,7 @@ func stripDirectives(f *lint.File, directives []directiveStrip) []byte {
continue
}
for line := startLine; line <= endLine; line++ {
stripLines[line] = true
stripLines[line] = struct{}{}
}
}

Expand Down Expand Up @@ -316,20 +316,20 @@ func piLineRange(pi *piparser.ProcessingInstruction, f *lint.File) (int, int) {
return start, f.LineOfOffset(pi.ClosureLine.Start)
}

func overlapsAny(from, to int, set map[int]bool) bool {
func overlapsAny(from, to int, set map[int]struct{}) bool {
for line := from; line <= to; line++ {
if set[line] {
if _, ok := set[line]; ok {
return true
}
}
return false
}

func emitLines(srcLines [][]byte, strip map[int]bool) []byte {
func emitLines(srcLines [][]byte, strip map[int]struct{}) []byte {
var b bytes.Buffer
for i, line := range srcLines {
lineNum := i + 1
if strip[lineNum] {
if _, ok := strip[lineNum]; ok {
continue
}
b.Write(line)
Expand Down
8 changes: 5 additions & 3 deletions internal/index/locate.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,9 +462,11 @@ func listItemValue(line string) (string, bool) {
// Returns "" when none precedes the item.
func enclosingListKey(lines [][]byte, line int) string {
for i := line - 2; i >= 0; i-- {
m := piArgRE.FindStringSubmatch(string(lines[i]))
if len(m) >= 3 && strings.TrimSpace(m[2]) == "" {
return m[1]
// FindSubmatch accepts []byte directly, avoiding a string allocation
// per scanned line when the caller passes bytes.
m := piArgRE.FindSubmatch(lines[i])
if len(m) >= 3 && len(m[2]) == 0 {
return string(m[1])
}
// A populated `key: value` line or another list item does not
// open a list block for our item; keep scanning past list items
Expand Down
12 changes: 12 additions & 0 deletions internal/index/locate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,15 @@ func TestLocateFrontMatterKindsListItemWithDifferentValues(t *testing.T) {
assert.Equal(t, "kinds", res.FrontMatterKey)
assert.Equal(t, "reference", res.FrontMatterValue)
}

func TestEnclosingListKey_FindsParentKey(t *testing.T) {
lines := [][]byte{
[]byte("inputs:"),
[]byte(" - alpha"),
[]byte(" - beta"),
[]byte(" - gamma"),
}
// Line 4 (1-based) is "gamma"; the enclosing key is "inputs".
got := enclosingListKey(lines, 4)
assert.Equal(t, "inputs", got)
}
3 changes: 2 additions & 1 deletion internal/lsp/rename.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ func isValidRefDefLine(source []byte, line int) bool {
if bodyLine < 1 {
return false
}
return rename.ValidRefDefBodyLines(body)[bodyLine]
_, ok := rename.ValidRefDefBodyLines(body)[bodyLine]
return ok
}

// headingPrepareRange builds the rename range for an ATX or setext
Expand Down
14 changes: 7 additions & 7 deletions internal/rename/rename.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ func LinkRef(source []byte, oldLabel, newName string) ([]Edit, error) {
// reference definition goldmark accepted (not a code-block
// look-alike). The LSP prepare-rename gate consults it so the rename
// UI never surfaces on a `[label]: url`-shaped code sample.
func ValidRefDefBodyLines(body []byte) map[int]bool {
out := map[int]bool{}
func ValidRefDefBodyLines(body []byte) map[int]struct{} {
out := map[int]struct{}{}
for _, m := range validRefDefMatches(body) {
out[m.bodyLine] = true
out[m.bodyLine] = struct{}{}
}
return out
}
Expand Down Expand Up @@ -169,7 +169,7 @@ func validRefDefMatches(body []byte) []validRefDefMatch {
var out []validRefDefMatch
for _, m := range index.RefDefRegexpMatches(body) {
bodyLine := lineOfBodyOffset(body, m[2])
if consumed[bodyLine] {
if _, ok := consumed[bodyLine]; ok {
continue
}
raw := body[m[2]:m[3]]
Expand All @@ -190,8 +190,8 @@ func validRefDefMatches(body []byte) []validRefDefMatch {
// is by definition not a def. The Document root and
// LinkReferenceDefinition nodes are skipped: the former spans the
// whole buffer, the latter IS the line a real def lives on.
func contentBlockLines(root ast.Node, body []byte) map[int]bool {
out := map[int]bool{}
func contentBlockLines(root ast.Node, body []byte) map[int]struct{} {
out := map[int]struct{}{}
_ = ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
Expand All @@ -206,7 +206,7 @@ func contentBlockLines(root ast.Node, body []byte) map[int]bool {
ls := n.Lines()
for i := 0; i < ls.Len(); i++ {
seg := ls.At(i)
out[lineOfBodyOffset(body, seg.Start)] = true
out[lineOfBodyOffset(body, seg.Start)] = struct{}{}
}
return ast.WalkContinue, nil
})
Expand Down
7 changes: 5 additions & 2 deletions internal/rename/rename_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,12 @@ func TestLinkRef_WithFrontMatterLineOffset(t *testing.T) {

func TestValidRefDefBodyLines(t *testing.T) {
body := []byte("para\n\n[a]: u\n\n```\n[b]: v\n```\n")
// ValidRefDefBodyLines returns map[int]struct{} — presence means valid def.
got := ValidRefDefBodyLines(body)
assert.True(t, got[3], "real def on body line 3")
assert.False(t, got[6], "fenced def-shaped line is not a def")
_, ok3 := got[3]
assert.True(t, ok3, "real def on body line 3")
_, ok6 := got[6]
assert.False(t, ok6, "fenced def-shaped line is not a def")
}

func TestBodyAndFMOffset(t *testing.T) {
Expand Down
14 changes: 6 additions & 8 deletions internal/rules/concisenessscoring/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,15 @@ func (r *Rule) Check(f *lint.File) []lint.Diagnostic {
}

line := astutil.ParagraphLine(para, f)
message := fmt.Sprintf(
"conciseness score too low (%.2f < %.2f); target >= %.2f",
result.Conciseness, r.MinScore, r.MinScore,
)
examples := formatExamples(result.Cues)
var cuesSuffix string
if examples != "" {
message += fmt.Sprintf(
"; reduce verbose cues (e.g., %s)",
examples,
)
cuesSuffix = "; reduce verbose cues (e.g., " + examples + ")"
}
message := fmt.Sprintf(
"conciseness score too low (%.2f < %.2f); target >= %.2f%s",
result.Conciseness, r.MinScore, r.MinScore, cuesSuffix,
)

diags = append(diags, lint.Diagnostic{
File: f.Path,
Expand Down
50 changes: 50 additions & 0 deletions internal/rules/concisenessscoring/rule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,53 @@ func TestNewScorer_Success(t *testing.T) {
require.NoError(t, err)
assert.NotNil(t, s)
}

func TestCheck_MessageNoConcatenationWhenExamplesPresent(t *testing.T) {
// When verbose cues are found, the diagnostic message must include
// the cue examples in a single fmt.Sprintf rather than via `message +=`
// concatenation. This test verifies the combined message format so the
// implementation cannot silently drop the cue text.
src := []byte(verboseParagraph() + "\n")
f, err := lint.NewFile("test.md", src)
require.NoError(t, err)

threshold := modelConciseness(t)
r := &Rule{MinScore: threshold, MinWords: 20}
diags := r.Check(f)
if len(diags) == 0 {
t.Skip("model threshold did not trigger on fixture")
}
msg := diags[0].Message
// The message must contain both the score summary and the cue guidance.
assert.Contains(t, msg, "conciseness score too low")
assert.Contains(t, msg, "target >=")
// verboseParagraph always triggers cue detection; both must be present.
assert.Contains(t, msg, "reduce verbose cues")
assert.Contains(t, msg, "e.g.,", "message with cues must include formatted examples")
}

func TestCheck_NoCuesMessage(t *testing.T) {
// Exercises the if examples == "" branch: a paragraph that scores as
// verbose but produces no cue phrases yields the base message only.
noCuePara := "When the configuration is set, the setting is used by the " +
"configuration. The configuration uses the setting and the setting " +
"configures the configuration."
s, err := NewScorer()
require.NoError(t, err)
scored := s.Score(noCuePara)
if len(scored.Cues) > 0 {
t.Skip("scorer detected cues in fixture; model may have drifted")
}

src := []byte(noCuePara + "\n")
f, err := lint.NewFile("test.md", src)
require.NoError(t, err)

r := &Rule{MinScore: scored.Conciseness + 0.10, MinWords: 1}
diags := r.Check(f)
require.Len(t, diags, 1, "expected diagnostic for low-scoring no-cue paragraph")
msg := diags[0].Message
assert.Contains(t, msg, "conciseness score too low")
assert.Contains(t, msg, "target >=")
assert.NotContains(t, msg, "reduce verbose cues", "no cues detected, so no cue guidance")
}
5 changes: 3 additions & 2 deletions internal/secreview/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"sort"
"strconv"
"time"
)

Expand Down Expand Up @@ -112,9 +113,9 @@ func locStr(loc *Location) string {
s = "?"
}
if loc.StartLine != 0 {
s += fmt.Sprintf(":%d", loc.StartLine)
s += ":" + strconv.Itoa(loc.StartLine)
if loc.EndLine != 0 && loc.EndLine != loc.StartLine {
s += fmt.Sprintf("-%d", loc.EndLine)
s += "-" + strconv.Itoa(loc.EndLine)
}
}
return s
Expand Down
Loading