sveltejs/add-eslint-rule
Walks through the full workflow of adding a new ESLint rule to eslint-plugin-svelte — scaffolding via `pnpm run new`, implementing the rule, writing fixture-based tests, updating generated artifacts via `pnpm run update`, and adding a changeset. Use this when the user asks to add, implement, or create a new lint rule in this repository.
npx skills add https://github.com/sveltejs/eslint-plugin-svelte --skill add-eslint-rule
This skill describes the end-to-end workflow for adding a new rule to eslint-plugin-svelte. It covers the repository's automation scripts so you don't reinvent any of them.
Use this skill whenever the user asks to:
.svelte files and report / autofix it.Do not use it for fixing or modifying an existing rule (just edit the rule file directly), or for general refactors of plugin internals.
eslint-plugin-svelte/).packages/eslint-plugin-svelte/ unless noted.pnpm install already done.Before writing any code, pin down:
svelte MCP server's get-documentation if available).conditions: [{ svelteVersions: ['5'] }]. Rules that only apply pre-5 use ['3/4']. Search existing rules for examples: grep -rn "svelteVersions" packages/eslint-plugin-svelte/src/rules/.hasSuggestions: true and provide a suggest array. Look at no-at-debug-tags.ts (suggestion-style) and prefer-derived-over-derived-by.ts (autofix-style) for templates.meta.docs.category: 'Best Practices', 'Stylistic Issues', 'Security Vulnerability', 'Possible Errors', 'System', 'Extension Rules', 'SvelteKit'.pnpm run newFrom packages/eslint-plugin-svelte/:
pnpm run new <rule-id>
This script (tools/new-rule.ts) creates four artifacts:
src/rules/<rule-id>.ts — rule skeleton.tests/src/rules/<rule-id>.ts — RuleTester runner that loads fixtures.docs/rules/<rule-id>.md (at the repo root docs/) — documentation skeleton.tests/fixtures/rules/<rule-id>/{valid,invalid}/ — fixture directories.Never hand-author these — always go through pnpm run new so the scaffolding matches the repository's expected layout.
Edit src/rules/<rule-id>.ts:
meta.docs.description, category, recommended.conditions (see step 1) if the rule is version-sensitive.type: 'problem' | 'suggestion' | 'layout'.fixable: 'code' or hasSuggestions: true as appropriate.messages: {} with real message IDs.create(context) returning visitors. For Svelte-specific AST nodes (e.g. SvelteConstTag, SvelteDeclarationTag, SvelteIfBlock, SvelteMustacheTag), use string visitor keys — they are already typed via src/types-for-node.ts.Conventions to follow:
context.sourceCode (not the deprecated context.getSourceCode()).fixer.removeRange / fixer.replaceTextRange with explicit offsets over textual heuristics where possible.The test runner (tests/utils/utils.ts → loadTestCases) discovers fixtures automatically. Conventions:
tests/fixtures/rules/<rule-id>/{valid,invalid}/ and must end with -input.svelte (or -input.svelte.ts, -input.ts, etc. — anything matching *-input.*).-errors.yaml and -output.svelte are generated automatically the first time you run tests. Delete those files and rerun to regenerate when the rule changes.pnpm run test:update-fixtures -- -g <rule-id> (or set UPDATE_FIXTURES=1).<basename>-config.json (e.g. case01-config.json) for options/parser settings. To apply config to all fixtures in a directory, use _config.json.<basename>-requirements.json with { "svelte": ">=5.55.9" } etc. Fixtures whose requirements don't match the currently installed deps are skipped silently.{"only": true} in a config file to focus a single fixture during dev — remove before committing.Make sure to cover at least:
Always run tests scoped to the rule to keep feedback fast:
pnpm run mocha "tests/src/**/*.ts" --reporter dot --timeout 60000 -- -g <rule-id>
To regenerate fixtures while running:
UPDATE_FIXTURES=1 pnpm run mocha "tests/src/**/*.ts" --reporter dot --timeout 60000 -- -g <rule-id>
Inspect the generated *-errors.yaml and *-output.svelte — they encode the rule's behavior contract. Don't blindly accept them; verify they match intent.
After per-rule tests pass, run the full suite once before committing:
pnpm run test
pnpm run updateFrom packages/eslint-plugin-svelte/:
pnpm run update
This sequences eight generators (see tools/update.ts):
update-rules — regenerates src/utils/rules.ts (the registry array). Required for the test runner to find the new rule via plugin.rules[<rule-id>]. Without this, tests fail with Cannot read properties of undefined (reading 'meta').update-rulesets — refreshes config presets (recommended, etc.) based on meta.docs.recommended.update-docs — regenerates header blocks in each rule's doc file.update-readme — refreshes the rules table in the repo root README.md.update-docs-rules-index — refreshes docs/rules.md.update-types-for-node — refreshes src/types-for-node.ts (node visitor types).update-meta — refreshes src/meta.ts (version metadata).update-rule-types — regenerates src/rule-types.ts (typed rule option entries).If pnpm run update errors partway through (e.g. on a Prettier/PostCSS parse error in some unrelated doc), check git diff to confirm the early generators (which include update-rules) ran. As long as src/utils/rules.ts lists your new rule, the test suite will work; you can run failing generators individually via pnpm run ts ./tools/<generator>.ts.
After update, revert unrelated drift in fixtures or other rules — git checkout -- <path> for any file that was touched but isn't part of your rule. Common drift sources: tests/fixtures/rules/max-lines-per-block/**/*-errors.yaml formatting churn.
pnpm run lint:es src/rules/<rule-id>.ts
Auto-fix with pnpm run lint-fix. The plugin lints itself, so any style violations in your new rule will block CI.
From the repo root:
pnpm exec changeset
…or hand-write .changeset/<slug>.md:
---
'eslint-plugin-svelte': minor
---
feat: add `svelte/<rule-id>` rule
Use minor for new rules, patch for bug fixes / docs-only changes, major for breaking changes. Look at recent merged changesets via git log -- .changeset/*.md for tone.
Stage exactly the files you intend to ship — at minimum:
.changeset/<slug>.mdREADME.md (auto-edited)docs/rules.md (auto-edited)docs/rules/<rule-id>.mdpackages/eslint-plugin-svelte/src/rule-types.ts (auto-edited)packages/eslint-plugin-svelte/src/rules/<rule-id>.tspackages/eslint-plugin-svelte/src/utils/rules.ts (auto-edited)packages/eslint-plugin-svelte/tests/fixtures/rules/<rule-id>/packages/eslint-plugin-svelte/tests/src/rules/<rule-id>.tsSkip any fixture/doc that wasn't intentionally changed (revert with git checkout).
pnpm run new <rule-id> scaffolded.src/rules/<rule-id>.ts with meta, messages, conditions (if version-sensitive), and visitors.pnpm run mocha "tests/src/**/*.ts" -- -g <rule-id> passes.*-errors.yaml and *-output.svelte reviewed.pnpm run update completed; src/utils/rules.ts lists the rule.pnpm run test passes.pnpm run lint:es clean.docs/rules/<rule-id>.md filled in (description, examples, "Further Reading").minor bump.Cannot read properties of undefined (reading 'meta') during tests. You forgot pnpm run update (or at least pnpm run ts ./tools/update-rules.ts). The rule isn't in the registry yet.*-requirements.json is excluding the fixture under the current dep versions. Confirm with pnpm list svelte etc.conditions in meta. Add a svelteVersions array.range and getText plus a precise regex, rather than full-text replacement of the node.pnpm run update blew up mid-way. Check git status; the earlier generators usually completed. Don't block the whole rule on an unrelated Prettier error in a doc you didn't touch.pnpm run lint-fix or check the offending file via the --check output.Smallest viable rule, mirroring the repo's conventions:
import { createRule } from '../utils/index.js';
export default createRule('<rule-id>', {
meta: {
docs: {
description: '...',
category: 'Best Practices',
recommended: false
},
fixable: 'code',
schema: [],
messages: {
unexpected: '...'
},
type: 'suggestion',
conditions: [
{
svelteVersions: ['5']
}
]
},
create(context) {
return {
SvelteConstTag(node) {
context.report({
node,
messageId: 'unexpected',
fix(fixer) {
// minimal, range-based fix
return null;
}
});
}
};
}
});
Take sveltejs/add-eslint-rule from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm.
Without those the skill loads but fails at the first command.