Angular Modernizer
    Preparing search index...

    Parser Strategy interface.

    This interface defines the contract for all parser implementations in the platform. Each parser strategy must implement these methods to integrate with the parser orchestrator.

    Strategy Pattern:

    • Multiple parser implementations can coexist
    • Each parser has a unique name ('standard', 'enhanced', 'babel', etc.)
    • Parsers can decline to handle certain project types (canHandle returns false)
    • Parsers report their reliability for a given project context (0.0-1.0)
    • Orchestrator selects parser with highest reliability

    Lifecycle:

    1. Orchestrator calls canHandle(projectContext) on all registered parsers
    2. Orchestrator calls getReliability(projectContext) on capable parsers
    3. Orchestrator selects parser with highest reliability
    4. Orchestrator calls parse(file, content) to create AST
    5. Orchestrator calls transform(ast, rules) to apply transformations
    6. If parser fails, orchestrator tries fallback parsers

    Reliability Scores:

    • 0.95+: High reliability, standard Angular CLI projects (StandardParserStrategy)
    • 0.8-0.95: Good reliability, projects with minor incompatibilities
    • 0.6-0.8: Moderate reliability, projects with external decorators (EnhancedParserStrategy)
    • 0.4-0.6: Low reliability, experimental parsers
    • <0.4: Very low reliability, not recommended
    export class StandardParserStrategy implements ParserStrategy {
    name = 'standard';

    canHandle(context: ProjectContext): boolean {
    // Only handle standard projects without custom patterns
    return !context.hasCustomDecorators && !context.hasComplexConstructors;
    }

    getReliability(context: ProjectContext): number {
    return this.canHandle(context) ? 0.95 : 0.5;
    }

    async parse(file: string, content: string): Promise<AST> {
    // Use ts-morph directly without preprocessing
    const project = new Project();
    const sourceFile = project.createSourceFile(file, content);
    const diagnostics = sourceFile.getPreEmitDiagnostics();
    return {
    sourceFile,
    errors: diagnostics.map(d => ({ message: d.getMessageText(), line: d.getLineNumber() })),
    warnings: [],
    falsePositivesFiltered: 0,
    customDecorators: [],
    pathAliasesResolved: 0
    };
    }

    async transform(ast: AST, rules: TransformRule[]): Promise<TransformResult> {
    // Apply transformation rules to AST
    // ... transformation logic
    }
    }
    export class EnhancedParserStrategy implements ParserStrategy {
    name = 'enhanced';
    private falsePositiveDetector: FalsePositiveDetector;
    private pathAliasResolver: PathAliasResolver;

    constructor(project: Project) {
    this.falsePositiveDetector = new FalsePositiveDetector();
    this.pathAliasResolver = new PathAliasResolver(project);
    }

    canHandle(context: ProjectContext): boolean {
    // Handle projects with custom patterns
    return context.hasCustomDecorators || context.hasPathAliases || context.hasComplexConstructors;
    }

    getReliability(context: ProjectContext): number {
    // Calculate reliability based on complexity
    let reliability = 0.8;
    if (context.hasCustomDecorators) reliability -= 0.1;
    if (context.hasPathAliases) reliability -= 0.05;
    if (context.hasComplexConstructors) reliability -= 0.05;
    return Math.max(reliability, 0.6);
    }

    async parse(file: string, content: string): Promise<AST> {
    // Step 1: Resolve path aliases
    const preprocessed = this.pathAliasResolver.preprocessFileContent(content, file);

    // Step 2: Parse with ts-morph
    const project = new Project();
    const sourceFile = project.createSourceFile(file, preprocessed);
    const diagnostics = sourceFile.getPreEmitDiagnostics();

    // Step 3: Filter false positives
    const errors = diagnostics.map(d => ({
    message: d.getMessageText().toString(),
    line: d.getLineNumber() || 0,
    column: d.getStart() || 0
    }));
    const filteredResult = this.falsePositiveDetector.filterErrors(errors, content);

    return {
    sourceFile,
    errors: filteredResult.filtered,
    warnings: [],
    falsePositivesFiltered: filteredResult.removedCount,
    customDecorators: [], // Detected by ExternalDecoratorHandler
    pathAliasesResolved: preprocessed !== content ? 1 : 0
    };
    }

    async transform(ast: AST, rules: TransformRule[]): Promise<TransformResult> {
    // Apply transformations with enhanced error handling
    // ... transformation logic
    }
    }
    interface ParserStrategy {
        name: string;
        canHandle(projectContext: ParserProjectContext): boolean;
        getReliability(projectContext: ParserProjectContext): number;
        parse(file: string, content: string): Promise<ParserAST>;
        transform(
            ast: ParserAST,
            rules: TransformRule[],
        ): Promise<ParserTransformResult>;
    }

    Implemented by

    Index

    Properties

    name: string

    Unique name for this parser strategy.

    Standard names:

    • 'standard' - Standard ts-morph parser (no preprocessing)
    • 'enhanced' - ts-morph with FalsePositiveDetector and PathAliasResolver
    • 'babel' - Babel parser with TypeScript plugin
    • 'custom' - Custom parser implementations
    name = 'enhanced';
    

    Methods

    • Determine if this parser can handle the given project context.

      This method is called by the parser orchestrator to filter out parsers that are incompatible with the project. A parser should return false if it knows it cannot parse the project reliably.

      Decision Logic:

      • StandardParserStrategy: Returns false for projects with custom decorators or complex constructors
      • EnhancedParserStrategy: Returns true for projects with custom patterns
      • BabelParserStrategy: Returns true for all projects

      Performance: This method should be fast (<1ms) as it's called on every parser during selection.

      Parameters

      • projectContext: ParserProjectContext

        Project characteristics detected by ProjectCompatibilityValidator

      Returns boolean

      true if parser can handle this project, false otherwise

      canHandle(context: ProjectContext): boolean {
      // Only handle standard Angular CLI projects
      return !context.hasCustomDecorators && !context.hasComplexConstructors;
      }
      canHandle(context: ProjectContext): boolean {
      // Handle projects with custom patterns
      return context.hasCustomDecorators || context.hasPathAliases || context.hasComplexConstructors;
      }
    • Calculate reliability score for this parser on the given project.

      This method is called by the parser orchestrator to select the best parser for a project. Higher scores indicate higher reliability and confidence in the parser's ability to handle the project successfully.

      Reliability Tiers:

      • 0.95+: High reliability, standard Angular CLI projects (StandardParserStrategy)
      • 0.8-0.95: Good reliability, projects with minor incompatibilities
      • 0.6-0.8: Moderate reliability, projects with external decorators (EnhancedParserStrategy)
      • 0.4-0.6: Low reliability, experimental parsers
      • <0.4: Very low reliability, not recommended

      Calculation Guidelines:

      • Start with a base score (e.g., 0.8 for enhanced parser)
      • Subtract penalties for each incompatibility:
        • Custom decorators: -0.1
        • Path aliases: -0.05
        • Complex constructors: -0.05
      • Ensure minimum score (e.g., 0.6) to prevent zero reliability
      • If canHandle() returns false, return low score (e.g., 0.5)

      Performance: This method should be fast (<1ms) as it's called on every capable parser.

      Parameters

      • projectContext: ParserProjectContext

        Project characteristics detected by ProjectCompatibilityValidator

      Returns number

      Reliability score between 0.0 (cannot parse) and 1.0 (perfect reliability)

      getReliability(context: ProjectContext): number {
      if (!this.canHandle(context)) {
      return 0.5; // Low score if cannot handle
      }
      return 0.95; // High reliability for standard projects
      }
      getReliability(context: ProjectContext): number {
      let reliability = 0.8; // Base score

      // Apply penalties for complexity
      if (context.hasCustomDecorators) reliability -= 0.1;
      if (context.hasPathAliases) reliability -= 0.05;
      if (context.hasComplexConstructors) reliability -= 0.05;

      return Math.max(reliability, 0.6); // Minimum 0.6
      }
    • Parse a file and return an Abstract Syntax Tree.

      This method is responsible for:

      1. Preprocessing the file content (path alias resolution, false positive filtering)
      2. Parsing the content into a ts-morph SourceFile
      3. Detecting syntax errors and warnings
      4. Collecting preprocessing metadata

      Error Handling:

      • If parsing fails, throw an error with a descriptive message
      • Orchestrator will catch the error and try fallback parsers
      • Never return null or undefined

      Performance:

      • StandardParserStrategy: <5ms per file (baseline)
      • EnhancedParserStrategy: <20ms per file (<10% overhead target)
      • Target: <50ms per file for all parsers

      Parameters

      • file: string

        Absolute path to the file being parsed (for relative path calculation)

      • content: string

        File content to parse

      Returns Promise<ParserAST>

      Promise resolving to AST with metadata

      Error if parsing fails (orchestrator will catch and try fallbacks)

      async parse(file: string, content: string): Promise<AST> {
      const project = new Project();
      const sourceFile = project.createSourceFile(file, content);
      const diagnostics = sourceFile.getPreEmitDiagnostics();

      return {
      sourceFile,
      errors: diagnostics.map(d => ({
      message: d.getMessageText().toString(),
      line: d.getLineNumber() || 0,
      column: d.getStart() || 0
      })),
      warnings: [],
      falsePositivesFiltered: 0,
      customDecorators: [],
      pathAliasesResolved: 0
      };
      }
      async parse(file: string, content: string): Promise<AST> {
      // Step 1: Resolve path aliases
      const preprocessed = this.pathAliasResolver.preprocessFileContent(content, file);
      const pathAliasesResolved = preprocessed !== content ? 1 : 0;

      // Step 2: Parse with ts-morph
      const project = new Project();
      const sourceFile = project.createSourceFile(file, preprocessed);
      const diagnostics = sourceFile.getPreEmitDiagnostics();

      // Step 3: Filter false positives
      const errors = diagnostics.map(d => ({
      message: d.getMessageText().toString(),
      line: d.getLineNumber() || 0,
      column: d.getStart() || 0
      }));
      const filteredResult = this.falsePositiveDetector.filterErrors(errors, content);

      return {
      sourceFile,
      errors: filteredResult.filtered,
      warnings: [],
      falsePositivesFiltered: filteredResult.removedCount,
      customDecorators: [],
      pathAliasesResolved
      };
      }
    • Apply transformation rules to an AST.

      This method is responsible for:

      1. Applying transformation rules to the AST
      2. Collecting metadata about the transformation
      3. Detecting syntax errors after transformation
      4. Restoring path aliases if configured (config.pathAliases.preserveAliases)

      Error Handling:

      • If transformation fails, return a result with modified=false and error message
      • Never throw errors from this method
      • Syntax errors after transformation should be reported in result.metadata.syntaxErrors

      Performance:

      • Target: <1s per file for typical transformations
      • Log warning if transformation takes >30s

      Parameters

      Returns Promise<ParserTransformResult>

      Promise resolving to transformation result with metadata

      async transform(ast: AST, rules: TransformRule[]): Promise<TransformResult> {
      const startTime = Date.now();
      let modified = false;
      let changeCount = 0;

      for (const rule of rules) {
      const context = { sourceFile: ast.sourceFile, project: this.project, type: 'transform' };
      const result = await rule.transform(context);
      if (result.modified) {
      modified = true;
      changeCount += result.changeCount || 0;
      }
      }

      // Detect syntax errors after transformation
      const postDiagnostics = ast.sourceFile.getPreEmitDiagnostics();
      const syntaxErrors = postDiagnostics.map(d => d.getMessageText().toString());

      return {
      ruleId: rules.map(r => r.id).join(','),
      modified,
      message: modified ? 'Transformation succeeded' : 'No changes needed',
      changeCount,
      metadata: {
      duration: Date.now() - startTime,
      syntaxErrors,
      parserStrategy: this.name,
      falsePositivesFiltered: ast.falsePositivesFiltered,
      pathAliasesResolved: ast.pathAliasesResolved
      }
      };
      }