npx skills add ...
npx skills add getsentry/sentry --skill lint-new
npx skills add getsentry/sentry --skill lint-new
Create a new ESLint rule with tests for eslintPluginScraps. Use when asked to "create a lint rule", "add an eslint rule", "scaffold a rule", "write a new scraps rule", or "new design system lint rule". Covers rule creation, test authoring, registration, and autofix implementation.
Create a new ESLint rule named $ARGUMENTS in the eslintPluginScraps plugin.
Read references/rule-archetypes.md and pick the archetype that matches your rule's intent:
| You want to... | Archetype | Reference to load |
|---|---|---|
| Rewrite import paths | Import rewrite | Inline — simple pattern |
| Validate token/value usage per CSS property | Property validation | style-collector-guide.md |
| Restrict JSX elements in specific props | JSX structural | rule-archetypes.md §Archetype 3 |
| Detect patterns in static CSS text | Template text analysis | rule-archetypes.md §Archetype 4 |
Read the relevant reference before writing code. The archetypes document which AST visitors to use, which shared utilities apply, and which patterns are NOT appropriate for each approach.
Before writing AST traversal logic, check static/eslint/eslintPluginScraps/src/ast/ for reusable code:
| Utility | Location | Use for |
|---|---|---|
getStyledCallInfo | src/ast/utils/styled.ts | Classifying styled/css calls as element, component, or css |
createQuasiScanner | src/ast/scanner/index.ts | Scanning static CSS text in template literals (Archetype 4) |
createImportTracker | src/ast/tracker/imports.ts | Resolving where a local name was imported from |
createStyleCollector | src/ast/extractor/index.ts | Collecting CSS-in-JS dynamic value declarations (NOT static text) |
shouldAnalyze | src/ast/extractor/index.ts | Fast pre-scan to skip files without Emotion usage |
normalizePropertyName | src/ast/utils/normalizePropertyName.ts | Normalizing CSS property names |
decomposeValue | src/ast/extractor/value-decomposer.ts | Breaking complex expressions into all possible values |
| Theme tracker | src/ast/tracker/theme.ts | Tracking useTheme() and callback theme bindings |
If another rule already solves a similar problem, extract shared logic into src/ast/utils/ and reuse it.
static/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.tsstatic/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.spec.tsIf your rule needs configurable options, load references/schema-patterns.md.
Run tests:
Default stance: implement autofix unless the transformation is ambiguous or could change runtime behavior.
no-core-import.ts as canonical example)When a rule is fixable, every invalid test case MUST include output showing the expected code after the fix.
Add to static/eslint/eslintPluginScraps/src/rules/index.ts:
Add to eslint.config.ts inside the name: 'plugin/@sentry/scraps' block:
If modifying an existing rule rather than creating a new one:
use-semantic-token): changes often only require editing the config file (e.g., src/config/tokenRules.ts), not the rule logicbuildPropertyToRule)my-rule-name — verb-noun pattern (e.g., no-token-import, use-semantic-token)myRuleNamemy-rule-name.ts, my-rule-name.spec.ts)import {RuleTester} from '@typescript-eslint/rule-tester';
import {$RULE_NAME} from './$ARGUMENTS';
const ruleTester = new RuleTester();
ruleTester.run('$ARGUMENTS', $RULE_NAME, {
valid: [
{
code: '// valid code',
filename: '/project/src/file.tsx',
},
],
invalid: [
{
code: '// invalid code',
filename: '/project/src/file.tsx',
errors: [{messageId: 'forbidden'}],
output: '// expected output after autofix', // REQUIRED for fixable rules
},
],
});pnpm test-ci "static/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.spec.ts"pnpm test-ci "static/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.spec.ts"context.report({
node,
messageId: 'forbidden',
fix(fixer) {
return fixer.replaceText(node, newText);
// Also: fixer.replaceTextRange([start, end], text)
// fixer.insertTextBefore(node, text)
// fixer.insertTextAfter(node, text)
// fixer.remove(node)
// Return single fix or array of fixes
},
});import {$RULE_NAME} from './$ARGUMENTS';
export const rules = {
// existing rules...
$ARGUMENTS: $RULE_NAME,
};'@sentry/scraps/$ARGUMENTS': 'error',
// or with options:
'@sentry/scraps/$ARGUMENTS': ['error', { /* options */ }],