Angular Dead Code Elimination Tool
Angular projects often accumulate unused components, services, pipes, and directives over time, leading to larger bundle sizes and slower build times. This challenge asks you to implement a simplified dead code elimination tool that identifies and reports unused code within an Angular project, focusing on components. The goal is to provide a foundation for a more robust solution and understand the challenges involved in static analysis of Angular code.
Problem Description
You are tasked with creating a TypeScript function findUnusedComponents that analyzes an Angular project's code and identifies components that are not imported or used anywhere else in the project. The function should take the project's source code as input (represented as an array of strings, where each string is a file's content) and return an array of the names of unused components.
Key Requirements:
- Component Identification: The function must be able to identify Angular components based on the presence of the
@Componentdecorator. - Usage Detection: The function must analyze the code to determine if a component is imported and used in other files. "Usage" is defined as an import statement referencing the component, or a direct reference to the component class within another component's template (e.g.,
<app-my-component>). - Accurate Reporting: The function should accurately identify unused components without false positives (reporting components as unused when they are actually used).
- Simple Template Matching: For template usage detection, a simple string search is sufficient. No complex parsing of HTML is required.
- No External Dependencies: The solution should only use built-in TypeScript features and standard JavaScript libraries.
Expected Behavior:
The function should return an array of strings, where each string is the name of an unused component. The component name is the string between the quotes in the @Component decorator (e.g., @Component({ selector: 'app-my-component', ... }) would yield "app-my-component"). If no unused components are found, the function should return an empty array.
Edge Cases to Consider:
- Circular Dependencies: Handle cases where components import each other.
- Dynamic Imports: The tool should primarily focus on statically analyzable code. Dynamic imports (e.g.,
import('...')) are outside the scope of this challenge. - Component Namespaces: Components with the same name in different namespaces should be treated as distinct.
- Files with Syntax Errors: The function should gracefully handle files with syntax errors without crashing. It can skip such files.
Examples
Example 1:
Input: [
"import { MyComponent } from './my-component.component';",
"import { AnotherComponent } from './another-component.component';",
"@Component({ selector: 'app-my-component', ... }) export class MyComponent { }",
"@Component({ selector: 'app-another-component', ... }) export class AnotherComponent { }"
]
Output: []
Explanation: Both components are imported and therefore used.
Example 2:
Input: [
"import { MyComponent } from './my-component.component';",
"@Component({ selector: 'app-my-component', ... }) export class MyComponent { }",
"@Component({ selector: 'app-unused-component', ... }) export class UnusedComponent { }"
]
Output: ["app-unused-component"]
Explanation: MyComponent is imported, but UnusedComponent is not.
Example 3:
Input: [
"import { MyComponent } from './my-component.component';",
"@Component({ selector: 'app-my-component', ... }) export class MyComponent { }",
"@Component({ selector: 'app-unused-component', ... }) export class UnusedComponent { }",
"<div><app-my-component></app-my-component></div>"
]
Output: ["app-unused-component"]
Explanation: MyComponent is used in the template, but UnusedComponent is not.
Constraints
- Input Size: The input array can contain up to 100 files.
- File Size: Each file in the input array can be up to 10,000 characters long.
- Performance: The function should complete within 5 seconds for a typical Angular project (around 50 files).
- Component Decorator Format: The
@Componentdecorator is assumed to be in the standard Angular format. - Template Matching: Template matching is a simple string search. No HTML parsing is required.
Notes
- Focus on identifying unused components. Services, pipes, and directives are outside the scope of this challenge.
- You can assume that all files are valid TypeScript files (though they may contain syntax errors).
- Consider using regular expressions to extract component names from the
@Componentdecorator. - A simple approach to usage detection is to search for import statements and template references.
- This is a simplified problem; a real-world dead code elimination tool would require much more sophisticated analysis.
- Error handling is not a primary concern; focus on the core logic of identifying unused components.
- The order of components in the output array does not matter.