Angular Modernizer
    Preparing search index...

    Module @angular-modernizer/api - v1.2.0

    Analysis, transformation and investigation tools for Angular codebases, built on ts-morph. Plugins use these tools through context.api instead of manipulating the AST directly.

    Main entry points:

    @angular-modernizer/api

    Analysis and transformation utilities used by plugins. Plugins must use this API and never manipulate the AST directly.

    The @angular-modernizer/api package provides high-level, reusable tools that plugins use to analyze and transform Angular codebases. This package contains the core business logic that powers the entire platform.

    The API follows the "API-Driven Plugin" pattern:

    // Incorrect: plugin implements low-level AST logic
    class BadPlugin {
    analyze(sourceFile: SourceFile) {
    // 100 lines of AST manipulation code
    }
    }

    // Correct: plugin uses API tools
    class GoodPlugin {
    analyze(context: AnalysisContext) {
    const usages = context.api.analysis.templateAnalyzer.analyze(template);
    const hasOnPush = context.api.transformation.importManager.hasImport(
    context.sourceFile,
    'ChangeDetectionStrategy',
    '@angular/core',
    );
    // Focus on business logic, not implementation details
    }
    }

    The PublicApi is the tool set plugins receive as context.api. createPublicApi(project, options?) builds it for one ts-morph project (options: rootPath, pattern, componentDecorators):

    import { createPublicApi } from '@angular-modernizer/api';
    import { Project } from 'ts-morph';

    const project = new Project({ tsConfigFilePath: 'tsconfig.json' });
    const api = createPublicApi(project);

    const templateUsages = api.analysis.templateAnalyzer.analyze(template);
    api.transformation.importManager.addNamedImport(sourceFile, '@angular/core', 'inject');

    It contains:

    Path Tool
    analysis.servicePatternRecognizer ServicePatternRecognizer
    analysis.serviceDetector ServiceDetector
    analysis.responsibilityClassifier ResponsibilityClassifier
    analysis.symbolLocator SymbolLocator
    analysis.memberWriteFinder MemberWriteFinder
    analysis.templateLoader ComponentTemplateLoader
    analysis.templateAnalyzer TemplateAnalyzer
    transformation.ngModuleManager NgModuleManager
    transformation.importManager ImportManager
    ngMorph NgMorphAdapter
    project the ts-morph Project

    Other tools (DependencyAnalyzer, SelectorMapper, TemplateUsageAnalyzer, the investigation classes such as UsageFinder or CallGraphBuilder) are exported from the package and created directly.

    Purpose: extract all template usages.

    class TemplateAnalyzer {
    analyze(template: string): TemplateUsage[];
    extractInlineTemplate(decoratorText: string): string | undefined;
    usesSelector(template: string, selector: string): boolean;
    }

    interface TemplateUsage {
    type: 'element' | 'directive' | 'pipe' | 'component' | 'structural-directive' | 'control-flow';
    name: string;
    line?: number;
    column?: number;
    raw?: string;
    attributes?: Record<string, string>;
    metadata?: Record<string, unknown>;
    }

    Design: extracts all usages from templates without filtering or heuristics. Provides raw data for downstream decision-making.

    Example:

    const api = createPublicApi(project);
    const usages = api.analysis.templateAnalyzer.analyze(`
    <app-user [user]="currentUser" (save)="onSave()">
    <div *ngIf="loading">Loading...</div>
    <input [(ngModel)]="name">
    </app-user>
    `);

    // Result (one entry per usage, with line and column):
    // - element 'app-user', element 'div', element 'input'
    // - structural-directive '*ngIf'
    // - directive usages for the bindings

    Purpose: build comprehensive symbol maps. Not part of PublicApi; create it with new DependencyAnalyzer().

    class DependencyAnalyzer {
    analyzeDependencies(sourceFile: SourceFile): Dependency[];
    dependsOn(sourceFile: SourceFile, targetPath: string): boolean;
    buildSymbolMap(moduleFile: SourceFile, project?: Project): SymbolMap;
    getTransitiveDependencies(sourceFile: SourceFile, project: Project): Dependency[];
    detectCircularDependencies(project: Project): { cycle: string[]; severity: 'warning' | 'error' }[];
    extractSymbolInfo(sourceFile: SourceFile, className: string): SymbolInfo | undefined;
    }

    interface Dependency {
    from: string;
    to: string;
    type: 'import' | 'component-usage' | 'directive-usage' | 'service-injection';
    symbol?: string;
    metadata?: Record<string, unknown>;
    }

    interface SymbolMap {
    declared: Map<string, SymbolInfo>;
    imported: Map<string, SymbolInfo>;
    exported: Map<string, SymbolInfo>;
    available: Map<string, SymbolInfo>;
    }

    interface SymbolInfo {
    className: string;
    selectors: string[];
    inputs: string[];
    outputs: string[];
    type: 'component' | 'directive' | 'pipe' | 'module';
    filePath: string;
    }

    Key features:

    • Maps selectors to class names
    • Handles aliased @Input/@Output bindings
    • Resolves module declarations
    • Builds complete dependency graphs

    Example:

    const analyzer = new DependencyAnalyzer();
    const moduleFile = project.addSourceFileAtPath('user.module.ts');
    const symbolMap = analyzer.buildSymbolMap(moduleFile, project);

    // symbolMap.declared: UserComponent with selectors ['app-user'], inputs ['user'], outputs ['save']
    // symbolMap.exported: the symbols UserModule exports

    Also supports advanced analysis:

    // Detect circular dependencies
    const cycles = analyzer.detectCircularDependencies(project);

    // Get transitive dependencies
    const transitiveDeps = analyzer.getTransitiveDependencies(sourceFile, project);

    // Extract symbol information
    const symbolInfo = analyzer.extractSymbolInfo(sourceFile, 'MyComponent');
    console.log(symbolInfo?.inputs); // ['input1', 'customAlias']
    console.log(symbolInfo?.outputs); // ['output1']

    Purpose: classify the public methods of a class by concern and backend area, and decide whether it is a service bag.

    const result = api.analysis.responsibilityClassifier.classifyClass(classDecl);

    result.methods; // role per public method: a concern, 'delegation' or 'uncertain'
    result.concerns; // concern groups ('data-access', 'event', 'cache', 'ui-dom', ...) with `counted`
    result.areas; // backend areas of the data-access methods (from HTTP URLs) with `counted`
    result.uncertain; // methods without a signal >= 0.7, with their signals
    result.assessment; // { isBag, kind: 'service' | 'static-utility' | 'domain-candidate' | 'none', reason, confidence }

    Roles come from typed signals (HttpClient calls, reactive fields, DOM, file, dialog/router, storage, validators, ...); a method name alone never decides. resolveUrlTemplate(expr) (also exported) resolves an HTTP URL expression to a path template and its backend area. Used by the architecture:service-bag rule and the service-bag-transform.

    Purpose: safely manage TypeScript import statements.

    class ImportManager {
    addNamedImport(sourceFile: SourceFile, moduleSpecifier: string, namedImport: string, alias?: string): void;
    removeNamedImport(sourceFile: SourceFile, namedImport: string, moduleSpecifier?: string): void;
    getImports(sourceFile: SourceFile): ImportInfo[];
    organizeImports(sourceFile: SourceFile): void;
    mergeImports(sourceFile: SourceFile): void;
    cleanupEmptyImports(sourceFile: SourceFile): void;
    hasImport(sourceFile: SourceFile, symbol: string, module?: string): boolean;
    getImportAlias(sourceFile: SourceFile, symbol: string, module?: string): string | undefined;
    }

    Key features:

    • Prevents duplicate imports
    • Handles default vs named imports
    • Organizes import statements
    • Resolves import conflicts

    Example:

    const importManager = new ImportManager();

    importManager.addNamedImport(sourceFile, '@angular/core', 'OnInit');
    importManager.addNamedImport(sourceFile, '@angular/core', 'Directive', 'NgDirective');

    importManager.removeNamedImport(sourceFile, 'OnInit', '@angular/core');

    const hasImport = importManager.hasImport(sourceFile, 'Component', '@angular/core');
    const alias = importManager.getImportAlias(sourceFile, 'Component', '@angular/core');

    importManager.mergeImports(sourceFile);
    importManager.cleanupEmptyImports(sourceFile);

    Budgets from __tests__/performance/benchmark.test.ts (asserted only with PERF_ASSERTS=1):

    • DependencyAnalyzer: <200ms for a large component file
    • Memory usage: <50MB increase during repeated operations
    import { createPublicApi, DependencyAnalyzer } from '@angular-modernizer/api';
    import { ContextFactory } from '@angular-modernizer/plugin-system';

    const api = createPublicApi(project);

    // 1. Analyze template dependencies
    const template = `<div *ngIf="loading" [user]="user" (save)="onSave()"></div>`;
    const templateUsages = api.analysis.templateAnalyzer.analyze(template);

    // 2. Get component dependencies
    const sourceFile = project.addSourceFileAtPath('user.component.ts');
    const dependencies = new DependencyAnalyzer().analyzeDependencies(sourceFile);

    // 3. Transform component to standalone
    const context = ContextFactory.createTransformContext({
    sourceFile,
    project,
    api,
    config: {},
    });

    // The plugin uses the API to:
    // - Add standalone: true to @Component decorator
    // - Build imports array from templateUsages + dependencies
    // - Add necessary import statements

    All API methods are stateless and side-effect free:

    const result1 = api.analysis.templateAnalyzer.analyze(template);
    const result2 = api.analysis.templateAnalyzer.analyze(template);
    assert.deepEqual(result1, result2); // Always true

    Tools work together seamlessly:

    const templateUsages = api.analysis.templateAnalyzer.analyze(template);
    const dependencies = new DependencyAnalyzer().analyzeDependencies(sourceFile);
    const service = api.analysis.serviceDetector.detectService(classDecl);

    APIs handle edge cases gracefully:

    // Handles malformed templates
    const usages = api.analysis.templateAnalyzer.analyze('<invalid html>>>');
    // Returns empty array or best-effort parsing

    // A file without imports
    const dependencies = new DependencyAnalyzer().analyzeDependencies(emptyFile);
    // Returns an empty array, no crash

    Efficient algorithms for large codebases:

    • Lazy evaluation where possible
    • Caching of expensive operations (ServiceDetector, SelectorMapper)
    pnpm --filter @angular-modernizer/api test
    pnpm --filter @angular-modernizer/api test -- --coverage

    Test categories:

    • Unit tests: individual tool functionality
    • Integration tests: tool composition and workflows
    • Performance tests: large codebase handling
    • Edge case tests: error conditions and malformed input

    Testing patterns:

    import { createPublicApi, type PublicApi } from '@angular-modernizer/api';
    import { Project } from 'ts-morph';

    describe('TemplateAnalyzer', () => {
    let api: PublicApi;
    let project: Project;

    beforeEach(() => {
    project = new Project({ useInMemoryFileSystem: true });
    api = createPublicApi(project);
    });

    it('should extract component usages', () => {
    const template = '<app-user [user]="user"></app-user>';
    const usages = api.analysis.templateAnalyzer.analyze(template);

    expect(usages).toContainEqual(
    expect.objectContaining({ type: 'element', name: 'app-user' }),
    );
    });
    });
    packages/api/
    ├── src/
    │ ├── analysis/ # TemplateAnalyzer, DependencyAnalyzer, ResponsibilityClassifier, ...
    │ ├── investigation/ # UsageFinder, CallGraphBuilder, TemplateInspector, ...
    │ ├── metrics/
    │ ├── transformation/ # ImportManager, NgModuleManager, NgMorphAdapter
    │ ├── public-api.ts # PublicApi, createPublicApi
    │ └── index.ts
    ├── __tests__/
    │ ├── analysis/
    │ ├── investigation/
    │ ├── metrics/
    │ ├── performance/
    │ └── transformation/
    ├── package.json
    ├── tsconfig.json
    ├── jest.config.js
    └── README.md
    • ts-morph: TypeScript AST manipulation
    • ng-morph: Angular-aware AST helpers (NgMorphAdapter)
    • @angular/compiler: template parsing for the investigation tools
    • lru-cache, semver

    When adding new API tools:

    1. Follow the PublicApi pattern - add to the unified interface
    2. Write comprehensive tests (unit, integration, and edge cases)
    3. Document with examples and update this README
    4. Maintain backward compatibility
    5. Optimize for large codebases
    // 1. Create the tool interface
    export interface NewAnalyzer {
    analyze(input: InputType): OutputType;
    }

    // 2. Implement the tool
    export class NewAnalyzerImpl implements NewAnalyzer {
    analyze(input: InputType): OutputType {
    // Implementation
    }
    }

    // 3. Add to the PublicApi interface (src/public-api.ts)
    interface PublicApi {
    analysis: {
    // ... existing tools
    newAnalyzer: NewAnalyzer;
    };
    }

    // 4. Create it in createPublicApi(project, options?)
    // analysis: { ..., newAnalyzer: new NewAnalyzerImpl() }

    // 5. Export it from src/index.ts

    MIT License - See LICENSE file for details

    Enumerations

    ServicePattern

    Classes

    AngularSymbolFinder
    BootstrapExecutionAnalyzer
    CallableResolver
    CallGraphBuilder
    CitationVerifier
    CodebaseSearcher
    ComponentHostFinder
    ComponentTemplateLoader
    DependencyAnalyzer
    DirectiveIndex
    DynamicHostFinder
    DynamicResultFollower
    ExternalTemplateLoader
    FormGroupInspector
    I18nInspector
    ImportManager
    MemberWriteFinder
    NgModuleManager
    NgMorphAdapter
    OutputFlowTracer
    PayloadTracer
    ResponsibilityClassifier
    SelectorMapper
    ServiceDetector
    ServicePatternRecognizer
    StackTraceParser
    SymbolLocator
    SyntacticResolver
    TemplateAnalyzer
    TemplateInspector
    TemplateUsageAnalyzer
    TemplateUsageFinder
    TypeResolver
    UsageFinder

    Interfaces

    AngularSymbolFinderOptions
    AngularSymbolResult
    AreaGroup
    BagAssessment
    BindingCoverage
    BootstrapExecutionOptions
    BootstrapExecutionResult
    BootstrapRoot
    BootstrapWatchHit
    BoundOutputHost
    BulkDetectionResult
    Callable
    CallGraphOptions
    CallGraphResult
    CallNode
    Citation
    CitationAnchor
    CitationFileSource
    CitationResult
    ClassResponsibilities
    ComponentHost
    ComponentHostFinderOptions
    ComponentHostQuery
    ComponentHostResult
    ComponentTemplateOptions
    ComponentTemplateReport
    ComponentTemplateSource
    ConcernGroup
    DeclarationLookup
    DecoratorStringValue
    Dependency
    DirectiveIndexOptions
    DirectiveIO
    DirectiveIOMember
    DomainGroup
    DynamicCloseSite
    DynamicDataInjection
    DynamicHost
    DynamicHostArgument
    DynamicHostFinderOptions
    DynamicResultFlow
    DynamicResultForward
    DynamicResultHandler
    DynamicResultHost
    DynamicResultOperator
    DynamicResultOutcome
    DynamicResultUse
    DynamicRoute
    DynamicSite
    EmitSite
    FindUsagesOptions
    FormCascade
    FormGroupInspectorOptions
    FormOperation
    HandlerArgument
    HandlerArgumentFlow
    HandlerStep
    HandlerStepFlow
    HostOccurrence
    HostTarget
    I18nDefinition
    I18nDynamicUsage
    I18nInspectOptions
    I18nKeyDefinition
    I18nKeyReport
    I18nLayerGap
    I18nMissingTranslation
    I18nPrefixReport
    I18nReport
    I18nSite
    I18nSource
    I18nSourceOptions
    I18nSourceScan
    I18nUnusedKey
    I18nUsage
    I18nUsageOptions
    I18nUsageScan
    ImportInfo
    IndexedDirective
    IndexedPipe
    InspectedForm
    InspectedFormControl
    InspectedTemplateElement
    InspectTemplateOptions
    LiteralValues
    MatchableElement
    MemberWriteOptions
    MethodResponsibility
    NgMorphAdapterOptions
    OutputBinding
    OutputFlow
    OutputFlowQuery
    OutputFlowResult
    OutputFlowTracerOptions
    OutputSubscription
    PayloadBudget
    PayloadCallee
    PayloadEffect
    PayloadTraceOptions
    PlanCitationReport
    ProjectSymbolInfo
    PublicApi
    ReachedCallable
    ReactiveProperty
    ReportedBinding
    ReportedDirective
    ReportedElement
    ReportedI18nKey
    ReportedI18nPrefix
    ReportedPipe
    ResolvedMember
    ResolvedTypeInfo
    ResponsibilitySignal
    ResponsibilityThresholds
    SearchCoverage
    SearchOptions
    SearchResult
    SearchWithCoverageResult
    ServiceClassification
    ServiceDetectionResult
    StackFrame
    StackFrameFileIndex
    SymbolInfo
    SymbolMap
    SymbolUsage
    TemplateControlFlow
    TemplateEdge
    TemplateEdgeOptions
    TemplateElementBinding
    TemplateEventHandler
    TemplateHit
    TemplateI18nKey
    TemplateI18nPrefix
    TemplateInspection
    TemplateInspectorOptions
    TemplatePosition
    TemplateQuery
    TemplateSymbolUsage
    TemplateUsage
    TemplateUsageFinderOptions
    UnboundOutputHost
    UnresolvedI18nRegistration
    UrlTemplate

    Type Aliases

    AngularSymbolType
    BootstrapEdge
    BootstrapRootKind
    BootstrapTiming
    CallableKind
    CallNodeKind
    CitationStatus
    Concern
    DirectiveIOSource
    DynamicCertainty
    DynamicResolution
    DynamicResultUseKind
    EmitVia
    FormControlKind
    FormOperationName
    I18nConditionReason
    I18nDiscovery
    I18nKeySource
    I18nKeyStatus
    I18nLibrary
    I18nSourceKind
    I18nSourceSummary
    I18nUsageSource
    MethodRole
    OutputEmitterKind
    PayloadEffectKind
    SymbolKind
    TemplateBindingKind
    TemplateMatch
    TemplateUsageKind
    UsageType

    Variables

    CITATION_ATTENTION_STATUSES
    DEFAULT_COMPONENT_DECORATORS
    DEFAULT_THRESHOLDS
    DIRECTIVE_INDEX_DECORATORS
    EXCLUDED_SEARCH_DIRECTORIES
    FORM_STATE_OPERATIONS
    FORM_VALUE_OPERATIONS
    MAX_VALUE_LENGTH
    SIGNAL_WEIGHTS

    Functions

    anchorOf
    areaOfTemplate
    collectComponentTemplates
    collectDirectiveIO
    collectEventHandlers
    collectI18nUsages
    collectTemplateEdges
    componentDecoratorNames
    createCitationFileSource
    createPublicApi
    createStackFrameFileIndex
    declaredTypeName
    extractCitations
    findComponentDecorator
    findI18nSources
    findTemplateOwnerFiles
    i18nLibraryOf
    inspectTemplate
    isTestFile
    jsonDefinitions
    loadComponentTemplate
    normalizeWatchExpression
    positionInText
    readDecoratorString
    resolveUrlTemplate
    scanTemplate
    templateMayMatch
    toMatchableElement
    tracePayload
    traceStringLiterals