--- id: wiki-2026-0508-custom-eslint-rules-dev title: Custom ESLint Rules Development category: 10_Wiki/Topics status: verified canonical_id: self aliases: [custom ESLint, AST rule, eslint plugin, ast visitor, autofix, semgrep alternative] duplicate_of: none source_trust_level: A confidence_score: 0.9 verification_status: applied tags: [eslint, ast, static-analysis, code-quality, custom-rule, plugin, autofix, semgrep] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: JavaScript / TypeScript framework: ESLint / Semgrep / typescript-eslint --- # Custom ESLint Rules ## 매 한 줄 > **"매 team-specific rule 의 자동 enforcement"**. 매 AST visitor pattern + 매 fixer API. 매 architectural rule (no domain → infra), 매 anti-pattern (deprecated API), 매 convention (naming). 매 modern alternative: Semgrep (multi-language). ## 매 핵심 ### 매 ESLint architecture 1. **Parser** (espree, @typescript-eslint/parser): 매 source → 매 AST. 2. **Rule**: 매 visit AST node + 매 report. 3. **Fixer**: 매 auto-correct. 4. **Config**: 매 enable / disable. ### 매 AST node type - **Program**: 매 root. - **VariableDeclaration**, **FunctionDeclaration**. - **CallExpression**, **MemberExpression**. - **ArrowFunctionExpression**, **ObjectExpression**, **ArrayExpression**. - **TSInterfaceDeclaration**, **TSTypeAliasDeclaration** (TS). ### 매 use case 1. **Architectural**: domain → infrastructure 의 forbid. 2. **Convention**: naming, file structure. 3. **Deprecation**: 매 old API 의 detect. 4. **Security**: dangerous pattern (eval, dangerouslySetInnerHTML). 5. **Performance**: anti-pattern (e.g., unnecessary re-render). 6. **Domain-specific**: business rule. ### 매 selector syntax (Esquery) - `Identifier[name="foo"]`: 매 specific name. - `CallExpression > MemberExpression`: 매 chain. - `:not(...)`: 매 exclusion. - `[callee.name="alert"]`: 매 attribute match. ### 매 modern alternative: Semgrep - 매 multi-language (Python, Go, Java, Rust, ...). - 매 pattern-based (more readable). - 매 ESLint custom 보다 매 fast write. ### 매 LLM-aided rule generation - 매 plain English → 매 ESLint rule code. - 매 example-based. ## 💻 패턴 ### Basic ESLint rule ```js // eslint-rule-no-console-log.js module.exports = { meta: { type: 'suggestion', docs: { description: 'Disallow console.log calls', recommended: true, }, fixable: 'code', schema: [], messages: { noConsoleLog: 'Avoid console.log in production. Use logger.', }, }, create(context) { return { CallExpression(node) { if ( node.callee.type === 'MemberExpression' && node.callee.object.name === 'console' && node.callee.property.name === 'log' ) { context.report({ node, messageId: 'noConsoleLog', fix(fixer) { // 매 auto-fix: 매 logger.info 의 replace return fixer.replaceText(node.callee, 'logger.info'); }, }); } }, }; }, }; ``` ### Plugin structure ``` my-eslint-plugin/ ├── package.json ├── lib/ │ ├── index.js # 매 plugin entry │ └── rules/ │ ├── no-console-log.js │ └── enforce-domain-isolation.js └── tests/ └── no-console-log.test.js ``` ```js // lib/index.js module.exports = { rules: { 'no-console-log': require('./rules/no-console-log'), 'enforce-domain-isolation': require('./rules/enforce-domain-isolation'), }, configs: { recommended: { plugins: ['@my-org'], rules: { '@my-org/no-console-log': 'error', '@my-org/enforce-domain-isolation': 'error', }, }, }, }; ``` ### Architectural rule (Clean Architecture) ```js // 매 src/domain/* 의 매 src/infrastructure/* 의 import 의 forbid module.exports = { meta: { type: 'problem', messages: { noInfraInDomain: 'domain layer must not import from infrastructure', }, }, create(context) { return { ImportDeclaration(node) { const filename = context.getFilename(); if (!filename.includes('/domain/')) return; const importPath = node.source.value; if (importPath.includes('/infrastructure/')) { context.report({ node, messageId: 'noInfraInDomain', }); } }, }; }, }; ``` ### Test (RuleTester) ```js const { RuleTester } = require('eslint'); const rule = require('./no-console-log'); const tester = new RuleTester(); tester.run('no-console-log', rule, { valid: [ 'logger.info("hi");', 'console.error("err");', ], invalid: [ { code: 'console.log("hi");', output: 'logger.info("hi");', errors: [{ messageId: 'noConsoleLog' }], }, ], }); ``` ### TypeScript-aware rule ```ts import { TSESTree, ESLintUtils } from '@typescript-eslint/utils'; const createRule = ESLintUtils.RuleCreator(name => `https://docs.example.com/${name}`); export default createRule({ name: 'no-any-type', meta: { type: 'suggestion', docs: { description: 'Forbid `any` type', recommended: true }, schema: [], messages: { noAny: 'Avoid `any` — use `unknown` or specific type.' }, }, defaultOptions: [], create(context) { return { TSAnyKeyword(node) { context.report({ node, messageId: 'noAny' }); }, }; }, }); ``` ### Selector-based (esquery) ```js // 매 매 React.useState() 의 매 first arg 의 type check module.exports = { create(context) { return { 'CallExpression[callee.object.name="React"][callee.property.name="useState"]'(node) { const arg = node.arguments[0]; if (!arg) { context.report({ node, message: 'useState requires initial value' }); } }, }; }, }; ``` ### Semgrep alternative ```yaml # .semgrep/no-console-log.yml rules: - id: no-console-log pattern: console.log(...) message: 'Avoid console.log in production' severity: WARNING languages: [javascript, typescript] fix: logger.info(...) - id: domain-no-infra patterns: - pattern: import $X from "$Y" - metavariable-pattern: metavariable: $Y pattern: '*infrastructure*' paths: include: - 'src/domain/**' message: 'domain must not import infrastructure' severity: ERROR languages: [typescript] ``` ### LLM-generated rule ```python def generate_eslint_rule(description, examples): prompt = f"""Generate an ESLint custom rule. Description: {description} Bad examples: {format_examples(examples['bad'])} Good examples (allowed): {format_examples(examples['good'])} Output: complete eslint rule .js file with autofix where possible. Use selector syntax. Include test cases.""" return llm.generate(prompt) ``` ### Apply via flat config (ESLint 9+) ```js // eslint.config.js import myPlugin from '@my-org/eslint-plugin'; export default [ { plugins: { '@my-org': myPlugin }, rules: { '@my-org/no-console-log': 'error', '@my-org/enforce-domain-isolation': 'error', }, }, ]; ``` ### Performance: visitor reuse ```js // 매 ❌ Bad — 매 매 node 마다 fileName check create(context) { return { 'ImportDeclaration'(node) { const fn = context.getFilename(); if (!fn.includes('/domain/')) return; // ... }, }; } // 매 ✅ Better — 매 file-level cache create(context) { const filename = context.getFilename(); if (!filename.includes('/domain/')) return {}; // 매 visitor 의 skip entire return { 'ImportDeclaration'(node) { /* ... */ }, }; } ``` ## 🤔 결정 기준 | 상황 | Tool | |---|---| | Single language (JS/TS) | ESLint custom | | Multi-language | Semgrep | | Cross-cutting (security) | CodeQL / Semgrep | | Architectural | dependency-cruiser | | Quick + readable pattern | Semgrep | | AST manipulation needed | ESLint with autofix | | Type-aware | typescript-eslint | **기본값**: 매 single-lang JS/TS = ESLint. 매 multi-lang = Semgrep. ## 🔗 Graph - 부모: [[ESLint-Static-Analysis|Static-Analysis-Linting]] · [[Code-Quality]] - 변형: [[ESLint]] · [[Semgrep]] · [[CodeQL]] · [[dependency-cruiser]] - 응용: [[AST]] · [[Code_Smells]] · [[CI_CD 파이프라인 및 IDE 통합 보안]] · [[Architecture-Anti-Patterns]] - Adjacent: [[Abstract_Syntax_Tree]] · [[Clean-Code-Principles]] · [[Quality_Code_Review_Modern]] ## 🤖 LLM 활용 **언제**: 매 team-wide convention enforcement. 매 architectural rule. 매 AI-generated code 의 normalize. **언제 X**: 매 single-time check (just grep). 매 dynamic / runtime issue. ## ❌ 안티패턴 - **No test (RuleTester)**: 매 rule 의 buggy. - **No fixer**: 매 manual burden. - **Slow rule**: 매 매 file performance ↓. - **Over-broad selector**: 매 false positive. - **No documentation URL**: 매 dev 의 confusion. - **Custom rule for what built-in exists**: 매 wheel reinvent. ## 🧪 검증 / 중복 - Verified (ESLint docs, typescript-eslint, Semgrep docs). - 신뢰도 A. - Related: [[ESLint-Static-Analysis|Static-Analysis-Linting]] · [[Code_Smells]] · [[Architecture-Anti-Patterns]] · [[CI_CD 파이프라인 및 IDE 통합 보안]] · [[Abstract_Syntax_Tree]]. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — AST + 매 basic / architectural / TS / Semgrep / LLM-gen code |