Angular Workspace Symbol Generation
This challenge focuses on programmatically generating symbols for your Angular workspace, similar to what IDEs use for code navigation. This is crucial for building advanced tooling, refactoring utilities, or even custom code generators that understand your project's structure. You will create a utility function that takes a directory path and outputs a structured representation of Angular components, services, and modules found within.
Problem Description
Your task is to develop a TypeScript function, generateWorkspaceSymbols, that scans a given directory (representing an Angular project root) and identifies Angular components, services, and modules. The function should then output this information in a standardized, structured format.
Key Requirements:
- Identify Angular Artifacts: The function must be able to detect files that define Angular components (using
@Component), services (using@Injectable), and modules (using@NgModule). - Extract Key Information: For each identified artifact, extract:
type: "component", "service", or "module".name: The name of the class or function that is decorated.filePath: The relative path to the file containing the artifact.exportName(optional): The exported name if the artifact is exported from its file.
- Handle Nested Structures: The function should recursively scan subdirectories within the provided path.
- Output Format: The output should be an array of objects, each representing an identified Angular artifact.
Expected Behavior:
Given a path to an Angular project, the function should return an array of symbol objects. For example, if a project has a src/app/my-component/my-component.component.ts defining a component and src/app/my.service.ts defining a service, the output might look like:
[
{
"type": "component",
"name": "MyComponent",
"filePath": "src/app/my-component/my-component.component.ts"
},
{
"type": "service",
"name": "MyService",
"filePath": "src/app/my.service.ts"
}
]
Edge Cases to Consider:
- Empty Directories: The function should gracefully handle empty directories.
- Non-Angular Files: Files that do not define Angular artifacts should be ignored.
- Multiple Artifacts per File: A single file might define multiple Angular artifacts (though less common for components/services, more for modules). The function should identify all of them.
- Exported vs. Non-Exported: Differentiate between symbols that are exported and those that are not.
- File Naming Conventions: While not strictly enforced, be aware of common Angular naming conventions (e.g.,
.component.ts,.service.ts).
Examples
Example 1:
Input: A directory structure like:
/project-root
├── src
│ ├── app
│ │ ├── app.component.ts (defines @Component)
│ │ ├── app.module.ts (defines @NgModule)
│ │ ├── services
│ │ │ └── user.service.ts (defines @Injectable)
│ │ └── components
│ │ └── home
│ │ └── home.component.ts (defines @Component)
│ └── ...
Output:
[
{
"type": "component",
"name": "AppComponent",
"filePath": "src/app/app.component.ts"
},
{
"type": "module",
"name": "AppModule",
"filePath": "src/app/app.module.ts"
},
{
"type": "service",
"name": "UserService",
"filePath": "src/app/services/user.service.ts"
},
{
"type": "component",
"name": "HomeComponent",
"filePath": "src/app/components/home/home.component.ts"
}
]
Explanation:
The function scans the /project-root, identifies the decorated classes in the specified files, and extracts their type, name, and relative file path.
Example 2:
Input: A directory structure like:
/project-root
├── src
│ ├── core
│ │ ├── interceptors
│ │ │ └── auth.interceptor.ts (defines @Injectable, exported as authInterceptor)
│ │ └── guards
│ │ └── auth.guard.ts (defines @Injectable, not explicitly exported)
│ └── ...
Output:
[
{
"type": "service",
"name": "AuthInterceptor",
"filePath": "src/core/interceptors/auth.interceptor.ts",
"exportName": "authInterceptor"
},
{
"type": "service",
"name": "AuthGuard",
"filePath": "src/core/guards/auth.guard.ts"
}
]
Explanation:
This example demonstrates handling exported symbols. AuthInterceptor is identified with its exportName, while AuthGuard is identified by its class name as it's not explicitly exported.
Constraints
- The
generateWorkspaceSymbolsfunction will be called with a string representing the absolute or relative path to the project's root directory. - The analysis should be limited to
.tsfiles. - The solution should rely on static analysis of the TypeScript code, not runtime execution.
- Assume standard Angular project structure and common TypeScript syntax.
- Avoid using external libraries that directly parse Angular metadata (like
ngMetadata) unless explicitly allowed. Focus on AST (Abstract Syntax Tree) traversal using the TypeScript compiler API.
Notes
- You will likely need to use the TypeScript Compiler API (
typescriptpackage) to parse the code and traverse the Abstract Syntax Tree (AST). - Look for
ClassDeclarationnodes with associatedDecorators. - Check the decorator names for
@Component,@Injectable, and@NgModule. - Consider how to extract the class name and any explicit
exportstatements. - Recursion is a natural fit for directory traversal.
- Think about how to determine the
exportName. This might involve looking forExportDeclarationnodes and matching them withClassDeclarations.