如何检查导入文件中类的Decorator?NestJS ESLint规则适配
解决NestJS自定义ESLint规则无法检查外部导入类的@Injectable装饰器问题
问题背景
我需要确保NestJS模块的@Module装饰器的providers数组中配置的类都带有@Injectable装饰器。自己编写的自定义ESLint规则在检查当前文件声明的类时正常工作,但对从外部文件导入的类失效,请问该如何处理?
现有实现代码(仅能处理当前文件类):
module.exports = { 'injectable-providers': { create: function (context) { return { ClassDeclaration: function (node) { const moduleDecorator = nestModule.getModuleDecorator(node); if (!moduleDecorator) { return; } const properties = moduleDecorator.expression.arguments[0].properties; if (!properties) { return; } const providersProperty = properties.find( (property) => property.key.name === 'providers' ); const providers = providersProperty?.value.elements ?? []; providers.forEach((provider) => { const providerNode = context.getScope().upper.set.get(provider.name) ?.defs[0]?.node; if (providerNode && !nestInjectable.isInjectable(providerNode)) { context.report({ message: 'Provider is not `@Injectable`', node: provider, }); } }); }, }; }, }, }; const nestModule = { getModuleDecorator: (node) => node?.decorators?.find( (decorator) => decorator.expression.callee.name === 'Module' ), }; const nestInjectable = { isInjectable: (node) => { const isClass = node?.type === 'ClassDeclaration'; const hasInjectableDecorator = (node?.decorators ?? []).some( (decorator) => decorator.expression.callee.name === 'Injectable' ); return node && isClass && hasInjectableDecorator; }, };
问题原因
现有代码仅通过当前文件的作用域查找类定义,但外部导入的类的AST节点不在当前文件的作用域内,因此无法获取其装饰器信息。
解决方案
要检查外部导入类的装饰器,必须借助TypeScript的类型检查器(TypeChecker)——ESLint的AST仅包含当前文件内容,外部文件的类信息需要通过TypeChecker解析类型元数据来获取。
修改后的规则代码
const { ESLintUtils } = require('@typescript-eslint/utils'); module.exports = { 'injectable-providers': { create: function (context) { // 获取TypeScript类型检查器实例 const typeChecker = ESLintUtils.getParserServices(context).program.getTypeChecker(); return { ClassDeclaration: function (node) { const moduleDecorator = nestModule.getModuleDecorator(node); if (!moduleDecorator) return; const moduleOptions = moduleDecorator.expression.arguments[0]; if (!moduleOptions || moduleOptions.type !== 'ObjectExpression') return; const providersProperty = moduleOptions.properties.find( prop => prop.key?.name === 'providers' ); if (!providersProperty || providersProperty.value.type !== 'ArrayExpression') return; const providers = providersProperty.value.elements; providers.forEach(provider => { // 仅处理标识符类型的provider(排除工厂函数、直接值等情况) if (provider.type !== 'Identifier') return; // 获取provider对应的符号(关联到外部文件的类声明) const symbol = typeChecker.getSymbolAtLocation(provider); if (!symbol) return; // 从符号中提取外部文件的类声明节点 const classDeclaration = symbol.declarations?.find( decl => decl.type === 'ClassDeclaration' ); if (!classDeclaration) return; // 检查该类是否带有@Injectable装饰器 if (!nestInjectable.isInjectable(classDeclaration)) { context.report({ message: 'Provider 必须添加 `@Injectable` 装饰器', node: provider, }); } }); }, }; }, }, }; const nestModule = { getModuleDecorator: (node) => node?.decorators?.find( (decorator) => decorator.expression.type === 'CallExpression' && decorator.expression.callee.name === 'Module' ), }; const nestInjectable = { isInjectable: (node) => { if (!node || node.type !== 'ClassDeclaration') return false; return node.decorators?.some( decorator => decorator.expression.type === 'CallExpression' && decorator.expression.callee.name === 'Injectable' ); }, };
关键修改说明
- 引入TypeScript ESLint工具:通过
@typescript-eslint/utils获取TypeChecker,用于解析外部类的类型信息 - 关联外部类声明:通过
typeChecker.getSymbolAtLocation获取导入标识符对应的符号,进而找到外部文件中的类声明节点 - 增强AST节点判断:添加了对装饰器节点类型的判断,避免因AST结构不匹配导致的误判
注意事项
- 确保ESLint配置中使用
@typescript-eslint/parser作为解析器,并正确配置tsconfigRootDir指向项目的TypeScript配置文件 - 规则依赖TypeScript类型信息,运行ESLint时需要保证项目已正确编译或TypeScript配置有效
内容的提问来源于stack exchange,提问作者Ivan Rubinson
相关产品推荐
相关产品推荐

