feat(processor): StripComments chroma engine with legacy fallback
This commit is contained in:
@@ -6,6 +6,8 @@ import (
|
|||||||
|
|
||||||
"github.com/alecthomas/chroma/v2"
|
"github.com/alecthomas/chroma/v2"
|
||||||
"github.com/alecthomas/chroma/v2/lexers"
|
"github.com/alecthomas/chroma/v2/lexers"
|
||||||
|
"github.com/carlosarraes/shush/internal/config"
|
||||||
|
"github.com/carlosarraes/shush/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
@@ -61,3 +63,160 @@ func leadingWhitespace(s string) string {
|
|||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StripComments(src []byte, filename string, cfg *config.Config, opts Options) ([]byte, []LineChange, error) {
|
||||||
|
lexer := resolveLexer(filename)
|
||||||
|
if lexer == nil {
|
||||||
|
return legacyStrip(src, filename, cfg, opts)
|
||||||
|
}
|
||||||
|
it, err := lexer.Tokenise(nil, string(src))
|
||||||
|
if err != nil {
|
||||||
|
return legacyStrip(src, filename, cfg, opts)
|
||||||
|
}
|
||||||
|
toks := it.Tokens()
|
||||||
|
|
||||||
|
var check strings.Builder
|
||||||
|
for _, t := range toks {
|
||||||
|
check.WriteString(t.Value)
|
||||||
|
}
|
||||||
|
if check.String() != string(src) {
|
||||||
|
return legacyStrip(src, filename, cfg, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
origLines := strings.Split(string(src), "\n")
|
||||||
|
origLineText := func(n int) string {
|
||||||
|
if n >= 1 && n-1 < len(origLines) {
|
||||||
|
return origLines[n-1]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var changes []LineChange
|
||||||
|
var out strings.Builder
|
||||||
|
var cur strings.Builder
|
||||||
|
line := 1
|
||||||
|
hadDrop := false
|
||||||
|
hadPreserved := false
|
||||||
|
|
||||||
|
flush := func() {
|
||||||
|
result := cur.String()
|
||||||
|
orig := origLineText(line)
|
||||||
|
action := "kept"
|
||||||
|
emit := true
|
||||||
|
if hadDrop {
|
||||||
|
trimmed := strings.TrimRight(result, " \t")
|
||||||
|
if strings.TrimSpace(trimmed) == "" {
|
||||||
|
action = "removed"
|
||||||
|
if opts.PreserveLines {
|
||||||
|
result = leadingWhitespace(orig)
|
||||||
|
} else {
|
||||||
|
emit = false
|
||||||
|
result = ""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = trimmed
|
||||||
|
action = "modified"
|
||||||
|
}
|
||||||
|
} else if hadPreserved {
|
||||||
|
action = "preserved"
|
||||||
|
}
|
||||||
|
changes = append(changes, LineChange{Num: line, Original: orig, Result: result, Action: action})
|
||||||
|
if emit {
|
||||||
|
out.WriteString(result)
|
||||||
|
out.WriteString("\n")
|
||||||
|
}
|
||||||
|
cur.Reset()
|
||||||
|
hadDrop = false
|
||||||
|
hadPreserved = false
|
||||||
|
line++
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range toks {
|
||||||
|
startLine := line
|
||||||
|
endLine := startLine + strings.Count(t.Value, "\n")
|
||||||
|
drop := shouldDrop(t.Type, opts)
|
||||||
|
if drop && cfg != nil && cfg.ShouldPreserveComment(t.Value) {
|
||||||
|
drop = false
|
||||||
|
}
|
||||||
|
if drop && opts.InRange != nil {
|
||||||
|
for ln := startLine; ln <= endLine; ln++ {
|
||||||
|
if !opts.InRange(ln) {
|
||||||
|
drop = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
segs := strings.Split(t.Value, "\n")
|
||||||
|
for si, seg := range segs {
|
||||||
|
if drop {
|
||||||
|
if seg != "" {
|
||||||
|
hadDrop = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cur.WriteString(seg)
|
||||||
|
if seg != "" && isCommentType(t.Type) {
|
||||||
|
hadPreserved = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if si < len(segs)-1 {
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cur.Len() > 0 {
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(out.String()), changes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func legacyStrip(src []byte, filename string, cfg *config.Config, opts Options) ([]byte, []LineChange, error) {
|
||||||
|
lang, err := DetectLanguage(filename)
|
||||||
|
if err != nil {
|
||||||
|
return src, nil, err
|
||||||
|
}
|
||||||
|
p := &Processor{cli: types.CLI{Inline: opts.Inline, Block: opts.Block, PreserveLines: opts.PreserveLines}}
|
||||||
|
text := strings.TrimSuffix(string(src), "\n")
|
||||||
|
var lines []string
|
||||||
|
if len(src) > 0 {
|
||||||
|
lines = strings.Split(text, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
var changes []LineChange
|
||||||
|
var out strings.Builder
|
||||||
|
for i, ln := range lines {
|
||||||
|
num := i + 1
|
||||||
|
process := opts.InRange == nil || opts.InRange(num)
|
||||||
|
result := ln
|
||||||
|
action := "kept"
|
||||||
|
emit := true
|
||||||
|
if process {
|
||||||
|
nl := p.removeCommentsFromLine(ln, lang, cfg)
|
||||||
|
if nl != ln {
|
||||||
|
if nl == "" {
|
||||||
|
action = "removed"
|
||||||
|
emit = false
|
||||||
|
} else {
|
||||||
|
action = "modified"
|
||||||
|
result = nl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changes = append(changes, LineChange{Num: num, Original: ln, Result: result, Action: action})
|
||||||
|
if emit {
|
||||||
|
out.WriteString(result)
|
||||||
|
out.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []byte(out.String()), changes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func changed(changes []LineChange) bool {
|
||||||
|
for _, c := range changes {
|
||||||
|
if c.Action == "modified" || c.Action == "removed" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package processor
|
package processor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/alecthomas/chroma/v2"
|
"github.com/alecthomas/chroma/v2"
|
||||||
|
"github.com/carlosarraes/shush/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResolveLexer(t *testing.T) {
|
func TestResolveLexer(t *testing.T) {
|
||||||
@@ -56,3 +58,41 @@ func TestShouldDrop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStripComments(t *testing.T) {
|
||||||
|
def := config.Default()
|
||||||
|
todo := &config.Config{Preserve: []string{"TODO:"}, ContextLines: 3}
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
file string
|
||||||
|
src string
|
||||||
|
cfg *config.Config
|
||||||
|
opts Options
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"multiline block collapses", "a.c", "code();\n/* a\n b */\nmore();\n", def, Options{}, "code();\nmore();\n"},
|
||||||
|
{"inline block both sides kept", "a.c", "x(); /* c */ y();\n", def, Options{}, "x(); y();\n"},
|
||||||
|
{"preproc kept comment dropped", "a.c", "#include <stdio.h>\nint x; // c\n", def, Options{}, "#include <stdio.h>\nint x;\n"},
|
||||||
|
{"unterminated block to eof", "a.c", "x();\n/* no end\nmore\n", def, Options{}, "x();\n"},
|
||||||
|
{"marker inside line comment inert", "a.c", "z(); // a /* b\nw();\n", def, Options{}, "z();\nw();\n"},
|
||||||
|
{"raw string preserved", "a.go", "s := `a /* b */ c`\n", def, Options{}, "s := `a /* b */ c`\n"},
|
||||||
|
{"string marker preserved", "a.js", "console.log(\"/* x */\"); /* r */ y();\n", def, Options{}, "console.log(\"/* x */\"); y();\n"},
|
||||||
|
{"preserve pattern keeps line", "a.go", "x := 1 // TODO: keep\n", todo, Options{}, "x := 1 // TODO: keep\n"},
|
||||||
|
{"preserve-lines blanks comment-only", "a.py", " # c\nx = 1\n", def, Options{PreserveLines: true}, " \nx = 1\n"},
|
||||||
|
{"inline flag keeps block", "a.c", "code(); /* b */ // l\n", def, Options{Inline: true}, "code(); /* b */\n"},
|
||||||
|
{"block flag keeps line", "a.c", "code(); /* b */ // l\n", def, Options{Block: true}, "code(); // l\n"},
|
||||||
|
{"straddle block kept", "a.c", "/* a\n b */ x()\n", def, Options{InRange: func(n int) bool { return n == 2 }}, "/* a\n b */ x()\n"},
|
||||||
|
{"range block removed when fully inside", "a.c", "x()\n/* a\n b */\ny()\n", def, Options{InRange: func(n int) bool { return n >= 2 && n <= 3 }}, "x()\ny()\n"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
out, _, err := StripComments([]byte(c.src), c.file, c.cfg, c.opts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(out, []byte(c.want)) {
|
||||||
|
t.Errorf("got %q want %q", out, c.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user