Files
2nd/10_Wiki/Topics/AI_and_ML/Custom-ESLint-Rules-Development.md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

356 lines
9.3 KiB
Markdown

---
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 |