Security lint — 매 dangerous API (eval, innerHTML) 금지.
Migration codemod — 매 deprecated API 의 자동 fixer.
💻 패턴
1. Minimal rule skeleton
// rules/no-foo.js
exportdefault{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'),});}},};},};
import{RuleTester}from'eslint';importrulefrom'../rules/no-foo.js';consttester=newRuleTester({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';constcreateRule=ESLintUtils.RuleCreator((name)=>`https://acme.dev/rules/${name}`,);exportdefaultcreateRule({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'});},};},});