--- name: lint-new description: 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. ## Step 1: Choose Your Archetype Read [references/rule-archetypes.md](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](references/style-collector-guide.md) | | Restrict JSX elements in specific props | JSX structural | [rule-archetypes.md](references/rule-archetypes.md) §Archetype 3 | | Detect patterns in static CSS text | Template text analysis | [rule-archetypes.md](references/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. ## Step 2: Check Shared Utilities 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. ## Step 3: Create Files 1. **Rule**: `static/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.ts` 2. **Test**: `static/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.spec.ts` ### Rule Template ```typescript import {ESLintUtils} from '@typescript-eslint/utils'; export const $RULE_NAME = ESLintUtils.RuleCreator.withoutDocs({ meta: { type: 'problem', docs: { description: '[Rule description]', }, fixable: 'code', // include if rule has autofix — see Autofix Guidance schema: [], messages: { forbidden: 'Error message shown to user', }, }, create(context) { return { // AST visitor methods — see your chosen archetype }; }, }); ``` If your rule needs configurable options, load [references/schema-patterns.md](references/schema-patterns.md). ### Test Template ```typescript 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 }, ], }); ``` Run tests: ```bash pnpm test-ci "static/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.spec.ts" ``` ## Autofix Guidance **Default stance: implement autofix** unless the transformation is ambiguous or could change runtime behavior. ### Safe autofix patterns - Import path rewrites (see `no-core-import.ts` as canonical example) - Adding/removing JSX attributes with known values - Wrapping expressions in a known component - Identifier renames with no shadowing risk ### Do NOT autofix when - Multiple valid fixes exist and the right choice requires human judgment - The fix requires type information not available from the AST alone - The transformation alters control flow or runtime behavior - The change spans multiple files ### Fixer API ```typescript 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 }, }); ``` When a rule is fixable, every invalid test case MUST include `output` showing the expected code after the fix. ## Step 4: Register the Rule ### 1. Rule Index Add to `static/eslint/eslintPluginScraps/src/rules/index.ts`: ```typescript import {$RULE_NAME} from './$ARGUMENTS'; export const rules = { // existing rules... $ARGUMENTS: $RULE_NAME, }; ``` ### 2. ESLint Config Add to `eslint.config.ts` inside the `name: 'plugin/@sentry/scraps'` block: ```typescript '@sentry/scraps/$ARGUMENTS': 'error', // or with options: '@sentry/scraps/$ARGUMENTS': ['error', { /* options */ }], ``` ### 3. Verify ```bash pnpm test-ci "static/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.spec.ts" ``` ## Extending an Existing Rule If modifying an existing rule rather than creating a new one: 1. Read the existing rule and its config files to understand the architecture 2. For **config-driven rules** (like `use-semantic-token`): changes often only require editing the config file (e.g., `src/config/tokenRules.ts`), not the rule logic 3. Watch for reverse-mapping side effects — adding a new category can change which category is _suggested_ for shared properties (last writer wins in `buildPropertyToRule`) 4. Update existing tests for any changed behavior, then add new test cases ## Naming Convention - **Rule name** (kebab-case): `my-rule-name` — verb-noun pattern (e.g., `no-token-import`, `use-semantic-token`) - **Export name** (camelCase): `myRuleName` - **File name**: matches rule name exactly (`my-rule-name.ts`, `my-rule-name.spec.ts`)