
Lint New
- 22 installs
- 44.5k repo stars
- Updated August 5, 2026
- getsentry/sentry
lint-new is an agent skill that 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.
About
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 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
- **Rule**: `static/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.ts`
- **Test**: `static/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.spec.ts`
- Import path rewrites (see `no-core-import.ts` as canonical example)
- Adding/removing JSX attributes with known values
- Wrapping expressions in a known component
Lint New by the numbers
- 22 all-time installs (skills.sh)
- Ranked #1,468 of 1,879 Marketing & SEO skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
lint-new capabilities & compatibility
- Capabilities
- **rule**: `static/eslint/eslintpluginscraps/src/ · **test**: `static/eslint/eslintpluginscraps/src/ · import path rewrites (see `no core import.ts` as · adding/removing jsx attributes with known values · wrapping expressions in a known component
- Use cases
- documentation
What lint-new says it does
--- 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.
npx skills add https://github.com/getsentry/sentry --skill lint-newAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 22 |
|---|---|
| repo stars | ★ 44.5k |
| Last updated | August 5, 2026 |
| Repository | getsentry/sentry ↗ |
What problem does lint-new solve for developers using this skill?
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"
Who is it for?
Developers who need lint-new patterns described in the cached skill documentation.
Skip if: Skip when docs are empty or the task is outside the skill's documented scope.
When should I use this skill?
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"
What you get
Actionable workflows and conventions from SKILL.md for lint-new.
Files
Create a new ESLint rule named $ARGUMENTS in the eslintPluginScraps plugin.
Step 1: Choose Your Archetype
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.
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
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.
Test Template
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:
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.tsas 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
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:
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:
'@sentry/scraps/$ARGUMENTS': 'error',
// or with options:
'@sentry/scraps/$ARGUMENTS': ['error', { /* options */ }],3. Verify
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)
Rule Archetypes
Load this reference when deciding which AST approach to use for a new rule.
Decision Table
| You want to... | Archetype | Key patterns | Example rule |
|---|---|---|---|
| Rewrite import paths | Import rewrite | ImportDeclaration visitor, fixer.replaceText(node.source, ...) | no-core-import |
| Validate which CSS properties a token/value is used with | Property validation | createStyleCollector + Program:exit deferred validation | use-semantic-token |
| Restrict which JSX elements appear in specific props | JSX structural constraint | Import tracking + recursive JSX tree walk + config schema | restrict-jsx-slot-children |
| Detect patterns in static CSS text (selectors, raw values) | Template text analysis | TaggedTemplateExpression → walk quasi.quasis for static text | no-dom-coupling (PR #109906) |
Archetype 1: Import Rewrite
When: Rule checks import sources and rewrites them.
Pattern: Single ImportDeclaration visitor. Autofix replaces the source string.
create(context) {
return {
ImportDeclaration(node) {
const importPath = node.source.value;
if (typeof importPath === 'string' && importPath.startsWith(FORBIDDEN)) {
context.report({
node,
messageId: '...',
fix(fixer) {
return fixer.replaceText(node.source, `'${newPath}'`);
},
});
}
},
};
}Autofix: Almost always safe. The fix just changes a string literal.
Edge cases: Type-only imports (import type), mixed named imports, re-exports — all handled automatically since you're only replacing the source path string.
Archetype 2: Property Validation (Style Collector)
When: Rule validates which CSS properties a dynamic value (theme token, variable) is used with.
Key insight: Uses a two-phase approach — collect during traversal, validate after.
1. createStyleCollector(context) returns {collector, visitors} — spread visitors into your return object 2. In Program:exit, iterate collector.getAll() to validate each StyleDeclaration 3. Call collector.clear() at the end for cleanup
create(context) {
if (!shouldAnalyze(context)) return {}; // Fast bailout
const {collector, visitors} = createStyleCollector(context);
return {
...visitors,
'Program:exit'() {
for (const decl of collector.getAll()) {
// decl.property.name — the CSS property (already normalized)
// decl.values — array of {rawNode, tokenInfo: {tokenPath, node}}
validateDeclaration(decl);
}
collector.clear();
},
};
}Important: The collector handles _interpolated expressions_ (${...} parts) in template literals. It does NOT analyze static CSS text in quasis. If you need to detect patterns in the static text itself (like raw hex colors or nested selectors), use Archetype 4 instead.
Config-driven rules: If validation rules vary by category, put the mapping in src/config/ and load it from there. See tokenRules.ts for the pattern. This lets you add new categories without changing the rule logic.
`shouldAnalyze`: Always use this as a fast pre-scan bailout. It checks for Emotion import/usage patterns via regex and skips files that clearly don't use styled-components.
Archetype 3: JSX Structural Constraint
When: Rule restricts which JSX elements can appear in specific props or slots.
Pattern: Use createImportTracker from src/ast/tracker/imports.ts for import resolution, plus a JSXAttribute visitor for tree walking:
1. createImportTracker() — merge its visitors, then use resolve(localName) or findLocalNames(source, name) to check imports 2. JSXAttribute — when a configured prop is found, recursively walk the JSX tree checking each element against the allowed set
create(context) {
const importTracker = createImportTracker();
return {
...importTracker.visitors,
JSXAttribute(node) {
// Use importTracker.resolve(displayName) to check where an element comes from
// Use importTracker.findLocalNames(source, name) to find local aliases
},
};
}Key patterns:
- Handle import aliasing:
import {Foo as Bar}meansBaris the local name —importTracker.resolve('Bar')returns{source, imported: 'Foo'} - Handle member expressions:
MenuComponents.Alertmust match${localName}.${member} - Recurse through: direct JSX children, ternaries, logical expressions (
&&,||,??),JSXExpressionContainer,JSXFragment, arrow function expression bodies - Skip
React.Fragment/<Fragment>(transparent wrappers) - Stop recursion on disallowed elements (report and return)
Schema: Uses a complex options schema with nested arrays. See restrict-jsx-slot-children for the full pattern.
Autofix: Generally NOT safe — replacing JSX elements requires understanding the component API, which is beyond what the AST alone can tell you.
Archetype 4: Template Text Analysis
When: Rule detects patterns in the static CSS text of template literals (not in interpolated expressions).
Pattern: Use createQuasiScanner from src/ast/scanner/index.ts — it handles shouldAnalyze bailout, tag detection via getStyledCallInfo, and quasi iteration for you:
import {createQuasiScanner} from '../ast/scanner/index';
create(context) {
return createQuasiScanner(context, (cssText, quasi, info) => {
// cssText: the static CSS text of this quasi segment
// quasi: the TemplateElement node (use for error reporting)
// info: { kind: 'element' | 'component' | 'css', name?: string }
for (const match of cssText.matchAll(MY_PATTERN)) {
context.report({ node: quasi, messageId: '...' });
}
});
}The scanner calls your analyze callback for every quasi element in every styled/css tagged template in the file. It automatically skips files without Emotion usage.
When to use this vs Archetype 2: If you're looking for patterns in the CSS _text itself_ (raw colors, nested selectors, property names), use createQuasiScanner. If you're validating _what values are passed_ to CSS properties via interpolation (${theme.tokens.X}), use createStyleCollector.
Tag detection utility: getStyledCallInfo(node) from src/ast/utils/styled.ts classifies any TaggedTemplateExpression or CallExpression as {kind: 'element', name}, {kind: 'component', name}, {kind: 'css'}, or null. Handles styled.div, styled('div'), styled(Component), styled(Component).attrs(...), and css patterns. The scanner uses this internally, but you can also use it directly in custom visitors.
Rule Options Schema Patterns
Load this reference when your rule needs configurable options.
No Options (Default)
Most rules need no options. Use empty schema:
ESLintUtils.RuleCreator.withoutDocs<never[], MessageIds>({
meta: { schema: [] },
defaultOptions: [],
create(context) { ... },
});Note: Use never[] not [] for the options type parameter — [] violates @typescript-eslint/no-restricted-types.
Simple Options: String Array
For rules with a configurable set of enabled features:
interface Options {
enabledCategories?: string[];
}
ESLintUtils.RuleCreator.withoutDocs<[Options], MessageIds>({
meta: {
schema: [{
type: 'object',
properties: {
enabledCategories: {
type: 'array',
items: { type: 'string' },
},
},
additionalProperties: false,
}],
},
defaultOptions: [{}],
create(context, [options = {}]) {
const enabled = options.enabledCategories
? new Set(options.enabledCategories)
: null; // null = all enabled
...
},
});In eslint.config.ts: '@sentry/scraps/rule-name': ['error', {enabledCategories: ['background', 'border']}]
Complex Options: Nested Config
For rules with rich, structured configuration (like slot restrictions):
interface Options {
slots: Array<{
propNames: [string, ...string[]];
allowed: Array<{
source: string;
names: [string, ...string[]];
}>;
componentNames?: string[];
}>;
}Schema mirrors the TypeScript interface:
schema: [{
type: 'object',
properties: {
slots: {
type: 'array',
items: {
type: 'object',
properties: {
propNames: { type: 'array', minItems: 1, items: { type: 'string' } },
allowed: {
type: 'array', minItems: 1,
items: {
type: 'object',
properties: {
source: { type: 'string' },
names: { type: 'array', minItems: 1, items: { type: 'string' } },
},
required: ['source', 'names'],
additionalProperties: false,
},
},
componentNames: { type: 'array', items: { type: 'string' } },
},
required: ['propNames', 'allowed'],
additionalProperties: false,
},
},
},
required: ['slots'],
additionalProperties: false,
}],Config-Driven Rules
For rules where the validation logic is generic but the data varies by category, extract the configuration into a separate file in src/config/:
src/config/tokenRules.ts ← Category definitions, property mappings
src/rules/use-semantic-token.ts ← Generic validation logicThis pattern means adding a new category requires only a config change — no rule logic changes. The rule imports and iterates the config.
Reverse Mapping Pattern
When your config maps categories → allowed properties, you often also need the reverse (property → expected category) for error messages:
function buildPropertyToRule(rules: TokenRule[]) {
const result = new Map<string, string>();
for (const rule of rules) {
for (const property of rule.allowedProperties) {
result.set(property, rule.name); // Last writer wins
}
}
return result;
}Ordering matters: When multiple categories share a property (e.g., box-shadow in both focus and shadow), the last category in the array wins the reverse mapping. This affects which category is _suggested_ in error messages. Keep this in mind when adding new categories.
Style Collector Guide
Load this reference when your rule needs to analyze CSS-in-JS style declarations — specifically the _dynamic values_ passed via interpolation.
When to Use
Use createStyleCollector when your rule needs to:
- Validate which CSS properties a theme token is used with
- Check that interpolated values match expected types or categories
- Analyze the relationship between CSS properties and their dynamic values
Do NOT use it when you need to:
- Detect patterns in static CSS text — use
createQuasiScannerfromsrc/ast/scanner/index.tsinstead - Check import paths (use
ImportDeclarationvisitor) - Restrict JSX element usage (use JSX tree walking +
createImportTracker)
Architecture
File: src/ast/extractor/index.ts
createStyleCollector(context)
├── createThemeTracker() ← tracks useTheme() / callback theme bindings
├── createStyledExtractor() ← handles styled.div`...` and styled(X)`...`
├── createCssPropExtractor() ← handles css={} and css`...` props
└── createStylePropExtractor() ← handles style={{}} prop
Returns: { collector, visitors, themeTracker }What the Collector Captures
Each StyleDeclaration in collector.getAll():
interface StyleDeclaration {
property: {
name: string; // Normalized CSS property (e.g., 'background-color')
node: TSESTree.Node; // AST node of the property name
};
values: Array<{
rawNode: TSESTree.Node;
tokenInfo?: {
tokenPath: string; // e.g., 'content.primary', 'border.secondary'
node: TSESTree.Node; // AST node of the token access
};
}>;
}What It Does NOT Capture
- Static text in template literal quasis (the non-interpolated parts)
- CSS property names that appear only in static text without dynamic values
- Comments, whitespace, or formatting
Two-Phase Pattern
The collector uses deferred validation — it collects during traversal and you validate in `Program:exit`:
create(context) {
if (!shouldAnalyze(context)) return {};
const {collector, visitors} = createStyleCollector(context);
return {
...visitors, // Spread the collector's visitors (handles all extraction)
'Program:exit'() {
for (const decl of collector.getAll()) {
// Your validation logic here
}
collector.clear(); // REQUIRED: cleanup for next file
},
};
}Why deferred? Because a single styled block may have properties and values spread across multiple AST nodes (template quasis + expressions). The collector aggregates them all, then you validate the complete picture.
shouldAnalyze: Fast Pre-Scan
import {shouldAnalyze} from '../ast/extractor/index';
if (!shouldAnalyze(context)) return {};This regex-based pre-scan checks for Emotion imports/usage patterns. Returns false for files that clearly don't use styled-components. Always use it as the first line in create() for any rule that uses the style collector or analyzes Emotion patterns.
Common Pitfall: Collector vs Static Text
The #1 mistake is using createStyleCollector when your rule needs to analyze static CSS text. Example:
const Box = styled.div`
color: #ff0000; ← This is static text in a quasi — collector won't see it
background: ${p => p.theme.tokens.background.primary}; ← This IS captured
`;If your rule detects raw hex colors, nested selectors, or other patterns in the _text itself_, use createQuasiScanner from src/ast/scanner/index.ts instead. See the "Template Text Analysis" archetype in rule-archetypes.md.
Related skills
FAQ
What does lint-new do?
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 creati
When should I use 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 creati
Is lint-new safe to install?
Review the Security Audits panel on this page before installing in production.