f8b21af4be
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>
5.8 KiB
5.8 KiB
id, title, category, status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, verification_status, tags, raw_sources, last_reinforced, github_commit, tech_stack
| id | title | category | status | canonical_id | aliases | duplicate_of | source_trust_level | confidence_score | verification_status | tags | raw_sources | last_reinforced | github_commit | tech_stack | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| wiki-2026-0508-eslint-plugin-development | ESLint Plugin Development | 10_Wiki/Topics | verified | self |
|
none | A | 0.9 | applied |
|
2026-05-10 | pending |
|
ESLint Plugin Development
매 한 줄
"매 ESLint plugin은 AST visitor + RuleTester.". ESLint 9 (2024-) Flat config 시대에는 plugin =
{rules, configs, processors}object 의 export. 매 rule =meta(docs, fixable, schema) +create(context)returning visitor map (e.g.,CallExpression(node) { ... }).
매 핵심
매 AST 기반
- ESLint = ESTree spec (espree parser default; @typescript-eslint/parser for TS).
- 매 rule visitor가 specific node type 방문 —
Identifier,CallExpression,JSXElement등. context.report({node, message, fix})으로 violation 보고.
매 Plugin shape (Flat config)
- ESLint 9+ deprecated
.eslintrc형식 — 매eslint.config.jsflat config. - Plugin export =
{meta, rules, configs, processors}— meta에name/version명시.
매 응용
- Internal style guide — 매 monorepo 의 component naming, import order.
- Framework rules —
eslint-plugin-react,eslint-plugin-vue처럼 framework-specific. - Security lint — 매 dangerous API (eval, innerHTML) 금지.
- Migration codemod — 매 deprecated API 의 자동 fixer.
💻 패턴
1. Minimal rule skeleton
// rules/no-foo.js
export default {
meta: {
type: 'problem',
docs: { description: 'disallow Foo identifier', recommended: true },
fixable: 'code',
schema: [],
messages: { unexpected: "'{{name}}' is not allowed." },
},
create(context) {
return {
Identifier(node) {
if (node.name === 'Foo') {
context.report({
node,
messageId: 'unexpected',
data: { name: node.name },
fix: (fixer) => fixer.replaceText(node, 'Bar'),
});
}
},
};
},
};
2. Plugin entry (ESM, ESLint 9 flat)
// index.js
import noFoo from './rules/no-foo.js';
const plugin = {
meta: { name: 'eslint-plugin-acme', version: '1.0.0' },
rules: { 'no-foo': noFoo },
};
plugin.configs = {
recommended: {
plugins: { acme: plugin },
rules: { 'acme/no-foo': 'error' },
},
};
export default plugin;
3. Consumer flat config
// eslint.config.js
import acme from 'eslint-plugin-acme';
export default [
acme.configs.recommended,
{ rules: { 'acme/no-foo': ['error'] } },
];
4. RuleTester (built-in test)
import { RuleTester } from 'eslint';
import rule from '../rules/no-foo.js';
const tester = new RuleTester({
languageOptions: { ecmaVersion: 2024, sourceType: 'module' },
});
tester.run('no-foo', rule, {
valid: ['const Bar = 1;'],
invalid: [{
code: 'const Foo = 1;',
errors: [{ messageId: 'unexpected' }],
output: 'const Bar = 1;',
}],
});
5. TypeScript rule with @typescript-eslint
import { ESLintUtils } from '@typescript-eslint/utils';
const createRule = ESLintUtils.RuleCreator(
(name) => `https://acme.dev/rules/${name}`,
);
export default createRule({
name: 'no-any',
meta: {
type: 'suggestion',
docs: { description: 'disallow any' },
schema: [],
messages: { noAny: 'Avoid any; use unknown.' },
},
defaultOptions: [],
create(context) {
return {
TSAnyKeyword(node) {
context.report({ node, messageId: 'noAny' });
},
};
},
});
6. Suggestions (non-auto fix)
context.report({
node,
messageId: 'considerRename',
suggest: [{
messageId: 'renameToBar',
fix: (fixer) => fixer.replaceText(node, 'Bar'),
}],
});
7. AST exploration (astexplorer.net)
// Visitor pattern — leverage selector strings
return {
'CallExpression[callee.name="eval"]'(node) {
context.report({ node, message: 'eval disallowed' });
},
};
8. Scope / variable analysis
create(context) {
return {
Identifier(node) {
const scope = context.sourceCode.getScope(node);
const variable = scope.references.find((r) => r.identifier === node);
if (variable?.resolved?.defs[0]?.type === 'ImportBinding') {
// imported identifier
}
},
};
}
매 결정 기준
| 상황 | Approach |
|---|---|
| Codebase-specific 매 규칙 | Internal plugin (private npm). |
| OSS 공유 | Publish eslint-plugin-foo. |
| TS-only | @typescript-eslint/utils RuleCreator. |
| 매 codemod 용 | jscodeshift / ts-morph (ESLint fix 보다 more powerful). |
| 매 단순 ban API | no-restricted-syntax config — plugin 불필요. |
기본값: 매 ESLint 9 flat config + @typescript-eslint/utils RuleCreator.
🔗 Graph
- 부모: ESLint · AST
- 변형: Biome
- Adjacent: Prettier · TypeScript
🤖 LLM 활용
언제: AST visitor 작성, RuleTester case 생성, 매 selector string 의 작성. 언제 X: 매 cross-file 분석 (ESLint = single-file) — 매 ts-morph / lsif 사용.
❌ 안티패턴
- Regex on source: 매 source string regex 의 X — 매 AST 사용.
- No tests: 매 RuleTester 없는 rule 의 X — false positive 의 즉시 발생.
- Auto-fix without safety: 매 fix가 매 semantics 변경 시
suggest사용. .eslintrcin 2026: 매 flat config 으로 migrate.
🧪 검증 / 중복
- Verified (ESLint 9 docs, eslint.org/docs/latest/extend).
- 신뢰도 A.
🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — ESLint 9 flat config plugin authoring 패턴 |