Skip to content

fix(lint): respect lint-config.yaml in mxcli lint CLI and options in Starlark rules#700

Merged
ako merged 1 commit into
mendixlabs:mainfrom
Andries-Smit:fix/lint-use-yaml-config
Jun 26, 2026
Merged

fix(lint): respect lint-config.yaml in mxcli lint CLI and options in Starlark rules#700
ako merged 1 commit into
mendixlabs:mainfrom
Andries-Smit:fix/lint-use-yaml-config

Conversation

@Andries-Smit

Copy link
Copy Markdown
Contributor

Summary

  • mxcli lint CLI command was silently ignoring lint-config.yamlFindConfigFile/LoadConfig/ApplyConfig were only wired into the executor (REPL/MDL script) path, not the CLI subcommand
  • Rule options from YAML were stored in RuleConfig but never delivered to rules (no mechanism existed to pass them)
  • ModuleRoles() and RoleMappings() iterators in LintContext did not apply IsExcluded(), so Starlark rules using module_roles() reported violations from excluded modules

Changes

cmd/mxcli/cmd_lint.go

  • Wire FindConfigFileLoadConfigApplyConfig after Starlark rules are loaded; excludeModules from config merges with --exclude flag; --rules allowlist still wins last

mdl/linter/linter.go

  • Add Configurable interface; Run calls Configure(options) on any rule implementing it before Check

mdl/linter/context.go

  • Apply IsExcluded() in ModuleRoles() and RoleMappings() iterators

mdl/linter/starlark.go

  • StarlarkRule implements Configurable (stores options map)
  • Add get_option(key, default=None) builtin so .star rules can read per-rule options from lint-config.yaml at check time

mdl/linter/rules/domain_size.go

  • DomainModelSizeRule implements Configurable with max_entities option

Tests

  • New mdl/linter/config_test.go: LoadConfig YAML parsing (excludeModules, enabled, severity, options), ApplyConfig, FindConfigFile
  • Extended linter_test.go: Configurable interface called before Check, not called when options empty
  • Extended domain_size_test.go: Configure with int/float64/unknown key, raises threshold end-to-end

Test plan

  • go test ./mdl/linter/... passes (56 tests)
  • mxcli lint -p app.mpr with lint-config.yaml in project root respects excludeModules, disabled rules, severity overrides
  • Starlark rule calling get_option("key", default) reads value from options: block in YAML
  • --exclude flag still works independently when no config file present

Yaml example

excludeModules:
  - Administration
  - System
rules:
  MPR001:
    enabled: false
  SEC014:
    severity: error
    options:
      min_length: 12

🤖 Generated with Claude Code

https://claude.ai/code/session_01R1yYeYn9rosf8UdAimdatt

@github-actions

Copy link
Copy Markdown

AI Code Review

What Looks Good

The PR effectively addresses the three main issues described:

  1. Config file integration: Wires FindConfigFileLoadConfigApplyConfig into the CLI lint command path (was only in REPL/MDL script path)
  2. Rule options delivery: Adds Configurable interface so rules receive options from lint-config.yaml before Check is called
  3. Excluded modules handling: Fixes ModuleRoles() and RoleMappings() iterators to apply IsExcluded()

The implementation follows established patterns:

  • Uses existing linter.FindConfigFile/LoadConfig/ApplyConfig functions
  • Adds minimal, focused interfaces (Configurable)
  • Properly handles type conversions (int/float64) for YAML numbers
  • Maintains correct precedence: config → CLI flags → --rules allowlist
  • Includes comprehensive tests covering YAML parsing, config application, and edge cases

Test additions are thorough:

  • New config_test.go validates YAML loading for all config sections
  • Extended linter_test.go verifies Configurable interface behavior
  • Enhanced domain_size_test.go tests end-to-end configuration

Starlark integration is clean:

  • get_option(key, default) builtin allows rules to access per-rule options
  • Proper error handling and type conversion in goValueToStarlark

Recommendation

Approve - The PR is scoped to a single concern (fixing lint config handling), implements the solution correctly with appropriate tests, and maintains consistency with existing code patterns. No MDL syntax changes are involved, so full-stack consistency requirements don't apply. All checklist items are satisfied for this type of change.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

…Starlark rules

The `mxcli lint` CLI command was ignoring the lint-config.yaml file entirely —
config loading only existed in the executor (REPL/MDL script) path, not the
CLI subcommand. Additionally, rule `options` from the YAML were never passed
to rules even when loaded.

Fixes:
- Wire FindConfigFile/LoadConfig/ApplyConfig into cmd/mxcli/cmd_lint.go so
  excludeModules, rule enabled flags, severity overrides, and options are all
  respected when running `mxcli lint`
- Add Configurable interface (linter.go): rules implementing Configure(options)
  receive their options map before Check is called
