feat(config): preserve lint/tool directives by default across supported languages
This commit is contained in:
+74
-22
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -22,11 +23,67 @@ func Default() *Config {
|
||||
"XXX:",
|
||||
"@ts-ignore",
|
||||
"@ts-expect-error",
|
||||
"@ts-nocheck",
|
||||
"eslint-",
|
||||
"prettier-ignore",
|
||||
"biome-ignore",
|
||||
"deno-lint-ignore",
|
||||
"stylelint-",
|
||||
"istanbul ignore",
|
||||
"c8 ignore",
|
||||
"v8 ignore",
|
||||
"type: ignore",
|
||||
"pylint:",
|
||||
"mypy:",
|
||||
"type: ignore",
|
||||
"pyright:",
|
||||
"ruff:",
|
||||
"flake8:",
|
||||
"noqa",
|
||||
"nosec",
|
||||
"noinspection",
|
||||
"pragma: no cover",
|
||||
"fmt: off",
|
||||
"fmt: on",
|
||||
"pyre-ignore",
|
||||
"pyre-fixme",
|
||||
"isort:",
|
||||
"nolint",
|
||||
"go:",
|
||||
"lint:ignore",
|
||||
"rubocop:",
|
||||
"frozen_string_literal",
|
||||
"NOLINT",
|
||||
"clang-format",
|
||||
"cppcheck-suppress",
|
||||
"IWYU pragma",
|
||||
"ReSharper",
|
||||
"CHECKSTYLE",
|
||||
"NOPMD",
|
||||
"ktlint-disable",
|
||||
"swiftlint:",
|
||||
"ignore_for_file",
|
||||
"phpstan-",
|
||||
"psalm-",
|
||||
"phpcs:",
|
||||
"@phan",
|
||||
"scalafix:",
|
||||
"scalastyle:",
|
||||
"luacheck:",
|
||||
"selene:",
|
||||
"shellcheck",
|
||||
"hadolint",
|
||||
"tflint",
|
||||
"tfsec:",
|
||||
"checkov:",
|
||||
"yamllint",
|
||||
"{-#",
|
||||
"HLINT",
|
||||
"credo:",
|
||||
"solhint-",
|
||||
"slither-disable",
|
||||
"buf:lint",
|
||||
"@generated",
|
||||
"DO NOT EDIT",
|
||||
},
|
||||
ContextLines: 3,
|
||||
}
|
||||
@@ -189,27 +246,22 @@ func matchesWildcard(text, pattern string) bool {
|
||||
}
|
||||
|
||||
func CreateExampleConfig() error {
|
||||
content := `# Shush Configuration
|
||||
# Patterns to preserve in comments (supports wildcards with *)
|
||||
preserve = [
|
||||
"TODO:",
|
||||
"FIXME:",
|
||||
"HACK:",
|
||||
"XXX:",
|
||||
"@ts-ignore",
|
||||
"@ts-expect-error",
|
||||
"eslint-",
|
||||
"prettier-ignore",
|
||||
"pylint:",
|
||||
"mypy:",
|
||||
"type: ignore",
|
||||
"*IMPORTANT*", # Example wildcard: preserves any comment containing IMPORTANT
|
||||
"*DEBUG*", # Example wildcard: preserves any comment containing DEBUG
|
||||
]
|
||||
def := Default()
|
||||
|
||||
# Number of context lines to show around changes in preview mode (default: 3)
|
||||
context_lines = 3
|
||||
`
|
||||
var b strings.Builder
|
||||
b.WriteString("# Shush Configuration\n")
|
||||
b.WriteString("# Patterns to preserve in comments (supports wildcards with *)\n")
|
||||
b.WriteString("# Defaults below cover common lint/tool directives for supported languages.\n")
|
||||
b.WriteString("preserve = [\n")
|
||||
for _, p := range def.Preserve {
|
||||
fmt.Fprintf(&b, " %q,\n", p)
|
||||
}
|
||||
b.WriteString(" # Wildcards (*) preserve any comment containing the pattern:\n")
|
||||
b.WriteString(" \"*IMPORTANT*\",\n")
|
||||
b.WriteString(" \"*DEBUG*\",\n")
|
||||
b.WriteString("]\n\n")
|
||||
b.WriteString("# Number of context lines to show around changes in preview mode\n")
|
||||
fmt.Fprintf(&b, "context_lines = %d\n", def.ContextLines)
|
||||
|
||||
return os.WriteFile(".shush.toml", []byte(content), 0644)
|
||||
return os.WriteFile(".shush.toml", []byte(b.String()), 0644)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCreateExampleConfigRoundTrips(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Chdir(dir)
|
||||
|
||||
if err := CreateExampleConfig(); err != nil {
|
||||
t.Fatalf("create example config: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := loadFromFile(".shush.toml")
|
||||
if err != nil {
|
||||
t.Fatalf("parse generated config: %v", err)
|
||||
}
|
||||
|
||||
def := Default()
|
||||
have := make(map[string]bool, len(cfg.Preserve))
|
||||
for _, p := range cfg.Preserve {
|
||||
have[p] = true
|
||||
}
|
||||
for _, p := range def.Preserve {
|
||||
if !have[p] {
|
||||
t.Errorf("generated config missing default preserve pattern %q", p)
|
||||
}
|
||||
}
|
||||
if cfg.ContextLines != def.ContextLines {
|
||||
t.Errorf("context_lines = %d, want %d", cfg.ContextLines, def.ContextLines)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldPreserveDirectives(t *testing.T) {
|
||||
cfg := Default()
|
||||
directives := []string{
|
||||
"//nolint:errcheck",
|
||||
"//go:build linux",
|
||||
"# noqa: E501",
|
||||
"# type: ignore",
|
||||
"# rubocop:disable Style/Foo",
|
||||
"# frozen_string_literal: true",
|
||||
"{-# LANGUAGE OverloadedStrings #-}",
|
||||
"// @phpstan-ignore-line",
|
||||
"-- luacheck: ignore",
|
||||
"// NOPMD",
|
||||
"// ReSharper disable once Foo",
|
||||
"// ignore_for_file: foo",
|
||||
"# credo:disable-for-next-line",
|
||||
"// solhint-disable-next-line",
|
||||
"# yamllint disable-line",
|
||||
}
|
||||
for _, d := range directives {
|
||||
if !cfg.ShouldPreserveComment(d) {
|
||||
t.Errorf("directive not preserved by default: %q", d)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,3 +115,45 @@ func TestStripComments(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripCommentsPreservesDirectives(t *testing.T) {
|
||||
def := config.Default()
|
||||
cases := []struct {
|
||||
name string
|
||||
file string
|
||||
src string
|
||||
}{
|
||||
{"go nolint", "a.go", "x := 1 //nolint:errcheck\n"},
|
||||
{"go build directive", "a.go", "//go:build linux\npackage main\n"},
|
||||
{"python noqa", "a.py", "x = 1 # noqa: E501\n"},
|
||||
{"python type ignore", "a.py", "y = 2 # type: ignore\n"},
|
||||
{"python nosec", "a.py", "p = 1 # nosec B105\n"},
|
||||
{"js eslint disable", "a.js", "const a = 1 // eslint-disable-line\n"},
|
||||
{"ts expect error", "a.ts", "const b = 1 // @ts-expect-error\n"},
|
||||
{"c clang nolint", "a.c", "int x; // NOLINT(cert-err58-cpp)\n"},
|
||||
{"shell shellcheck", "a.sh", "y=1 # shellcheck disable=SC2086\n"},
|
||||
{"ruby rubocop", "a.rb", "x = 1 # rubocop:disable Style/Foo\n"},
|
||||
{"ruby frozen string", "a.rb", "# frozen_string_literal: true\nx = 1\n"},
|
||||
{"haskell pragma", "a.hs", "{-# LANGUAGE OverloadedStrings #-}\nmodule M where\n"},
|
||||
{"php phpstan", "a.php", "<?php\n$x = 1; // @phpstan-ignore-line\n"},
|
||||
{"lua luacheck", "a.lua", "local x = 1 -- luacheck: ignore\n"},
|
||||
{"java nopmd", "a.java", "int x = 1; // NOPMD\n"},
|
||||
{"csharp resharper", "a.cs", "int x = 1; // ReSharper disable once Foo\n"},
|
||||
{"dart ignore for file", "a.dart", "// ignore_for_file: foo\nvar x = 1;\n"},
|
||||
{"elixir credo", "a.ex", "x = 1 # credo:disable-for-next-line\n"},
|
||||
{"solidity solhint", "a.sol", "uint x; // solhint-disable-next-line\n"},
|
||||
{"kotlin ktlint", "a.kt", "val x = 1 // ktlint-disable\n"},
|
||||
{"yaml yamllint", "a.yml", "x: 1 # yamllint disable-line\n"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
out, _, err := StripComments([]byte(c.src), c.file, def, Options{})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if string(out) != c.src {
|
||||
t.Errorf("directive not preserved:\ngot %q\nwant %q", out, c.src)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user