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
56 changes: 56 additions & 0 deletions cmd/wfctl/modernize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,62 @@ modules:
}
}

func TestCamelCaseConfigCheck_SchemaDefinedKeysNotFlagged(t *testing.T) {
// openapi module uses snake_case keys (spec_file, register_routes, swagger_ui,
// max_body_bytes) defined in its own schema — these must NOT be flagged.
input := `
modules:
- name: api-docs
type: openapi
config:
spec_file: ./specs/api.yaml
register_routes: false
swagger_ui:
enabled: true
path: /docs
max_body_bytes: 1048576
`
rule := findRule("camelcase-config")
if rule == nil {
t.Fatal("camelcase-config rule not found")
}
doc := parseTestYAML(t, input)
findings := rule.Check(doc, []byte(input))
if len(findings) != 0 {
t.Errorf("expected no findings for schema-defined snake_case keys, got %d: %v", len(findings), findings)
}
}

func TestCamelCaseConfigCheck_UnknownModuleTypeStillFlagged(t *testing.T) {
// For a module type not in the schema registry, snake_case keys should still be flagged.
input := `
modules:
- name: my-thing
type: custom.unknown
config:
snake_key: value
`
rule := findRule("camelcase-config")
if rule == nil {
t.Fatal("camelcase-config rule not found")
}
doc := parseTestYAML(t, input)
findings := rule.Check(doc, []byte(input))
if len(findings) == 0 {
t.Fatal("expected findings for snake_case config keys on unknown module type")
}
found := false
for _, f := range findings {
if strings.Contains(f.Message, `"snake_key"`) {
found = true
break
}
}
if !found {
t.Errorf("expected a finding mentioning key \"snake_key\", got: %v", findings)
}
}

func TestModernizeAllRulesRegistered(t *testing.T) {
rules := modernize.AllRules()
expectedIDs := []string{
Expand Down
34 changes: 33 additions & 1 deletion modernize/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"regexp"
"strings"

"github.com/GoCodeAlone/workflow/schema"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -625,6 +626,28 @@ func requestParseConfigRule() Rule {
var snakeCaseKeyRegex = regexp.MustCompile(`^[a-z]+(_[a-z0-9]+)+$`)

func camelCaseConfigRule() Rule {
// Build a registry of schema-defined config key names per module type so
// that keys which are intentionally snake_case (e.g. openapi's spec_file,
// register_routes, swagger_ui) are never flagged as anti-patterns.
schemaRegistry := schema.NewModuleSchemaRegistry()

// Cache the schema key sets by module type to avoid rebuilding them for
// every module instance encountered during a Check run.
schemaKeyCache := make(map[string]map[string]bool)
schemaKeysFor := func(moduleType string) map[string]bool {
if cached, ok := schemaKeyCache[moduleType]; ok {
return cached
}
keys := make(map[string]bool)
if ms := schemaRegistry.Get(moduleType); ms != nil {
for i := range ms.ConfigFields {
keys[ms.ConfigFields[i].Key] = true
}
}
schemaKeyCache[moduleType] = keys
return keys
}

return Rule{
ID: "camelcase-config",
Description: "Detect snake_case config field names (engine requires camelCase)",
Expand All @@ -641,9 +664,18 @@ func camelCaseConfigRule() Rule {
if nameNode != nil {
modName = nameNode.Value
}

// Resolve the set of officially defined config keys for this module
// type so that schema-declared snake_case keys are not flagged.
var schemaKeys map[string]bool
typeNode := findMapValue(mod, "type")
if typeNode != nil && typeNode.Value != "" {
schemaKeys = schemaKeysFor(typeNode.Value)
}

for i := 0; i+1 < len(cfg.Content); i += 2 {
key := cfg.Content[i]
if key.Kind == yaml.ScalarNode && snakeCaseKeyRegex.MatchString(key.Value) {
if key.Kind == yaml.ScalarNode && snakeCaseKeyRegex.MatchString(key.Value) && !schemaKeys[key.Value] {
findings = append(findings, Finding{
RuleID: "camelcase-config",
Line: key.Line,
Expand Down
Loading