- Fix ModuleRoles() and RoleMappings() iterators in context.go to apply
  IsExcluded() so Starlark rules using module_roles()/role_mappings() respect
  the excludeModules list
- Add get_option(key, default) builtin to Starlark rules so .star files can
  read per-rule options from lint-config.yaml at check time
- Implement Configurable on DomainModelSizeRule with max_entities option
- Add config_test.go covering LoadConfig YAML parsing, ApplyConfig, and
  FindConfigFile; extend linter_test.go and domain_size_test.go for new paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R1yYeYn9rosf8UdAimdatt
@Andries-Smit Andries-Smit force-pushed the fix/lint-use-yaml-config branch from 1dd7ba3 to 506fb1c Compare June 26, 2026 09:43
@github-actions

Copy link
Copy Markdown

AI Code Review

Critical Issues

  • None found

Moderate Issues

  • None found

Minor Issues

  • None found

What Looks Good

  • The PR correctly fixes all three issues mentioned in the summary:
    1. Wires config loading into the CLI lint command path (FindConfigFileLoadConfigApplyConfig)
    2. Adds Configurable interface to deliver rule options from YAML to rules
    3. Fixes ModuleRoles() and RoleMappings() iterators to respect IsExcluded()
  • Implementation follows existing patterns:
    • Optional Configurable interface (rules don't need to implement it)
    • Proper merging of config excludeModules with --exclude flag
    • --rules allowlist still takes precedence as intended
    • Starlark rules get get_option() builtin to access config options
    • Existing rule (DomainModelSizeRule) properly implements Configurable
  • Tests are comprehensive:
    • New config_test.go covers YAML loading, merging, defaults
    • Extended linter_test.go verifies Configurable interface behavior
    • Extended domain_size_test.go tests config options with int/float64/unknown keys
    • All tests appear to be proper unit tests (no time.Sleep for synchronization)
  • Error handling is appropriate (warning message on config load failure)
  • Changes are focused and atomic - single concern (lint config respect)
  • No MDL syntax changes, so full-stack consistency checklist doesn't apply
  • No security issues apparent

Recommendation

Approve - The PR thoroughly addresses the reported issues with minimal, focused changes that follow project patterns and include appropriate test coverage. All applicable checklist items are satisfied.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@ako

ako commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

A genuine bug-fix PR (not just a new builtin), and I verified it end-to-end.

Fixes three real bugs:

  1. lint-config.yaml ignored by the CLI — Find/Load/ApplyConfig were only wired into the executor/REPL path; now wired into cmd_lint.go.
  2. Rule options never delivered — new optional Configurable interface; Run() calls Configure(options) before Check, only when options are non-empty.
  3. ModuleRoles()/RoleMappings() ignored exclusions — both now apply ctx.IsExcluded(), consistent with the other iterators.

Plus a get_option(key, default) Starlark builtin and DomainModelSizeRule as the first Configurable rule.

Verified:

  • Properly rebased onto current main (merge-base == main head), CLEAN.
  • No spurious warning on the no-config path (the main regression risk): FindConfigFile returns "", LoadConfig("") hits os.IsNotExist and returns the default config with nil error. Confirmed by a targeted test and a real run.
  • Comprehensive unit tests (config parsing, ApplyConfig, Configurable called/not-called, Configure int/float64/unknown-key, find-config in root and .claude/). Full ./mdl/linter/... suite green.
  • The untested CLI glue, exercised manually on testdata/expr-checker/minimal.mpr (baseline 40 warnings):
    • excludeModules: [System] → 40 → 1
    • SEC001: {enabled: false} → 40 → 2
    • malformed YAML → warns + continues with defaults ✓
    • no config → 40, silent

Notes (one recurring, rest minor):

  1. Docs gap (should-add): the new get_option() builtin and the lint-config.yaml options:/CLI capability aren't in the user skill .claude/skills/mendix/write-lint-rules.md (only in docs/11-proposals). Same gap as feat(lint): expose XPath/expression AST to Starlark rules #688/feat(lint): expose scheduled_events() query function in Starlark rules #692 — add get_option() to the builtins table and document the config feature.
  2. Malformed config = warn + silently lint with defaults. A typo'd config emits only a stderr warning while the lint proceeds and its exit code reflects default config, not the user's intent — in CI this can mask a config that never applied. Consider a hard failure for a present-but-invalid config (distinct from absent).
  3. goValueToStarlark is scalar-only — list/map option values from YAML silently become None in get_option(). Fine for current scalar options; worth documenting the limitation.
  4. The CLI glue has no automated test (the underlying pieces are well-covered); I validated it manually.

Recommendation: approve — the fix is correct, well-tested, and I confirmed the end-to-end behavior including the no-config regression risk. The docs addition is the one thing genuinely worth doing before/with merge; the rest are minor.

@ako ako merged commit d66404a into mendixlabs:main Jun 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants