Support defining a linter ruleset in a yaml file - #11851
Support defining a linter ruleset in a yaml file#11851Timothee Guerin (timotheeguerin) wants to merge 5 commits into
Conversation
Support referencing a standalone yaml ruleset from `linter.extends` with the `file:` prefix. Relative paths resolve against the file declaring them, ruleset files can extend other files and library rulesets, and circular references are reported. Fixes microsoft#3011
commit: |
|
All changed packages have been documented.
Show changes
|
|
You can try these changes here
|
There was a problem hiding this comment.
🟢 Approval recommended
The implementation is covered by targeted tests and includes docs/changeset; only minor test-description grammar nits remain.
Pull request overview
Adds support in @typespec/compiler for extending linter rulesets from standalone YAML files via linter.extends entries prefixed with file:, enabling repos to share/version rulesets without requiring library releases.
Changes:
- Introduces
file:ruleset resolution/loading (including nested and circular-reference detection) and updates types to allow file-based ruleset refs. - Updates config loading so
file:references intspconfig.yamlresolve relative to the declaring config (including when reached viaextends). - Adds documentation, tests, and a
.chronusfeature entry for the new capability.
File summaries
| File | Description |
|---|---|
| website/src/content/docs/docs/handbook/configuration/configuration.mdx | Documents file: ruleset extension and relative path behavior. |
| packages/compiler/test/core/linter.test.ts | Adds unit/integration coverage for loading and composing file-based rulesets. |
| packages/compiler/test/config/config.test.ts | Verifies config-loader resolution of file: ruleset refs relative to config files (including extended configs). |
| packages/compiler/src/index.ts | Exports new ruleset ref types from the public surface. |
| packages/compiler/src/core/types.ts | Adds RuleSetFileRef / RuleSetRef and updates LinterRuleSet.extends typing. |
| packages/compiler/src/core/program.ts | Passes projectRoot to linter ruleset extension for base-dir resolution. |
| packages/compiler/src/core/messages.ts | Adds circular-ruleset-file diagnostic. |
| packages/compiler/src/core/linter.ts | Implements file: handling, nested resolution, and circular file-stack detection. |
| packages/compiler/src/core/linter-ruleset-file.ts | New YAML ruleset file loader + schema validation. |
| packages/compiler/src/config/types.ts | Updates config types to accept RuleSetRef[] in linter.extends. |
| packages/compiler/src/config/config-loader.ts | Makes file: ruleset refs absolute at load-time so they remain resolvable across config extension boundaries. |
| .chronus/changes/linter-ruleset-file-2026-8-3-11-52-0.md | Records the feature change for @typespec/compiler with an example. |
Review details
Suppressed comments (2)
packages/compiler/test/core/linter.test.ts:910
- Test description should use "resolves" (third-person singular) for consistency/grammar.
it("resolve nested file reference relative to the ruleset file", async () => {
packages/compiler/test/core/linter.test.ts:927
- Grammar in the test description: use "emits" and "doesn't exist".
it("emit a diagnostic when the file doesn't exists", async () => {
- Files reviewed: 12/12 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Ruleset file diagnostics are currently attributed to <anonymous file> due to parseYaml being invoked with a raw string instead of a source carrying the actual file path.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
packages/compiler/src/core/linter-ruleset-file.ts:55
parseYaml(file)is given a raw string, soparseYamlcreates a SourceFile with path<anonymous file>, causing YAML/invalid-schema diagnostics from ruleset files to report the wrong filename (contradicting the intendedpath:line:colreporting). Set theYamlScript's file path to the actual ruleset path before returning diagnostics (or pass a SourceFile with the correct path).
packages/compiler/src/core/types.ts:2676- Missing space before the parenthesis in the doc comment makes it harder to read.
- Files reviewed: 12/12 changed files
- Comments generated: 0 new
- Review effort level: Lite
There was a problem hiding this comment.
🟢 Approval recommended
The implementation matches the stated behavior (resolution, composition, cycle handling) and is backed by comprehensive tests plus an appropriate feature changelog entry.
Review details
- Files reviewed: 12/12 changed files
- Comments generated: 0 new
- Review effort level: Lite
There was a problem hiding this comment.
🟢 Approval recommended
The implementation is well-covered by tests and includes docs/changeset updates, with only minor diagnostic-targeting polish suggested.
Review details
- Files reviewed: 12/12 changed files
- Comments generated: 1
- Review effort level: Lite
| createDiagnostic({ | ||
| code: "circular-ruleset-file", | ||
| format: { path: resolvedPath }, | ||
| target: NoTarget, | ||
| }), |
There was a problem hiding this comment.
🟢 Approval recommended
The implementation matches the stated resolution rules, includes cycle/schema/malformed-YAML handling, and is backed by targeted compiler/config tests plus documentation and a feature changeset.
Review details
- Files reviewed: 12/12 changed files
- Comments generated: 0 new
- Review effort level: Lite
Today the only way to share a set of linter rules is to ship them as a ruleset inside a library. That means a repo like
azure-rest-api-specscannot onboard or disable a rule without waiting for a new release of the package that owns it, and it cannot combine rules coming from several sources into one reusable list.This lets a ruleset live in a plain yaml file, referenced from
linter.extendswith afile:prefix.The file has the exact same shape as the
linterconfig, so ruleset files compose freely with each other and with library rulesets. Relative paths always resolve against the file that declares them — afile:intspconfig.yamlis relative to the config (including when that config was reached throughextends:), and afile:inside a ruleset is relative to that ruleset. Circular references report a diagnostic instead of hanging, and schema violations point at the offending line:This also opens the door to versioned rulesets: add a new file whenever a rule is introduced, and repos opt in by pointing at the newer one.
Fixes #3011
Known follow-up
The language server does not watch ruleset files yet, so editing one does not retrigger a recompile until the config or a source file changes.