Skip to content

Linter: Per-rule custom configuration options - #2858

Open
drjayvee wants to merge 6 commits into
marcoroth:mainfrom
drjayvee:linter-rule-options
Open

drjayvee wants to merge 6 commits into
marcoroth:mainfrom
drjayvee:linter-rule-options

Conversation

@drjayvee

Copy link
Copy Markdown
Contributor

Proposal / Proof of Concept

This PR implements the foundation for per-rule custom linter configuration options, as proposed in #1204. It's framed as a PoC to settle the mechanism before rolling options out to more rules — feedback on the approach is very welcome.

Once the foundation is agreed upon, follow-up PRs can address the related use cases: #163, #2041, #1836, #550, plus the erb-no-unused-expressions side-effect-method list (see #1586) and an allow-list for erb-no-output-in-attribute-position (see #1204 comments).

How it works

Option keys sit flat under the rule name, next to the built-in keys (enabled, severity, …), matching the issue's example and erb_lint's layout:

linter:
  rules:
    html-allowed-script-type:
      enabled: true
      allowedTypes:
        - text/javascript
        - application/json
      allowBlank: false

Rules declare their options with a type parameter, defaults, and an optional zod schema:

export class HTMLAllowedScriptTypeRule extends ParserRule<BaseAutofixContext, HTMLAllowedScriptTypeOptions> {
  static optionsSchema = z.object({
    allowedTypes: z.array(z.string()),
    allowBlank: z.boolean(),
  }).strict()

  get defaultOptions(): HTMLAllowedScriptTypeOptions {
    return { allowedTypes: ["text/javascript", "module", "importmap", "speculationrules", "application/ld+json"], allowBlank: true }
  }
}

Design decisions

  • Flat keys (not a nested options: object), per the issue and erb_lint precedent.
  • Reserved keys: enabled, severity, frameworks, autoCorrect, include, only, exclude, environments keep their built-in meaning and are never passed to rules. A dev-time test asserts no rule's defaultOptions collides with them.
  • Validation: when a rule declares static optionsSchema, user options are validated when the rule is instantiated. Invalid options are a hard error in the CLI (one line per error, exit 1) and a warning in the language server; either way the rule falls back to its defaults for that run, so a bad config never crashes a lint. Strict schemas mean a typo like allowdTypes is reported instead of silently ignored.
  • camelCase option keys, matching failLevel/indentWidth.
  • Array options are replaced, not concatenated with defaults.
  • Option keys under the all pseudo rule are ignored.
  • html-allowed-script-type is the pilot rule (it had hardcoded constants with a note referencing Linter: Support per-rule custom configuration options #1204); its defaults are unchanged, so behavior without user config is identical.

Implementation notes

  • @herb-tools/config: RuleConfigBaseSchema is now a z.looseObject (unknown keys pass validation), Config.getRuleOptions(ruleName) returns a rule's config minus the reserved keys, BUILT_IN_RULE_CONFIG_KEYS lives in the browser-safe /schema subpath.
  • @herb-tools/linter: ParserRule/LexerRule/SourceRule gained a second generic TOptions (after TAutofixContext, so existing rules compile unchanged), defaultOptions/options/resolveOptions/optionsErrors, and Linter.validateRuleOptions(config, rules). All rule-instantiation sites (lint loop, deferred rules, autofix paths) resolve options; registry constructor types are generic-agnostic so option-carrying rules can register.
  • Custom rules from .herb/rules/**/*.mjs get the same support via the base classes (schema optional); duck-typed rules without the base class are tolerated.
  • FullRuleConfig now lists built-in keys explicitly (an index signature on RuleConfig would collapse the previous Omit-based derivation).

Validation

  • @herb-tools/config and @herb-tools/linter suites green (~6,000 tests, including the browser vitest project), typecheck green.
  • New tests: option-key acceptance in the schema, reserved-key stripping, all ignoring options, merge precedence and array replacement, schema-failure fallback, validateRuleOptions error reporting (bad values + unknown keys), end-to-end linting with custom options, and pilot-rule behavior with custom allowedTypes/allowBlank.
  • Docs: new "Custom Rule Options" section in configuration.md and an "Options" section on the pilot rule's page (template for future rules).

Known limitations / follow-ups

  • Browser rules (BrowserRule, live-DOM environment) don't support options yet — only Parser/Lexer/Source rules. Easy to add the same base-class treatment if the approach is accepted.
  • Versioning/changelog entries intentionally left out until the approach is approved.

Closes #1204 (pending feedback on the approach).

drjayvee and others added 6 commits October 29, 2026 12:18
Co-authored-by: pi <noreply@pi.dev>
Agent-Model: openrouter/moonshotai/kimi-k3
Co-authored-by: pi <noreply@pi.dev>
Agent-Model: openrouter/moonshotai/kimi-k3
Co-authored-by: pi <noreply@pi.dev>
Agent-Model: openrouter/moonshotai/kimi-k3
Co-authored-by: pi <noreply@pi.dev>
Agent-Model: openrouter/moonshotai/kimi-k3
Co-authored-by: pi <noreply@pi.dev>
Agent-Model: openrouter/moonshotai/kimi-k3
Co-authored-by: pi <noreply@pi.dev>
Agent-Model: openrouter/moonshotai/kimi-k3
@github-actions github-actions Bot added documentation Improvements or additions to documentation linter @herb-tools/linter for HTML+ERB templates language-server @herb-tools/language-server and Language Server Protocol support typescript TypeScript source across the javascript/ packages linter-rule Individual linter rules and their documentation config Herb configuration file handling and config options labels Oct 29, 2026
@drjayvee drjayvee changed the title Linter: Per-rule custom configuration options (PoC for #1204) Linter: Per-rule custom configuration options Oct 29, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config Herb configuration file handling and config options documentation Improvements or additions to documentation language-server @herb-tools/language-server and Language Server Protocol support linter @herb-tools/linter for HTML+ERB templates linter-rule Individual linter rules and their documentation typescript TypeScript source across the javascript/ packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linter: Support per-rule custom configuration options

1 participant