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
61 changes: 54 additions & 7 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ func PutIssueBuffer(issues []Issue) {

// Config contains all configuration options for the goconst analyzer.
type Config struct {
// IgnoreStrings is a regular expression to filter strings
IgnoreStrings string
// IgnoreStrings is a list of regular expressions to filter strings
IgnoreStrings []string
// IgnoreTests indicates whether test files should be excluded
IgnoreTests bool
// MatchWithConstants enables matching strings with existing constants
Expand All @@ -68,11 +68,51 @@ type Config struct {
FindDuplicates bool
}

// Run analyzes the provided AST files for duplicated strings or numbers
// according to the provided configuration.
// It returns a slice of Issue objects containing the findings.
func Run(files []*ast.File, fset *token.FileSet, cfg *Config) ([]Issue, error) {
p := New(
// NewWithIgnorePatterns creates a new instance of the parser with support for multiple ignore patterns.
// This is an alternative constructor that takes a slice of ignore string patterns.
func NewWithIgnorePatterns(
path, ignore string,
ignoreStrings []string,
ignoreTests, matchConstant, numbers, findDuplicates bool,
numberMin, numberMax, minLength, minOccurrences int,
excludeTypes map[Type]bool) *Parser {

// Join multiple patterns with OR for regex
var ignoreStringsPattern string
if len(ignoreStrings) > 0 {
if len(ignoreStrings) > 1 {
// Wrap each pattern in parentheses and join with OR
patterns := make([]string, len(ignoreStrings))
for i, pattern := range ignoreStrings {
patterns[i] = "(" + pattern + ")"
}
ignoreStringsPattern = strings.Join(patterns, "|")
} else {
// Single pattern case
ignoreStringsPattern = ignoreStrings[0]
}
}

return New(
path,
ignore,
ignoreStringsPattern,
ignoreTests,
matchConstant,
numbers,
findDuplicates,
numberMin,
numberMax,
minLength,
minOccurrences,
excludeTypes,
)
}

// RunWithConfig is a convenience function that runs the analysis with a Config object
// directly supporting multiple ignore patterns.
func RunWithConfig(files []*ast.File, fset *token.FileSet, cfg *Config) ([]Issue, error) {
p := NewWithIgnorePatterns(
"",
"",
cfg.IgnoreStrings,
Expand Down Expand Up @@ -233,3 +273,10 @@ func Run(files []*ast.File, fset *token.FileSet, cfg *Config) ([]Issue, error) {
// Don't return the buffer to pool as the caller now owns it
return issueBuffer, nil
}

// Run analyzes the provided AST files for duplicated strings or numbers
// according to the provided configuration.
// It returns a slice of Issue objects containing the findings.
func Run(files []*ast.File, fset *token.FileSet, cfg *Config) ([]Issue, error) {
return RunWithConfig(files, fset, cfg)
}
2 changes: 1 addition & 1 deletion api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func example() {
config: &Config{
MinStringLength: 3,
MinOccurrences: 2,
IgnoreStrings: "test",
IgnoreStrings: []string{"test"},
},
expectedIssues: 1, // Only "another" should be reported
},
Expand Down
57 changes: 54 additions & 3 deletions cmd/goconst/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Examples:

var (
flagIgnore = flag.String("ignore", "", "ignore files matching the given regular expression")
flagIgnoreStrings = flag.String("ignore-strings", "", "ignore strings matching the given regular expression")
flagIgnoreStrings = flag.String("ignore-strings", "", "ignore strings matching the given regular expressions (comma separated)")
flagIgnoreTests = flag.Bool("ignore-tests", true, "exclude tests from the search")
flagMinOccurrences = flag.Int("min-occurrences", 2, "report from how many occurrences")
flagMinLength = flag.Int("min-length", 3, "only report strings with the minimum given length")
Expand Down Expand Up @@ -93,10 +93,17 @@ func main() {
// run analyzes a single path for repeated strings that could be constants.
// It returns true if any issues were found, and an error if the analysis failed.
func run(path string) (bool, error) {
gco := goconst.New(
// Parse ignore strings - handling comma-separated values
var ignoreStrings []string
if *flagIgnoreStrings != "" {
// Split by commas but handle escaping
ignoreStrings = parseCommaSeparatedValues(*flagIgnoreStrings)
}

gco := goconst.NewWithIgnorePatterns(
path,
*flagIgnore,
*flagIgnoreStrings,
ignoreStrings,
*flagIgnoreTests,
*flagMatchConstant,
*flagNumbers,
Expand All @@ -115,6 +122,50 @@ func run(path string) (bool, error) {
return printOutput(strs, consts, *flagOutput)
}

// parseCommaSeparatedValues splits a comma-separated string into a slice of strings,
// handling escaping of commas within values.
func parseCommaSeparatedValues(input string) []string {
if input == "" {
return nil
}

// Simple case - no escaping needed
if !strings.Contains(input, "\\,") {
return strings.Split(input, ",")
}

// Handle escaped commas
var result []string
var current strings.Builder
escaped := false

for _, char := range input {
if escaped {
if char == ',' {
current.WriteRune(',')
} else {
current.WriteRune('\\')
current.WriteRune(char)
}
escaped = false
} else if char == '\\' {
escaped = true
} else if char == ',' {
result = append(result, current.String())
current.Reset()
} else {
current.WriteRune(char)
}
}

// Don't forget the last value
if current.Len() > 0 {
result = append(result, current.String())
}

return result
}

// usage prints the usage documentation to the specified writer.
func usage(out io.Writer) {
if _, err := fmt.Fprint(out, usageDoc); err != nil {
Expand Down
55 changes: 54 additions & 1 deletion compat/compat_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func example() {

// Configure exactly as golangci-lint does
cfg := &goconstAPI.Config{
IgnoreStrings: "test-ignore",
IgnoreStrings: []string{"test-ignore"},
MatchWithConstants: true,
MinStringLength: 3,
MinOccurrences: 2,
Expand Down Expand Up @@ -97,4 +97,57 @@ func example() {
issue.Str, issue.MatchingConst, expected.matchingConst)
}
}
}

func TestMultipleIgnorePatternsIntegration(t *testing.T) {
const testCode = `package example

func example() {
// These should be ignored by different patterns
foo1 := "foobar"
foo2 := "foobar"

bar1 := "barbaz"
bar2 := "barbaz"

// These should be detected
test1 := "example"
test2 := "example"
}
`

// Parse the test code
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "example.go", testCode, 0)
if err != nil {
t.Fatalf("Failed to parse test code: %v", err)
}

// Configure with multiple ignore patterns
cfg := &goconstAPI.Config{
IgnoreStrings: []string{"foo.+", "bar.+"}, // Multiple patterns
MinStringLength: 3,
MinOccurrences: 2,
}

// Run the analysis
issues, err := goconstAPI.Run([]*ast.File{f}, fset, cfg)
if err != nil {
t.Fatalf("Run() error = %v", err)
}

// Verify that "foobar" and "barbaz" are ignored but "example" is found
if len(issues) != 1 {
t.Errorf("Expected 1 issue, got %d", len(issues))
for _, issue := range issues {
t.Logf("Found issue: %q with %d occurrences",
issue.Str, issue.OccurrencesCount)
}
return
}

// The only issue should be "example"
if issues[0].Str != "example" {
t.Errorf("Expected to find 'example', got %q", issues[0].Str)
}
}
26 changes: 25 additions & 1 deletion compat/compat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func TestGolangCICompatibility(t *testing.T) {
// See: https://github.com/golangci/golangci-lint/blob/main/pkg/golinters/goconst/goconst.go

cfg := goconstAPI.Config{
IgnoreStrings: "test",
IgnoreStrings: []string{"test"},
MatchWithConstants: true,
MinStringLength: 3,
MinOccurrences: 2,
Expand Down Expand Up @@ -51,4 +51,28 @@ func TestGolangCICompatibility(t *testing.T) {
_ = issue.OccurrencesCount
_ = issue.Str
_ = issue.MatchingConst
}

// TestMultipleIgnorePatterns verifies that multiple ignore patterns work correctly
func TestMultipleIgnorePatterns(t *testing.T) {
// Test configuration with multiple ignore patterns
cfg := goconstAPI.Config{
IgnoreStrings: []string{"foo.+", "bar.+", "test"},
MinStringLength: 3,
MinOccurrences: 2,
}

// Create a simple test file
fset := token.NewFileSet()

// We just want to verify that multiple patterns are accepted
_, err := goconstAPI.Run(nil, fset, &cfg)
if err != nil {
// We expect an error since we passed nil files
// but the important part is that multiple patterns are accepted
t.Log("Expected error from nil files:", err)
}

// This tests the construction and acceptance of the config
// Actual pattern matching is tested in integration tests
}
31 changes: 31 additions & 0 deletions scripts/test-golangci-compat.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ func example() {
// This should be ignored due to ignore-strings
skip := "test-ignore"
skip2 := "test-ignore"

// These should be ignored with the multiple pattern test
foo1 := "foo-prefix"
foo2 := "foo-prefix"

bar1 := "bar-prefix"
bar2 := "bar-prefix"
}
EOF

Expand Down Expand Up @@ -122,4 +129,28 @@ if ! grep -q '"constants":{"test-const":\[.*"Name":"ExistingConst"' "$TEST_DIR/o
exit 1
fi

# Test 5: Test with multiple ignore patterns (comma-separated)
echo "Test 5: Testing multiple ignore patterns..."
"$GOCONST_BIN" -ignore-strings "test-ignore,foo-prefix,bar-prefix" "$TEST_DIR/testpkg" > "$TEST_DIR/output5.txt"
if grep -q "test-ignore" "$TEST_DIR/output5.txt"; then
echo "Failed: Should NOT detect 'test-ignore' string"
cat "$TEST_DIR/output5.txt"
exit 1
fi
if grep -q "foo-prefix" "$TEST_DIR/output5.txt"; then
echo "Failed: Should NOT detect 'foo-prefix' string"
cat "$TEST_DIR/output5.txt"
exit 1
fi
if grep -q "bar-prefix" "$TEST_DIR/output5.txt"; then
echo "Failed: Should NOT detect 'bar-prefix' string"
cat "$TEST_DIR/output5.txt"
exit 1
fi
if ! grep -q "duplicate" "$TEST_DIR/output5.txt"; then
echo "Failed: Should detect 'duplicate' string"
cat "$TEST_DIR/output5.txt"
exit 1
fi

echo "All compatibility tests PASSED!"
Loading