Provider resolution

Source: src/engine/graph/type-resolver.ts

Extracts dependency information from every @Injectable() class: constructor dependencies, public method count, and file location. Rules like no-unused-providers, no-missing-injectable, and no-business-logic-in-controllers need to know which providers exist, what they depend on, and how many methods they expose.

Input is the parsed project and the file list:

project: Project     // ts-morph AST project
files: string[]      // file paths to analyze

The result maps each provider name to its info:

Map<string, ProviderInfo>
 
interface ProviderInfo {
  name: string                    // class name
  filePath: string
  classDeclaration: ClassDeclaration
  dependencies: string[]          // constructor parameter type names
  publicMethodCount: number
}

How it works

For each file, finds all classes decorated with @Injectable():

  1. Extract constructor parameters: each parameter's type becomes a dependency name.
  2. Normalize type names: extractSimpleTypeName() reduces the annotation to a bare class name.
    • import("@prisma/client").PrismaServicePrismaService
    • Repository<User>Repository
    • ConfigServiceConfigService
  3. Count public methods: methods with no access modifier, or an explicit public.

Example

Given this class:

@Injectable()
export class UserService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly config: ConfigService,
  ) {}
 
  async findAll() { /* ... */ }
  async findById(id: string) { /* ... */ }
  private validate(user: User) { /* ... */ }
}

The resolver produces:

{
  name: "UserService",
  filePath: "/src/user/user.service.ts",
  dependencies: ["PrismaService", "ConfigService"],
  publicMethodCount: 2,  // findAll + findById (validate is private)
}

Debugging tips

  • Only classes with @Injectable() are resolved. Controllers use @Controller(), so they are absent from the providers map and are handled separately during rule execution.
  • If a dependency name looks wrong, for example because it still contains an import path, check the type annotation on the constructor parameter.