name: new-rule description: Implement a new SonarJS rule from scratch. Use when creating a new rule, scaffolding rule files, or understanding the full rule implementation workflow. disable-model-invocation: true
Overview
New rules follow the pattern: RSPEC description → scaffold → implement → test → ruling.
Step 1: Scaffold the Rule
npm run new-rule
This interactive script generates in packages/jsts/src/rules/SXXXX/:
-
index.ts— rule export -
rule.ts— ESLint rule implementation (skeleton) -
cb.fixture.js— empty comment-based test fixture -
cb.test.js— test launcher
It also auto-generates (not tracked by git):
- Java check class
SXXXX.java - Updates
rules/rules.tsandrules/plugin-rules.ts - Updates
AllRules.java
Step 2: Configure the Java Check Class
In the generated Java class, verify:
-
@JavaScriptRuleand/or@TypeScriptRuleannotations match target languages - If rule has options, override
configurations()method (see/rule-optionsskill) - If rule targets test files, extend
TestFileCheckinstead ofCheck
Step 3: Implement the Rule
File Structure
| File | Purpose |
|---|---|
rule.ts |
ESLint rule implementation |
meta.ts |
Manual metadata: implementation, eslintId, schema, re-exports fields |
config.ts |
Option definitions with fields array (if rule has options) |
generated-meta.ts |
Auto-generated from RSPEC — do not edit |
Rule Template
import { generateMeta } from '../helpers/index.js';
import { meta } from './meta.js';
const messages = {
errorKey: 'Error message to display',
};
export const rule: Rule.RuleModule = {
meta: generateMeta(meta, { messages }),
create(context: Rule.RuleContext) {
return {
Identifier(node: estree.Identifier) {
if (/* violation detected */) {
context.report({ messageId: 'errorKey', node });
}
},
};
},
};
Be Conservative
Never report when uncertain. False positives are worse than missed detections.
const services = context.sourceCode.parserServices;
if (!isRequiredParserServices(services)) {
return; // No type info — don't report
}
When in doubt: skip.
Step 4: Check Shared Helpers
Before writing any utility code, check packages/jsts/src/rules/helpers/:
| File | Contains |
|---|---|
ast.ts |
isFunctionNode, isIdentifier, hasTypePredicateReturn, AST traversal |
module.ts |
isESModule, getImportDeclarations, getFullyQualifiedName |
package-jsons/dependencies.ts |
getDependencies, getReactVersion |
index.ts |
Re-exports all helpers — check here first |
If a new utility would benefit multiple rules, add it to the appropriate helper file.
Step 5: Generate Metadata
After setting up meta.ts and optionally config.ts:
npm run generate-meta
This creates/updates generated-meta.ts with defaultOptions, sonarKey, scope, languages.
Step 6: Write Tests
See /test-rule skill for full testing documentation.
Quick start — write cb.fixture.js:
someCleanCode(); // no issue raised
someFaultyCode(); // Noncompliant {{message}}
// ^^^^^^^^^^
Run:
npx tsx --test packages/jsts/src/rules/S1234/**/*.test.ts
Step 7: Run Ruling
See /ruling skill. Required before merging new or modified rules.
Rule Implementation Patterns
Wrapping an ESLint Rule (decorated)
// meta.ts
export const implementation = 'decorated';
export const eslintId = 'no-magic-numbers';
export const externalRules = [
{ externalPlugin: 'typescript-eslint', externalRule: 'no-magic-numbers' },
];
export * from './config.js';
Original Rule
// meta.ts
export const implementation = 'original';
export const eslintId = 'function-name';
export * from './config.js';
import type { JSONSchema4 } from '@typescript-eslint/utils/json-schema';
export const schema = {
type: 'array',
items: [{ type: 'object', properties: { format: { type: 'string' } } }],
} as const satisfies JSONSchema4;
RSPEC Tags
When creating the RSPEC PR:
- Tag
type-dependentif the rule uses TypeScript type information - Add
dependenciesfield if rule requires a specific import (e.g.,'react','jest') - Add
compatibleLanguages: ['js', 'ts']as appropriate
References
chat Comments (0)
Sign in to join the discussion and leave a comment.
Skill Details
Related Skills
Build your own?
Join 12,000+ developers contributing to the Claude ecosystem.
No comments yet. Be the first to share your thoughts!