diff --git a/packages/core/schematics/migrations/google3/BUILD.bazel b/packages/core/schematics/migrations/google3/BUILD.bazel index 0bc9d7a0a9a..bd21e587de1 100644 --- a/packages/core/schematics/migrations/google3/BUILD.bazel +++ b/packages/core/schematics/migrations/google3/BUILD.bazel @@ -6,7 +6,6 @@ ts_library( tsconfig = "//packages/core/schematics:tsconfig.json", visibility = ["//packages/core/schematics/test/google3:__pkg__"], deps = [ - "//packages/compiler", "//packages/core/schematics/migrations/activated-route-snapshot-fragment", "//packages/core/schematics/migrations/can-activate-with-redirect-to", "//packages/core/schematics/migrations/dynamic-queries", diff --git a/packages/core/schematics/migrations/google3/explicitQueryTimingRule.ts b/packages/core/schematics/migrations/google3/explicitQueryTimingRule.ts index 35f9b918c01..b80b49f5680 100644 --- a/packages/core/schematics/migrations/google3/explicitQueryTimingRule.ts +++ b/packages/core/schematics/migrations/google3/explicitQueryTimingRule.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import * as compiler from '@angular/compiler'; import {Replacement, RuleFailure, Rules} from 'tslint'; import * as ts from 'typescript'; @@ -50,7 +49,7 @@ export class Rule extends Rules.TypedRule { }); const queries = resolvedQueries.get(sourceFile); - const usageStrategy = new QueryUsageStrategy(classMetadata, typeChecker, compiler); + const usageStrategy = new QueryUsageStrategy(classMetadata, typeChecker); // No queries detected for the given source file. if (!queries) { diff --git a/packages/core/schematics/migrations/google3/noTemplateVariableAssignmentRule.ts b/packages/core/schematics/migrations/google3/noTemplateVariableAssignmentRule.ts index c22a34b9b19..99a4a521595 100644 --- a/packages/core/schematics/migrations/google3/noTemplateVariableAssignmentRule.ts +++ b/packages/core/schematics/migrations/google3/noTemplateVariableAssignmentRule.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import * as compiler from '@angular/compiler'; import {RuleFailure, Rules} from 'tslint'; import * as ts from 'typescript'; @@ -35,7 +34,7 @@ export class Rule extends Rules.TypedRule { // template variables. resolvedTemplates.forEach(template => { const filePath = template.filePath; - const nodes = analyzeResolvedTemplate(template, compiler); + const nodes = analyzeResolvedTemplate(template); const templateFile = template.inline ? sourceFile : createHtmlSourceFile(filePath, template.content); diff --git a/packages/core/schematics/migrations/router-link-empty-expression/analyze_template.ts b/packages/core/schematics/migrations/router-link-empty-expression/analyze_template.ts index 85f90eef356..83052259b1a 100644 --- a/packages/core/schematics/migrations/router-link-empty-expression/analyze_template.ts +++ b/packages/core/schematics/migrations/router-link-empty-expression/analyze_template.ts @@ -6,23 +6,21 @@ * found in the LICENSE file at https://angular.io/license */ -import type {TmplAstBoundAttribute} from '@angular/compiler'; +import {TmplAstBoundAttribute} from '@angular/compiler'; import {ResolvedTemplate} from '../../utils/ng_component_template'; import {parseHtmlGracefully} from '../../utils/parse_html'; import {RouterLinkEmptyExprVisitor} from './angular/html_routerlink_empty_expr_visitor'; -export function analyzeResolvedTemplate( - template: ResolvedTemplate, - compilerModule: typeof import('@angular/compiler')): TmplAstBoundAttribute[]|null { - const templateNodes = parseHtmlGracefully(template.content, template.filePath, compilerModule); +export function analyzeResolvedTemplate(template: ResolvedTemplate): TmplAstBoundAttribute[]|null { + const templateNodes = parseHtmlGracefully(template.content, template.filePath); if (!templateNodes) { return null; } - const visitor = new RouterLinkEmptyExprVisitor(compilerModule); + const visitor = new RouterLinkEmptyExprVisitor(); // Analyze the Angular Render3 HTML AST and collect all template variable assignments. visitor.visitAll(templateNodes); diff --git a/packages/core/schematics/migrations/router-link-empty-expression/angular/html_routerlink_empty_expr_visitor.ts b/packages/core/schematics/migrations/router-link-empty-expression/angular/html_routerlink_empty_expr_visitor.ts index 011344aa76f..152adef952c 100644 --- a/packages/core/schematics/migrations/router-link-empty-expression/angular/html_routerlink_empty_expr_visitor.ts +++ b/packages/core/schematics/migrations/router-link-empty-expression/angular/html_routerlink_empty_expr_visitor.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import type {TmplAstBoundAttribute, TmplAstElement, TmplAstTemplate} from '@angular/compiler'; +import {ASTWithSource, EmptyExpr, TmplAstBoundAttribute, TmplAstElement, TmplAstTemplate} from '@angular/compiler'; import {TemplateAstVisitor} from '../../../utils/template_ast_visitor'; /** @@ -27,8 +27,8 @@ export class RouterLinkEmptyExprVisitor extends TemplateAstVisitor { } override visitBoundAttribute(node: TmplAstBoundAttribute) { - if (node.name === 'routerLink' && node.value instanceof this.compilerModule.ASTWithSource && - node.value.ast instanceof this.compilerModule.EmptyExpr) { + if (node.name === 'routerLink' && node.value instanceof ASTWithSource && + node.value.ast instanceof EmptyExpr) { this.emptyRouterLinkExpressions.push(node); } } diff --git a/packages/core/schematics/migrations/router-link-empty-expression/index.ts b/packages/core/schematics/migrations/router-link-empty-expression/index.ts index 641220f685f..ae5e66a034b 100644 --- a/packages/core/schematics/migrations/router-link-empty-expression/index.ts +++ b/packages/core/schematics/migrations/router-link-empty-expression/index.ts @@ -8,9 +8,8 @@ import {logging, normalize} from '@angular-devkit/core'; import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics'; -import type {TmplAstBoundAttribute} from '@angular/compiler'; +import {EmptyExpr, TmplAstBoundAttribute} from '@angular/compiler'; import {relative} from 'path'; -import {loadEsmModule} from '../../utils/load_esm'; import {NgComponentTemplateVisitor, ResolvedTemplate} from '../../utils/ng_component_template'; import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths'; @@ -45,20 +44,8 @@ export default function(): Rule { 'Could not find any tsconfig file. Cannot check templates for empty routerLinks.'); } - let compilerModule; - try { - // Load ESM `@angular/compiler` using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - compilerModule = await loadEsmModule('@angular/compiler'); - } catch (e) { - throw new SchematicsException( - `Unable to load the '@angular/compiler' package. Details: ${e.message}`); - } - for (const tsconfigPath of [...buildPaths, ...testPaths]) { - runEmptyRouterLinkExpressionMigration( - tree, tsconfigPath, basePath, context.logger, compilerModule); + runEmptyRouterLinkExpressionMigration(tree, tsconfigPath, basePath, context.logger); } }; } @@ -68,8 +55,7 @@ export default function(): Rule { * which templates received updates. */ function runEmptyRouterLinkExpressionMigration( - tree: Tree, tsconfigPath: string, basePath: string, logger: Logger, - compilerModule: typeof import('@angular/compiler')) { + tree: Tree, tsconfigPath: string, basePath: string, logger: Logger) { const {program} = createMigrationProgram(tree, tsconfigPath, basePath); const typeChecker = program.getTypeChecker(); const templateVisitor = new NgComponentTemplateVisitor(typeChecker); @@ -80,15 +66,13 @@ function runEmptyRouterLinkExpressionMigration( sourceFiles.forEach(sourceFile => templateVisitor.visitNode(sourceFile)); const {resolvedTemplates} = templateVisitor; - fixEmptyRouterlinks(resolvedTemplates, tree, logger, compilerModule); + fixEmptyRouterlinks(resolvedTemplates, tree, logger); } -function fixEmptyRouterlinks( - resolvedTemplates: ResolvedTemplate[], tree: Tree, logger: Logger, - compilerModule: typeof import('@angular/compiler')) { +function fixEmptyRouterlinks(resolvedTemplates: ResolvedTemplate[], tree: Tree, logger: Logger) { const basePath = process.cwd(); const collectedFixes: string[] = []; - const fixesByFile = getFixesByFile(resolvedTemplates, compilerModule); + const fixesByFile = getFixesByFile(resolvedTemplates); for (const [absFilePath, templateFixes] of fixesByFile) { const treeFilePath = relative(normalize(basePath), normalize(absFilePath)); @@ -132,12 +116,10 @@ function fixEmptyRouterlinks( /** * Returns fixes for nodes in templates which contain empty routerLink assignments, grouped by file. */ -function getFixesByFile( - templates: ResolvedTemplate[], - compilerModule: typeof import('@angular/compiler')): Map { +function getFixesByFile(templates: ResolvedTemplate[]): Map { const fixesByFile = new Map(); for (const template of templates) { - const templateFix = fixEmptyRouterlinksInTemplate(template, compilerModule); + const templateFix = fixEmptyRouterlinksInTemplate(template); if (templateFix === null) { continue; } @@ -159,10 +141,8 @@ function getFixesByFile( return fixesByFile; } -function fixEmptyRouterlinksInTemplate( - template: ResolvedTemplate, compilerModule: typeof import('@angular/compiler')): FixedTemplate| - null { - const emptyRouterlinkExpressions = analyzeResolvedTemplate(template, compilerModule); +function fixEmptyRouterlinksInTemplate(template: ResolvedTemplate): FixedTemplate|null { + const emptyRouterlinkExpressions = analyzeResolvedTemplate(template); if (!emptyRouterlinkExpressions || emptyRouterlinkExpressions.length === 0) { return null; diff --git a/packages/core/schematics/migrations/static-queries/index.ts b/packages/core/schematics/migrations/static-queries/index.ts index 8d447d90335..72c90c2925b 100644 --- a/packages/core/schematics/migrations/static-queries/index.ts +++ b/packages/core/schematics/migrations/static-queries/index.ts @@ -11,7 +11,6 @@ import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit import {relative} from 'path'; import * as ts from 'typescript'; -import {loadEsmModule} from '../../utils/load_esm'; import {NgComponentTemplateVisitor} from '../../utils/ng_component_template'; import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths'; import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host'; @@ -70,21 +69,9 @@ async function runMigration(tree: Tree, context: SchematicContext) { } } - let compilerModule; - try { - // Load ESM `@angular/compiler` using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - compilerModule = await loadEsmModule('@angular/compiler'); - } catch (e) { - throw new SchematicsException( - `Unable to load the '@angular/compiler' package. Details: ${e.message}`); - } - if (buildProjects.size) { for (let project of Array.from(buildProjects.values())) { - failures.push( - ...await runStaticQueryMigration(tree, project, strategy, logger, compilerModule)); + failures.push(...await runStaticQueryMigration(tree, project, strategy, logger)); } } @@ -93,8 +80,8 @@ async function runMigration(tree: Tree, context: SchematicContext) { for (const tsconfigPath of testPaths) { const project = await analyzeProject(tree, tsconfigPath, basePath, analyzedFiles, logger); if (project) { - failures.push(...await runStaticQueryMigration( - tree, project, SELECTED_STRATEGY.TESTS, logger, compilerModule)); + failures.push( + ...await runStaticQueryMigration(tree, project, SELECTED_STRATEGY.TESTS, logger)); } } @@ -163,8 +150,7 @@ function analyzeProject( */ async function runStaticQueryMigration( tree: Tree, project: AnalyzedProject, selectedStrategy: SELECTED_STRATEGY, - logger: logging.LoggerApi, - compilerModule: typeof import('@angular/compiler')): Promise { + logger: logging.LoggerApi): Promise { const {sourceFiles, typeChecker, host, queryVisitor, tsconfigPath, basePath} = project; const printer = ts.createPrinter(); const failureMessages: string[] = []; @@ -191,11 +177,11 @@ async function runStaticQueryMigration( let strategy: TimingStrategy; if (selectedStrategy === SELECTED_STRATEGY.USAGE) { - strategy = new QueryUsageStrategy(classMetadata, typeChecker, compilerModule); + strategy = new QueryUsageStrategy(classMetadata, typeChecker); } else if (selectedStrategy === SELECTED_STRATEGY.TESTS) { strategy = new QueryTestStrategy(); } else { - strategy = new QueryTemplateStrategy(tsconfigPath, classMetadata, host, compilerModule); + strategy = new QueryTemplateStrategy(tsconfigPath, classMetadata, host); } try { diff --git a/packages/core/schematics/migrations/static-queries/strategies/template_strategy/template_strategy.ts b/packages/core/schematics/migrations/static-queries/strategies/template_strategy/template_strategy.ts index 9b1608e984f..2d3f83e82d1 100644 --- a/packages/core/schematics/migrations/static-queries/strategies/template_strategy/template_strategy.ts +++ b/packages/core/schematics/migrations/static-queries/strategies/template_strategy/template_strategy.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import type {AotCompiler, CompileDirectiveMetadata, CompileMetadataResolver, CompileNgModuleMetadata, CompileStylesheetMetadata, ElementAst, EmbeddedTemplateAst, NgAnalyzedModules, QueryMatch, StaticSymbol, TemplateAst} from '@angular/compiler'; +import {AotCompiler, CompileDirectiveMetadata, CompileMetadataResolver, CompileNgModuleMetadata, CompileStylesheetMetadata, ElementAst, EmbeddedTemplateAst, NgAnalyzedModules, QueryMatch, StaticSymbol, TemplateAst} from '@angular/compiler'; import {createProgram, Diagnostic, readConfiguration} from '@angular/compiler-cli'; import {resolve} from 'path'; import * as ts from 'typescript'; @@ -25,7 +25,7 @@ export class QueryTemplateStrategy implements TimingStrategy { constructor( private projectPath: string, private classMetadata: ClassMetadataMap, - private host: ts.CompilerHost, private compilerModule: typeof import('@angular/compiler')) {} + private host: ts.CompilerHost) {} /** * Sets up the template strategy by creating the AngularCompilerProgram. Returns false if @@ -54,8 +54,8 @@ export class QueryTemplateStrategy implements TimingStrategy { // breaks the analysis of the project because we instantiate a standalone AOT compiler // program which does not contain the custom logic by the Angular CLI Webpack compiler plugin. const directiveNormalizer = this.metadataResolver!['_directiveNormalizer']; - directiveNormalizer['_normalizeStylesheet'] = (metadata: CompileStylesheetMetadata) => { - return new this.compilerModule.CompileStylesheetMetadata( + directiveNormalizer['_normalizeStylesheet'] = function(metadata: CompileStylesheetMetadata) { + return new CompileStylesheetMetadata( {styles: metadata.styles, styleUrls: [], moduleUrl: metadata.moduleUrl!}); }; @@ -83,7 +83,7 @@ export class QueryTemplateStrategy implements TimingStrategy { } const parsedTemplate = this._parseTemplate(metadata, ngModule); - const queryTimingMap = this.findStaticQueryIds(parsedTemplate); + const queryTimingMap = findStaticQueryIds(parsedTemplate); const {staticQueryIds} = staticViewQueryIds(queryTimingMap); metadata.viewQueries.forEach((query, index) => { @@ -187,40 +187,6 @@ export class QueryTemplateStrategy implements TimingStrategy { private _getViewQueryUniqueKey(filePath: string, className: string, propName: string) { return `${resolve(filePath)}#${className}-${propName}`; } - - /** Figures out which queries are static and which ones are dynamic. */ - private findStaticQueryIds( - nodes: TemplateAst[], result = new Map()): - Map { - nodes.forEach((node) => { - const staticQueryIds = new Set(); - const dynamicQueryIds = new Set(); - let queryMatches: QueryMatch[] = undefined!; - if (node instanceof this.compilerModule.ElementAst) { - this.findStaticQueryIds(node.children, result); - node.children.forEach((child) => { - const childData = result.get(child)!; - childData.staticQueryIds.forEach(queryId => staticQueryIds.add(queryId)); - childData.dynamicQueryIds.forEach(queryId => dynamicQueryIds.add(queryId)); - }); - queryMatches = node.queryMatches; - } else if (node instanceof this.compilerModule.EmbeddedTemplateAst) { - this.findStaticQueryIds(node.children, result); - node.children.forEach((child) => { - const childData = result.get(child)!; - childData.staticQueryIds.forEach(queryId => dynamicQueryIds.add(queryId)); - childData.dynamicQueryIds.forEach(queryId => dynamicQueryIds.add(queryId)); - }); - queryMatches = node.queryMatches; - } - if (queryMatches) { - queryMatches.forEach((match) => staticQueryIds.add(match.queryId)); - } - dynamicQueryIds.forEach(queryId => staticQueryIds.delete(queryId)); - result.set(node, {staticQueryIds, dynamicQueryIds}); - }); - return result; - } } interface StaticAndDynamicQueryIds { @@ -228,6 +194,40 @@ interface StaticAndDynamicQueryIds { dynamicQueryIds: Set; } +/** Figures out which queries are static and which ones are dynamic. */ +function findStaticQueryIds( + nodes: TemplateAst[], result = new Map()): + Map { + nodes.forEach((node) => { + const staticQueryIds = new Set(); + const dynamicQueryIds = new Set(); + let queryMatches: QueryMatch[] = undefined!; + if (node instanceof ElementAst) { + findStaticQueryIds(node.children, result); + node.children.forEach((child) => { + const childData = result.get(child)!; + childData.staticQueryIds.forEach(queryId => staticQueryIds.add(queryId)); + childData.dynamicQueryIds.forEach(queryId => dynamicQueryIds.add(queryId)); + }); + queryMatches = node.queryMatches; + } else if (node instanceof EmbeddedTemplateAst) { + findStaticQueryIds(node.children, result); + node.children.forEach((child) => { + const childData = result.get(child)!; + childData.staticQueryIds.forEach(queryId => dynamicQueryIds.add(queryId)); + childData.dynamicQueryIds.forEach(queryId => dynamicQueryIds.add(queryId)); + }); + queryMatches = node.queryMatches; + } + if (queryMatches) { + queryMatches.forEach((match) => staticQueryIds.add(match.queryId)); + } + dynamicQueryIds.forEach(queryId => staticQueryIds.delete(queryId)); + result.set(node, {staticQueryIds, dynamicQueryIds}); + }); + return result; +} + /** Splits queries into static and dynamic. */ function staticViewQueryIds(nodeStaticQueryIds: Map): StaticAndDynamicQueryIds { diff --git a/packages/core/schematics/migrations/static-queries/strategies/usage_strategy/template_usage_visitor.ts b/packages/core/schematics/migrations/static-queries/strategies/usage_strategy/template_usage_visitor.ts index 6035592ab7c..118308bd24a 100644 --- a/packages/core/schematics/migrations/static-queries/strategies/usage_strategy/template_usage_visitor.ts +++ b/packages/core/schematics/migrations/static-queries/strategies/usage_strategy/template_usage_visitor.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import type {ParseSourceSpan, PropertyRead, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstTemplate} from '@angular/compiler'; +import {ImplicitReceiver, ParseSourceSpan, PropertyRead, RecursiveAstVisitor, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAstElement, TmplAstNode, TmplAstTemplate} from '@angular/compiler'; import {TemplateAstVisitor} from '../../../../utils/template_ast_visitor'; /** @@ -15,33 +15,10 @@ import {TemplateAstVisitor} from '../../../../utils/template_ast_visitor'; */ export class TemplateUsageVisitor extends TemplateAstVisitor { private hasQueryTemplateReference = false; - private expressionAstVisitor; + private expressionAstVisitor = new ExpressionAstVisitor(this.queryPropertyName); - constructor( - public queryPropertyName: string, compilerModule: typeof import('@angular/compiler')) { - super(compilerModule); - - // AST visitor that checks if the given expression contains property reads that - // refer to the specified query property name. - // This class must be defined within the template visitor due to the need to extend from a class - // value found within `@angular/compiler` which is dynamically imported and provided to the - // visitor. - this.expressionAstVisitor = new (class extends compilerModule.RecursiveAstVisitor { - hasQueryPropertyRead = false; - - - override visitPropertyRead(node: PropertyRead, span: ParseSourceSpan): any { - // The receiver of the property read needs to be "implicit" as queries are accessed - // from the component instance and not from other objects. - if (node.receiver instanceof compilerModule.ImplicitReceiver && - node.name === queryPropertyName) { - this.hasQueryPropertyRead = true; - return; - } - - super.visitPropertyRead(node, span); - } - })(); + constructor(public queryPropertyName: string) { + super(); } /** Checks whether the given query is statically accessed within the specified HTML nodes. */ @@ -92,3 +69,26 @@ export class TemplateUsageVisitor extends TemplateAstVisitor { node.handler.visit(this.expressionAstVisitor, node.handlerSpan); } } + +/** + * AST visitor that checks if the given expression contains property reads that + * refer to the specified query property name. + */ +class ExpressionAstVisitor extends RecursiveAstVisitor { + hasQueryPropertyRead = false; + + constructor(private queryPropertyName: string) { + super(); + } + + override visitPropertyRead(node: PropertyRead, span: ParseSourceSpan): any { + // The receiver of the property read needs to be "implicit" as queries are accessed + // from the component instance and not from other objects. + if (node.receiver instanceof ImplicitReceiver && node.name === this.queryPropertyName) { + this.hasQueryPropertyRead = true; + return; + } + + super.visitPropertyRead(node, span); + } +} diff --git a/packages/core/schematics/migrations/static-queries/strategies/usage_strategy/usage_strategy.ts b/packages/core/schematics/migrations/static-queries/strategies/usage_strategy/usage_strategy.ts index d094de00d72..367381e4896 100644 --- a/packages/core/schematics/migrations/static-queries/strategies/usage_strategy/usage_strategy.ts +++ b/packages/core/schematics/migrations/static-queries/strategies/usage_strategy/usage_strategy.ts @@ -35,9 +35,7 @@ const STATIC_QUERY_LIFECYCLE_HOOKS = { * this strategy here: https://hackmd.io/s/Hymvc2OKE */ export class QueryUsageStrategy implements TimingStrategy { - constructor( - private classMetadata: ClassMetadataMap, private typeChecker: ts.TypeChecker, - private compilerModule: typeof import('@angular/compiler')) {} + constructor(private classMetadata: ClassMetadataMap, private typeChecker: ts.TypeChecker) {} setup() {} @@ -103,9 +101,8 @@ export class QueryUsageStrategy implements TimingStrategy { // can be marked as static. if (classMetadata.template && hasPropertyNameText(query.property!.name)) { const template = classMetadata.template; - const parsedHtml = - parseHtmlGracefully(template.content, template.filePath, this.compilerModule); - const htmlVisitor = new TemplateUsageVisitor(query.property!.name.text, this.compilerModule); + const parsedHtml = parseHtmlGracefully(template.content, template.filePath); + const htmlVisitor = new TemplateUsageVisitor(query.property!.name.text); if (parsedHtml && htmlVisitor.isQueryUsedStatically(parsedHtml)) { return ResolvedUsage.SYNCHRONOUS; diff --git a/packages/core/schematics/migrations/template-var-assignment/analyze_template.ts b/packages/core/schematics/migrations/template-var-assignment/analyze_template.ts index a74aba88d22..a4a18c41f8b 100644 --- a/packages/core/schematics/migrations/template-var-assignment/analyze_template.ts +++ b/packages/core/schematics/migrations/template-var-assignment/analyze_template.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import type {PropertyWrite} from '@angular/compiler'; +import {PropertyWrite} from '@angular/compiler'; import {ResolvedTemplate} from '../../utils/ng_component_template'; import {parseHtmlGracefully} from '../../utils/parse_html'; import {HtmlVariableAssignmentVisitor} from './angular/html_variable_assignment_visitor'; @@ -21,16 +21,15 @@ export interface TemplateVariableAssignment { * Analyzes a given resolved template by looking for property assignments to local * template variables within bound events. */ -export function analyzeResolvedTemplate( - template: ResolvedTemplate, - compilerModule: typeof import('@angular/compiler')): TemplateVariableAssignment[]|null { - const templateNodes = parseHtmlGracefully(template.content, template.filePath, compilerModule); +export function analyzeResolvedTemplate(template: ResolvedTemplate): TemplateVariableAssignment[]| + null { + const templateNodes = parseHtmlGracefully(template.content, template.filePath); if (!templateNodes) { return null; } - const visitor = new HtmlVariableAssignmentVisitor(compilerModule); + const visitor = new HtmlVariableAssignmentVisitor(); // Analyze the Angular Render3 HTML AST and collect all template variable assignments. visitor.visitAll(templateNodes); diff --git a/packages/core/schematics/migrations/template-var-assignment/angular/html_variable_assignment_visitor.ts b/packages/core/schematics/migrations/template-var-assignment/angular/html_variable_assignment_visitor.ts index afc443e1544..d93b6a4f47c 100644 --- a/packages/core/schematics/migrations/template-var-assignment/angular/html_variable_assignment_visitor.ts +++ b/packages/core/schematics/migrations/template-var-assignment/angular/html_variable_assignment_visitor.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import type {ImplicitReceiver, ParseSourceSpan, PropertyWrite, RecursiveAstVisitor, TmplAstBoundEvent, TmplAstElement, TmplAstTemplate, TmplAstVariable} from '@angular/compiler'; +import {ImplicitReceiver, ParseSourceSpan, PropertyWrite, RecursiveAstVisitor, TmplAstBoundEvent, TmplAstElement, TmplAstTemplate, TmplAstVariable} from '@angular/compiler'; import {TemplateAstVisitor} from '../../../utils/template_ast_visitor'; @@ -24,35 +24,8 @@ export class HtmlVariableAssignmentVisitor extends TemplateAstVisitor { variableAssignments: TemplateVariableAssignment[] = []; private currentVariables: TmplAstVariable[] = []; - private expressionAstVisitor; - - constructor(compilerModule: typeof import('@angular/compiler')) { - super(compilerModule); - - // AST visitor that resolves all variable assignments within a given expression AST. - // This class must be defined within the template visitor due to the need to extend from a class - // value found within `@angular/compiler` which is dynamically imported and provided to the - // visitor. - this.expressionAstVisitor = new (class extends compilerModule.RecursiveAstVisitor { - constructor( - private variableAssignments: TemplateVariableAssignment[], - private currentVariables: TmplAstVariable[]) { - super(); - } - - override visitPropertyWrite(node: PropertyWrite, span: ParseSourceSpan) { - if (node.receiver instanceof compilerModule.ImplicitReceiver && - this.currentVariables.some(v => v.name === node.name)) { - this.variableAssignments.push({ - node: node, - start: span.start.offset, - end: span.end.offset, - }); - } - super.visitPropertyWrite(node, span); - } - })(this.variableAssignments, this.currentVariables); - } + private expressionAstVisitor = + new ExpressionAstVisitor(this.variableAssignments, this.currentVariables); override visitElement(element: TmplAstElement): void { this.visitAll(element.outputs); @@ -84,3 +57,24 @@ export class HtmlVariableAssignmentVisitor extends TemplateAstVisitor { node.handler.visit(this.expressionAstVisitor, node.handlerSpan); } } + +/** AST visitor that resolves all variable assignments within a given expression AST. */ +class ExpressionAstVisitor extends RecursiveAstVisitor { + constructor( + private variableAssignments: TemplateVariableAssignment[], + private currentVariables: TmplAstVariable[]) { + super(); + } + + override visitPropertyWrite(node: PropertyWrite, span: ParseSourceSpan) { + if (node.receiver instanceof ImplicitReceiver && + this.currentVariables.some(v => v.name === node.name)) { + this.variableAssignments.push({ + node: node, + start: span.start.offset, + end: span.end.offset, + }); + } + super.visitPropertyWrite(node, span); + } +} diff --git a/packages/core/schematics/migrations/template-var-assignment/index.ts b/packages/core/schematics/migrations/template-var-assignment/index.ts index 4e5a228a3d1..cf00cc7cf7e 100644 --- a/packages/core/schematics/migrations/template-var-assignment/index.ts +++ b/packages/core/schematics/migrations/template-var-assignment/index.ts @@ -10,7 +10,6 @@ import {logging, normalize} from '@angular-devkit/core'; import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics'; import {relative} from 'path'; -import {loadEsmModule} from '../../utils/load_esm'; import {NgComponentTemplateVisitor} from '../../utils/ng_component_template'; import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths'; import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host'; @@ -34,20 +33,8 @@ export default function(): Rule { 'assignments.'); } - let compilerModule; - try { - // Load ESM `@angular/compiler` using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - compilerModule = await loadEsmModule('@angular/compiler'); - } catch (e) { - throw new SchematicsException( - `Unable to load the '@angular/compiler' package. Details: ${e.message}`); - } - for (const tsconfigPath of [...buildPaths, ...testPaths]) { - runTemplateVariableAssignmentCheck( - tree, tsconfigPath, basePath, context.logger, compilerModule); + runTemplateVariableAssignmentCheck(tree, tsconfigPath, basePath, context.logger); } }; } @@ -57,8 +44,7 @@ export default function(): Rule { * if values are assigned to template variables within output bindings. */ function runTemplateVariableAssignmentCheck( - tree: Tree, tsconfigPath: string, basePath: string, logger: Logger, - compilerModule: typeof import('@angular/compiler')) { + tree: Tree, tsconfigPath: string, basePath: string, logger: Logger) { const {program} = createMigrationProgram(tree, tsconfigPath, basePath); const typeChecker = program.getTypeChecker(); const templateVisitor = new NgComponentTemplateVisitor(typeChecker); @@ -75,7 +61,7 @@ function runTemplateVariableAssignmentCheck( // template variables. resolvedTemplates.forEach(template => { const filePath = template.filePath; - const nodes = analyzeResolvedTemplate(template, compilerModule); + const nodes = analyzeResolvedTemplate(template); if (!nodes) { return; diff --git a/packages/core/schematics/migrations/undecorated-classes-with-di/create_ngc_program.ts b/packages/core/schematics/migrations/undecorated-classes-with-di/create_ngc_program.ts index 502ad8403fb..ffa6f91db02 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-di/create_ngc_program.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-di/create_ngc_program.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import type {AotCompiler} from '@angular/compiler'; +import {AotCompiler} from '@angular/compiler'; import {CompilerHost, createProgram, readConfiguration} from '@angular/compiler-cli'; import * as ts from 'typescript'; diff --git a/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/convert_directive_metadata.ts b/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/convert_directive_metadata.ts index ea17cdfab74..597dfe44c90 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/convert_directive_metadata.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/convert_directive_metadata.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import type {StaticSymbol} from '@angular/compiler'; +import {StaticSymbol} from '@angular/compiler'; import * as ts from 'typescript'; /** Error that will be thrown if an unexpected value needs to be converted. */ @@ -17,8 +17,7 @@ export class UnexpectedMetadataValueError extends Error {} * if metadata cannot be cleanly converted. */ export function convertDirectiveMetadataToExpression( - compilerModule: typeof import('@angular/compiler'), metadata: any, - resolveSymbolImport: (symbol: StaticSymbol) => string | null, + metadata: any, resolveSymbolImport: (symbol: StaticSymbol) => string | null, createImport: (moduleName: string, name: string) => ts.Expression, convertProperty?: (key: string, value: any) => ts.Expression | null): ts.Expression { if (typeof metadata === 'string') { @@ -26,7 +25,7 @@ export function convertDirectiveMetadataToExpression( } else if (Array.isArray(metadata)) { return ts.createArrayLiteral(metadata.map( el => convertDirectiveMetadataToExpression( - compilerModule, el, resolveSymbolImport, createImport, convertProperty))); + el, resolveSymbolImport, createImport, convertProperty))); } else if (typeof metadata === 'number') { return ts.createNumericLiteral(metadata.toString()); } else if (typeof metadata === 'boolean') { @@ -39,7 +38,7 @@ export function convertDirectiveMetadataToExpression( // In case there is a static symbol object part of the metadata, try to resolve // the import expression of the symbol. If no import path could be resolved, an // error will be thrown as the symbol cannot be converted into TypeScript AST. - if (metadata instanceof compilerModule.StaticSymbol) { + if (metadata instanceof StaticSymbol) { const resolvedImport = resolveSymbolImport(metadata); if (resolvedImport === null) { throw new UnexpectedMetadataValueError(); @@ -64,7 +63,7 @@ export function convertDirectiveMetadataToExpression( // the resolved metadata value into a TypeScript expression. if (propertyValue === null) { propertyValue = convertDirectiveMetadataToExpression( - compilerModule, metadataValue, resolveSymbolImport, createImport, convertProperty); + metadataValue, resolveSymbolImport, createImport, convertProperty); } literalProperties.push(ts.createPropertyAssignment(getPropertyName(key), propertyValue)); diff --git a/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/decorator_rewriter.ts b/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/decorator_rewriter.ts index e1daadb17eb..4696448aa32 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/decorator_rewriter.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/decorator_rewriter.ts @@ -6,7 +6,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import type {AotCompiler} from '@angular/compiler'; +import {AotCompiler} from '@angular/compiler'; import {PartialEvaluator} from '@angular/compiler-cli/src/ngtsc/partial_evaluator'; import * as ts from 'typescript'; diff --git a/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/import_rewrite_visitor.ts b/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/import_rewrite_visitor.ts index 4a1fff12daf..401d04076b9 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/import_rewrite_visitor.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-di/decorator_rewrite/import_rewrite_visitor.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import type {AotCompilerHost} from '@angular/compiler'; +import {AotCompilerHost} from '@angular/compiler'; import {dirname, resolve} from 'path'; import * as ts from 'typescript'; diff --git a/packages/core/schematics/migrations/undecorated-classes-with-di/index.ts b/packages/core/schematics/migrations/undecorated-classes-with-di/index.ts index ed2ea8b804a..4a0ba73c221 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-di/index.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-di/index.ts @@ -8,14 +8,13 @@ import {logging} from '@angular-devkit/core'; import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics'; -import type {AotCompiler} from '@angular/compiler'; +import {AotCompiler} from '@angular/compiler'; import {Diagnostic as NgDiagnostic} from '@angular/compiler-cli'; import {PartialEvaluator} from '@angular/compiler-cli/src/ngtsc/partial_evaluator'; import {TypeScriptReflectionHost} from '@angular/compiler-cli/src/ngtsc/reflection'; import {relative} from 'path'; import * as ts from 'typescript'; -import {loadEsmModule} from '../../utils/load_esm'; import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths'; import {canMigrateFile, createMigrationCompilerHost} from '../../utils/typescript/compiler_host'; @@ -45,20 +44,8 @@ export default function(): Rule { 'undecorated base classes which use DI.'); } - let compilerModule; - try { - // Load ESM `@angular/compiler` using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - compilerModule = await loadEsmModule('@angular/compiler'); - } catch (e) { - throw new SchematicsException( - `Unable to load the '@angular/compiler' package. Details: ${e.message}`); - } - for (const tsconfigPath of buildPaths) { - const result = - runUndecoratedClassesMigration(tree, tsconfigPath, basePath, ctx.logger, compilerModule); + const result = runUndecoratedClassesMigration(tree, tsconfigPath, basePath, ctx.logger); failures.push(...result.failures); programError = programError || !!result.programError; } @@ -83,9 +70,8 @@ export default function(): Rule { } function runUndecoratedClassesMigration( - tree: Tree, tsconfigPath: string, basePath: string, logger: logging.LoggerApi, - compilerModule: typeof import('@angular/compiler')): - {failures: string[], programError?: boolean} { + tree: Tree, tsconfigPath: string, basePath: string, + logger: logging.LoggerApi): {failures: string[], programError?: boolean} { const failures: string[] = []; const programData = gracefullyCreateProgram(tree, basePath, tsconfigPath, logger); @@ -106,8 +92,8 @@ function runUndecoratedClassesMigration( sourceFiles.forEach(sourceFile => declarationCollector.visitNode(sourceFile)); const {decoratedDirectives, decoratedProviders, undecoratedDeclarations} = declarationCollector; - const transform = new UndecoratedClassesTransform( - typeChecker, compiler, partialEvaluator, getUpdateRecorder, compilerModule); + const transform = + new UndecoratedClassesTransform(typeChecker, compiler, partialEvaluator, getUpdateRecorder); const updateRecorders = new Map(); // Run the migrations for decorated providers and both decorated and undecorated diff --git a/packages/core/schematics/migrations/undecorated-classes-with-di/transform.ts b/packages/core/schematics/migrations/undecorated-classes-with-di/transform.ts index 3a59ebde011..9b3d8f8b20e 100644 --- a/packages/core/schematics/migrations/undecorated-classes-with-di/transform.ts +++ b/packages/core/schematics/migrations/undecorated-classes-with-di/transform.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import type {AotCompiler, AotCompilerHost, CompileMetadataResolver, StaticSymbol, StaticSymbolResolver, SummaryResolver} from '@angular/compiler'; +import {AotCompiler, AotCompilerHost, CompileMetadataResolver, StaticSymbol, StaticSymbolResolver, SummaryResolver} from '@angular/compiler'; import {PartialEvaluator} from '@angular/compiler-cli/src/ngtsc/partial_evaluator'; import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core'; import * as ts from 'typescript'; @@ -58,8 +58,7 @@ export class UndecoratedClassesTransform { constructor( private typeChecker: ts.TypeChecker, private compiler: AotCompiler, private evaluator: PartialEvaluator, - private getUpdateRecorder: (sf: ts.SourceFile) => UpdateRecorder, - private compilerModule: typeof import('@angular/compiler')) { + private getUpdateRecorder: (sf: ts.SourceFile) => UpdateRecorder) { this.symbolResolver = compiler['_symbolResolver']; this.compilerHost = compiler['_host']; this.metadataResolver = compiler['_metadataResolver']; @@ -329,7 +328,7 @@ export class UndecoratedClassesTransform { directiveMetadata: DeclarationMetadata, targetSourceFile: ts.SourceFile): ts.Decorator|null { try { const decoratorExpr = convertDirectiveMetadataToExpression( - this.compilerModule, directiveMetadata.metadata, + directiveMetadata.metadata, staticSymbol => this.compilerHost .fileNameToModuleName(staticSymbol.filePath, targetSourceFile.fileName) diff --git a/packages/core/schematics/utils/load_esm.ts b/packages/core/schematics/utils/load_esm.ts deleted file mode 100644 index 16d3b211894..00000000000 --- a/packages/core/schematics/utils/load_esm.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {URL} from 'url'; - -/** - * This uses a dynamic import to load a module which may be ESM. - * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript - * will currently, unconditionally downlevel dynamic import into a require call. - * require calls cannot load ESM code and will result in a runtime error. To workaround - * this, a Function constructor is used to prevent TypeScript from changing the dynamic import. - * Once TypeScript provides support for keeping the dynamic import this workaround can - * be dropped. - * This is only intended to be used with Angular framework packages. - * - * @param modulePath The path of the module to load. - * @returns A Promise that resolves to the dynamically imported module. - */ -export async function loadEsmModule(modulePath: string|URL): Promise { - const namespaceObject = - (await new Function('modulePath', `return import(modulePath);`)(modulePath)); - - // If it is not ESM then the values needed will be stored in the `default` property. - // TODO_ESM: This can be removed once `@angular/*` packages are ESM only. - if (namespaceObject.default) { - return namespaceObject.default; - } else { - return namespaceObject; - } -} diff --git a/packages/core/schematics/utils/parse_html.ts b/packages/core/schematics/utils/parse_html.ts index 34ddf63b6bc..d7b1997ed0f 100644 --- a/packages/core/schematics/utils/parse_html.ts +++ b/packages/core/schematics/utils/parse_html.ts @@ -6,17 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ -import type {TmplAstNode} from '@angular/compiler'; +import {parseTemplate, TmplAstNode} from '@angular/compiler'; /** * Parses the given HTML content using the Angular compiler. In case the parsing * fails, null is being returned. */ -export function parseHtmlGracefully( - htmlContent: string, filePath: string, - compilerModule: typeof import('@angular/compiler')): TmplAstNode[]|null { +export function parseHtmlGracefully(htmlContent: string, filePath: string): TmplAstNode[]|null { try { - return compilerModule.parseTemplate(htmlContent, filePath).nodes; + return parseTemplate(htmlContent, filePath).nodes; } catch { // Do nothing if the template couldn't be parsed. We don't want to throw any // exception if a template is syntactically not valid. e.g. template could be diff --git a/packages/core/schematics/utils/template_ast_visitor.ts b/packages/core/schematics/utils/template_ast_visitor.ts index c779d88651d..ea630fb7e5a 100644 --- a/packages/core/schematics/utils/template_ast_visitor.ts +++ b/packages/core/schematics/utils/template_ast_visitor.ts @@ -24,15 +24,6 @@ import type {TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstBoundText, TmplAst * interface `Visitor` is not exported. */ export class TemplateAstVisitor implements TmplAstRecursiveVisitor { - /** - * Creates a new Render3 Template AST visitor using an instance of the `@angular/compiler` - * package. Passing in the compiler is required due to the need to dynamically import the - * ESM `@angular/compiler` into a CommonJS schematic. - * - * @param compilerModule The compiler instance that should be used within the visitor. - */ - constructor(protected readonly compilerModule: typeof import('@angular/compiler')) {} - visitElement(element: TmplAstElement): void {} visitTemplate(template: TmplAstTemplate): void {} visitContent(content: TmplAstContent): void {}