|
| 1 | +- Repo: eslint/yaml |
| 2 | +- Start Date: 2026-02-23 |
| 3 | +- RFC PR: (leave empty, to be filled when submitting) |
| 4 | +- Authors: André Ahlert (@andreahlert) |
| 5 | + |
| 6 | +# Official @eslint/yaml Language Plugin |
| 7 | + |
| 8 | +## Summary |
| 9 | + |
| 10 | +This RFC proposes creating an official ESLint language plugin for YAML, published as `@eslint/yaml`, under the ESLint organization. The plugin would allow users to lint YAML files (e.g. GitHub Actions workflows, Kubernetes manifests, Docker Compose, CI configs) using ESLint's flat config and the language plugin API established in [RFC #99 (2022-languages)](https://github.com/eslint/rfcs/blob/main/designs/2022-languages/README.md). It follows the same patterns as other official language plugins: `@eslint/json`, `@eslint/markdown`, `@eslint/css`, and `@eslint/html`. |
| 11 | + |
| 12 | +## Motivation |
| 13 | + |
| 14 | +- **Demand:** YAML was one of the most requested languages alongside CSS (which now has official `@eslint/css` support). Many developers work with YAML daily for GitHub Actions, Kubernetes, Docker Compose, Ansible, Helm charts, and other tooling. |
| 15 | +- **Consistency:** ESLint has expanded into JSON, Markdown, CSS, and HTML with official plugins. An official YAML plugin completes the story for common config/data formats and establishes a reference implementation maintained by the team. |
| 16 | +- **Existing ecosystem:** Community plugins such as [eslint-plugin-yml](https://github.com/ota-meshi/eslint-plugin-yml) (by @ota-meshi, v3 supports the language plugin API) and [eslint-yaml](https://github.com/43081j/eslint-yaml) already exist and work well. The TSC has expressed interest in a team-maintained plugin (see [Discussion #20482](https://github.com/eslint/eslint/discussions/20482)) and @nzakas has welcomed an RFC to kickstart this effort. An official plugin would provide long-term maintenance, alignment with ESLint's release cycle, and a single recommended solution for YAML linting. |
| 17 | +- **Reference implementation:** A team-maintained plugin serves as a reference for the language plugin API applied to YAML and encourages best practices for parsing, rules, and editor integration. |
| 18 | + |
| 19 | +## Detailed Design |
| 20 | + |
| 21 | +### Scope |
| 22 | + |
| 23 | +- **Package name:** `@eslint/yaml` |
| 24 | +- **Repository:** New repository at [github.com/eslint/yaml](https://github.com/eslint/yaml), following the pattern of `eslint/json`, `eslint/markdown`, `eslint/css`. |
| 25 | +- **Module format:** ESM-only, in line with the TSC decision that new packages should be ESM-only (see [2026-01-08 TSC meeting](https://github.com/eslint/tsc-meetings/blob/main/notes/2026/2026-01-08.md)). |
| 26 | +- **Compatibility:** ESLint v9.15.0+ (flat config only). No eslintrc support. |
| 27 | +- **TypeScript types:** Built-in type definitions shipped with the package from v1, following the direction the team has taken with `espree`, `eslint-scope`, and other packages. |
| 28 | + |
| 29 | +### Plugin export shape |
| 30 | + |
| 31 | +The plugin follows the same structure as `@eslint/json`: |
| 32 | + |
| 33 | +```js |
| 34 | +const plugin = { |
| 35 | + meta: { |
| 36 | + name: "@eslint/yaml", |
| 37 | + version: "0.1.0", // x-release-please-version |
| 38 | + }, |
| 39 | + languages: { |
| 40 | + yaml: new YAMLLanguage({ mode: "yaml" }), // YAML 1.2 (default) |
| 41 | + yaml11: new YAMLLanguage({ mode: "yaml11" }), // YAML 1.1 compatibility |
| 42 | + }, |
| 43 | + rules, |
| 44 | + configs: { |
| 45 | + recommended: { |
| 46 | + name: "@eslint/yaml/recommended", |
| 47 | + plugins: {}, |
| 48 | + rules: recommendedRules, |
| 49 | + }, |
| 50 | + }, |
| 51 | +}; |
| 52 | +``` |
| 53 | + |
| 54 | +Key points: |
| 55 | +- **`meta`** with `name` and `version` (aligned with all official plugins). |
| 56 | +- **Two language modes:** |
| 57 | + - `yaml/yaml` — YAML 1.2 (the current spec; strict mode, `yes`/`no` are strings, not booleans). |
| 58 | + - `yaml/yaml11` — YAML 1.1 compatibility (legacy; `yes`/`no`/`on`/`off` are booleans, octal `0777` syntax, etc.). Many real-world files (older Docker Compose, Ansible) still rely on 1.1 behavior. |
| 59 | +- **`configs.recommended`** — a recommended config that enables sensible default rules (e.g. `no-duplicate-keys`), so users can adopt with minimal configuration. |
| 60 | + |
| 61 | +### Language plugin contract |
| 62 | + |
| 63 | +The language definition implements the interface from [RFC #99](https://github.com/eslint/rfcs/blob/main/designs/2022-languages/README.md#language-definition-object): |
| 64 | + |
| 65 | +| Property | Value | Notes | |
| 66 | +|---|---|---| |
| 67 | +| `fileType` | `"text"` | YAML is a text format | |
| 68 | +| `lineStart` | `1` | 1-based line numbers | |
| 69 | +| `columnStart` | `1` | 1-based columns | |
| 70 | +| `nodeTypeKey` | `"type"` | Node type property in the AST | |
| 71 | +| `visitorKeys` | `{ YAMLStream: [...], YAMLDocument: [...], YAMLMapping: [...], ... }` | Traversal keys for each node type | |
| 72 | +| `validateOptions(options)` | Validates `languageOptions` | e.g. reject unknown keys | |
| 73 | +| `matchesSelectorClass(className, node, ancestry)` | Optional selector shortcuts | e.g. `"value"` matches all value nodes | |
| 74 | +| `parse(file, context)` | Parse YAML text → `ParseResult` | Never throws; parse errors returned in result | |
| 75 | +| `createSourceCode(file, input, context)` | Build `SourceCode` from parse result | Used by ESLint for reporting and fixes | |
| 76 | + |
| 77 | +### AST node types |
| 78 | + |
| 79 | +The AST should represent the YAML structure with at least the following node types: |
| 80 | + |
| 81 | +``` |
| 82 | +YAMLStream — root node representing the entire file (contains one or more documents) |
| 83 | + YAMLDocument — a single document (files may contain multiple documents separated by ---) |
| 84 | + YAMLMapping — a mapping (object / key-value collection) |
| 85 | + YAMLPair — a single key-value pair within a mapping |
| 86 | + YAMLSequence — a sequence (array / list) |
| 87 | + YAMLScalar — a scalar value (string, number, boolean, null) |
| 88 | + YAMLAlias — an alias reference (*anchor); the anchor name is stored as a property |
| 89 | + YAMLComment — a comment (# ...) |
| 90 | + YAMLDirective — a YAML directive (%YAML 1.2, %TAG, etc.) |
| 91 | +``` |
| 92 | + |
| 93 | +Note: anchors (`&name`) are **not** separate node types. An anchor is a property on the node it is attached to (e.g. `YAMLScalar.anchor = "name"`), following how YAML parsers represent them. Only alias references (`*name`) are distinct `YAMLAlias` nodes. |
| 94 | + |
| 95 | +The `YAMLStream` root node is necessary because YAML allows multiple documents in a single file. Even for single-document files, the stream node provides a consistent root for traversal. |
| 96 | + |
| 97 | +Each node must include: |
| 98 | +- `type` — the node type string (e.g. `"YAMLMapping"`) |
| 99 | +- `range` — `[startOffset, endOffset]` in the source text |
| 100 | +- `loc` — `{ start: { line, column }, end: { line, column } }` for reporting |
| 101 | + |
| 102 | +The exact AST shape will depend on the chosen parser, but should be documented in the plugin's README and type definitions. |
| 103 | + |
| 104 | +### Parser choice |
| 105 | + |
| 106 | +The RFC does not prescribe a specific parser. Candidates include: |
| 107 | + |
| 108 | +| Parser | YAML version | Comments | Location info | Notes | |
| 109 | +|---|---|---|---|---| |
| 110 | +| [yaml (eemeli/yaml)](https://github.com/eemeli/yaml) | 1.1 + 1.2 | Yes (CST) | Yes | Mature, actively maintained, CST → AST | |
| 111 | +| [yaml-ast-parser](https://github.com/mulesoft-labs/yaml-ast-parser) | 1.2 | No | Yes | Fork of js-yaml with AST | |
| 112 | +| Approach from eslint-plugin-yml | 1.2 | Yes | Yes | Uses yaml-eslint-parser | |
| 113 | + |
| 114 | +The parser must: |
| 115 | +1. Produce a traversable AST with location info for every node. |
| 116 | +2. Support comments (required for disable directives and style rules). |
| 117 | +3. Not throw on parse errors (return them as part of `ParseResult`). |
| 118 | +4. Be actively maintained and have a permissive license (MIT or Apache 2.0). |
| 119 | + |
| 120 | +The final choice will be made during implementation and documented in the repo. |
| 121 | + |
| 122 | +### Configuration comments and disable directives |
| 123 | + |
| 124 | +YAML supports line comments with `#`. The plugin will support ESLint configuration comments and disable directives: |
| 125 | + |
| 126 | +```yaml |
| 127 | +# eslint yaml/no-duplicate-keys: "error" |
| 128 | + |
| 129 | +name: CI |
| 130 | +on: push |
| 131 | + |
| 132 | +jobs: |
| 133 | + build: |
| 134 | + runs-on: ubuntu-latest |
| 135 | + steps: |
| 136 | + - uses: actions/checkout@v4 |
| 137 | + - run: echo "hello" # eslint-disable-line yaml/some-rule -- reason here |
| 138 | +``` |
| 139 | +
|
| 140 | +Both `eslint-disable`, `eslint-disable-next-line`, `eslint-enable`, and inline rule configuration will be supported, following the same patterns as `@eslint/json` in JSONC/JSON5 mode. |
| 141 | + |
| 142 | +### Rules (initial set) |
| 143 | + |
| 144 | +Following the direction of the project — which is [moving away from formatting rules](https://github.com/eslint/eslint/issues/20097) and toward correctness / best-practice rules — the initial set focuses on catching real problems: |
| 145 | + |
| 146 | +**Recommended (enabled in `configs.recommended`):** |
| 147 | + |
| 148 | +| Rule | Description | Fixable | |
| 149 | +|---|---|---| |
| 150 | +| `no-duplicate-keys` | Disallow duplicate keys in mappings | No | |
| 151 | +| `no-empty-keys` | Disallow empty string keys | No | |
| 152 | +| `no-unsafe-values` | Disallow unquoted values that would be interpreted differently between YAML 1.1 and 1.2 (e.g. `yes`, `no`, `on`, `off` are booleans in 1.1 but plain strings in 1.2) | No | |
| 153 | + |
| 154 | +**Additional (not in recommended, opt-in):** |
| 155 | + |
| 156 | +| Rule | Description | Fixable | |
| 157 | +|---|---|---| |
| 158 | +| `sort-keys` | Require mapping keys to be sorted | Yes | |
| 159 | +| `no-duplicate-anchors` | Disallow duplicate anchor names in a document | No | |
| 160 | +| `require-document-start` | Require explicit `---` document start marker | Yes | |
| 161 | + |
| 162 | +Formatting-related rules (indentation, quoting style, trailing newlines) are explicitly **out of scope** for v1, consistent with the project's direction toward delegating formatting to tools like Prettier. |
| 163 | + |
| 164 | +Additional correctness rules can be proposed and added in subsequent minor versions. |
| 165 | + |
| 166 | +### Configuration example |
| 167 | + |
| 168 | +```js |
| 169 | +// eslint.config.js |
| 170 | +import { defineConfig } from "eslint/config"; |
| 171 | +import yaml from "@eslint/yaml"; |
| 172 | +
|
| 173 | +export default defineConfig([ |
| 174 | + { |
| 175 | + plugins: { |
| 176 | + yaml, |
| 177 | + }, |
| 178 | + }, |
| 179 | +
|
| 180 | + // Lint YAML 1.2 files |
| 181 | + { |
| 182 | + files: ["**/*.yml", "**/*.yaml"], |
| 183 | + language: "yaml/yaml", |
| 184 | + extends: ["yaml/recommended"], |
| 185 | + }, |
| 186 | +
|
| 187 | + // Lint legacy YAML 1.1 files (optional, e.g. older Ansible playbooks) |
| 188 | + { |
| 189 | + files: ["**/ansible/**/*.yml"], |
| 190 | + language: "yaml/yaml11", |
| 191 | + extends: ["yaml/recommended"], |
| 192 | + }, |
| 193 | +]); |
| 194 | +``` |
| 195 | + |
| 196 | +### Collaboration with existing plugins |
| 197 | + |
| 198 | +- **eslint-plugin-yml (by @ota-meshi):** v3 already works as a language plugin. The official plugin may take inspiration from its rule set and AST design. Any code in the new repo will be written from scratch under the eslint org; collaboration and ideas from the community are welcome. |
| 199 | +- **eslint-yaml (by @43081j):** Another community plugin for reference. |
| 200 | +- No obligation to fork or replace community plugins. The goal is to provide an official, team-maintained option. Community plugins remain valid choices. |
| 201 | + |
| 202 | +### Repository and release process |
| 203 | + |
| 204 | +- New repo at `github.com/eslint/yaml`. |
| 205 | +- Release process aligned with [ESLint's release process](https://eslint.org/docs/latest/contribute/release-process): semver, release-please, provenance-signed publishes. |
| 206 | +- CI: lint, test, type-check, and integration tests against ESLint main. |
| 207 | + |
| 208 | +## Documentation |
| 209 | + |
| 210 | +- **README:** Installation, flat config usage with `defineConfig`, list of rules with recommended markers, language modes, editor setup. |
| 211 | +- **Rule docs:** One doc per rule with description, options, examples (valid/invalid), and fixable status. |
| 212 | +- **Editor setup:** Instructions for VS Code (`eslint.validate` with `yaml`) and JetBrains (ESLint scope including `*.yml`, `*.yaml`). |
| 213 | +- **eslint.org:** Link from the "language plugins" or "integrations" section to `@eslint/yaml` once published. |
| 214 | + |
| 215 | +## Drawbacks |
| 216 | + |
| 217 | +- **Maintenance burden:** Another official package for the team to maintain. The TSC has limited bandwidth and has recently implemented [spending reductions](https://github.com/eslint/tsc-meetings/blob/main/notes/2026/2026-01-08.md) due to budget constraints. **Mitigation:** The RFC author volunteers as primary champion for implementation and ongoing maintenance, starting with a minimal rule set. The goal is to reduce, not increase, the TSC's workload. |
| 218 | +- **Overlap with community plugins:** Some users are happy with eslint-plugin-yml; an official plugin could be perceived as fragmenting the ecosystem. **Mitigation:** Position as the recommended official option; document a clear migration path; avoid breaking existing community plugins. |
| 219 | +- **YAML complexity:** YAML has many edge cases (anchors, aliases, multi-document, custom tags, merge keys). **Mitigation:** Scope v1 to the most common use cases (GitHub Actions, K8s manifests, Docker Compose) and clearly document what is and isn't supported. Advanced features can be added incrementally. |
| 220 | + |
| 221 | +## Backwards Compatibility Analysis |
| 222 | + |
| 223 | +- **ESLint core:** No changes to `eslint` or `@eslint/js`. This is a new, standalone package. |
| 224 | +- **For users of community YAML plugins:** Fully opt-in. Users of eslint-plugin-yml or eslint-yaml can continue using them. Migration to `@eslint/yaml` would be config-based; a migration guide with rule name mapping (if applicable) will be provided. |
| 225 | + |
| 226 | +## Alternatives |
| 227 | + |
| 228 | +1. **Rely only on community plugins.** Community plugins already cover YAML linting well. However, the TSC has expressed interest in a team-maintained plugin to serve as a reference implementation aligned with ESLint's release cycle ([Discussion #20482](https://github.com/eslint/eslint/discussions/20482), [2024-09-05 TSC meeting](https://github.com/eslint/tsc-meetings/blob/main/notes/2024/2024-09-05.md#do-we-want-to-create-official-language-plugins-for-yaml-and-css)). |
| 229 | +2. **Adopt or fork eslint-plugin-yml under the eslint org.** This would require agreement with @ota-meshi and a potentially different maintenance model. This RFC proposes a new repo; collaboration with existing plugins is left open and encouraged. |
| 230 | +3. **Only document how to use community YAML plugins on eslint.org.** This satisfies discoverability but not the goal of having an official, team-maintained reference implementation. |
| 231 | + |
| 232 | +## Open Questions |
| 233 | + |
| 234 | +1. **Language modes:** Is `yaml/yaml` (1.2) + `yaml/yaml11` (1.1) the right split, or should there be a single mode with a version option in `languageOptions`? |
| 235 | +2. **Parser choice:** Which YAML parser best balances maintainability, comment support, location accuracy, and license compatibility? (See parser comparison table above.) |
| 236 | +3. **Initial rule set:** Are the proposed rules the right starting point? Should any be added or removed for v1? |
| 237 | +4. **Collaboration model:** Should the team formally invite @ota-meshi or other community maintainers as collaborators on the new repo? |
| 238 | +5. **Anchor / alias handling:** How deeply should v1 resolve anchors and aliases in the AST? Full resolution enables more rules but adds complexity. |
| 239 | + |
| 240 | +## Help Needed |
| 241 | + |
| 242 | +- TSC feedback on language modes, rule scope, and parser choice. |
| 243 | +- Volunteers for code review and ongoing maintenance (the RFC author will champion implementation). |
| 244 | +- Input from users of eslint-plugin-yml and eslint-yaml on must-have rules and config patterns. |
| 245 | + |
| 246 | +## Frequently Asked Questions |
| 247 | + |
| 248 | +**Q: Do I have to migrate from eslint-plugin-yml?** |
| 249 | +A: No. The official plugin is an additional option. You can migrate when and if you want. A migration guide will be provided. |
| 250 | + |
| 251 | +**Q: Will this support eslintrc?** |
| 252 | +A: No. Like other official language plugins, this targets flat config only (ESLint v9.15.0+). |
| 253 | + |
| 254 | +**Q: Can I use this with Prettier or other formatters?** |
| 255 | +A: Yes. This plugin focuses on correctness and best-practice rules, not formatting. You can use Prettier or another tool for YAML formatting alongside `@eslint/yaml` for linting. |
| 256 | + |
| 257 | +**Q: What about YAML 1.1 vs 1.2?** |
| 258 | +A: The plugin will provide two language modes — `yaml/yaml` for YAML 1.2 (default, current spec) and `yaml/yaml11` for YAML 1.1 compatibility. This follows the same pattern as `@eslint/json` providing `json/json`, `json/jsonc`, and `json/json5`. |
| 259 | + |
| 260 | +**Q: How does this relate to eslint-plugin-yml and eslint-yaml?** |
| 261 | +A: This plugin uses the official language plugins API, which is the recommended way to support non-JavaScript languages in ESLint. Community plugins may use different approaches (parsers, processors) or offer a different rule set. Both can coexist; users choose what fits their needs. |
| 262 | + |
| 263 | +## Related Discussions |
| 264 | + |
| 265 | +- [Interest in an official @eslint/yaml plugin? (Discussion #20482)](https://github.com/eslint/eslint/discussions/20482) — Community interest and TSC welcome for an RFC. |
| 266 | +- [RFC #99 — ESLint Language Plugins (2022-languages)](https://github.com/eslint/rfcs/blob/main/designs/2022-languages/README.md) — Language plugin API that this plugin will implement. |
| 267 | +- [eslint-plugin-yml](https://github.com/ota-meshi/eslint-plugin-yml) — Community YAML plugin (v3 supports the language plugin API). |
| 268 | +- [eslint-yaml](https://github.com/43081j/eslint-yaml) — Another community YAML plugin. |
| 269 | +- [2024-09-05 TSC meeting notes](https://github.com/eslint/tsc-meetings/blob/main/notes/2024/2024-09-05.md#do-we-want-to-create-official-language-plugins-for-yaml-and-css) — TSC discussed creating official language plugins for YAML and CSS; CSS was prioritized first. |
| 270 | +- [2026-01-08 TSC meeting notes](https://github.com/eslint/tsc-meetings/blob/main/notes/2026/2026-01-08.md) — ESM-only decision for new packages; budget and spending context. |
0 commit comments