diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts index 757c5744a5ed..286de3e60fef 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts @@ -85,8 +85,9 @@ export class PartialComponentLinkerVersion1 metaObj: AstObject, version: string, ): LinkedDefinition { - const meta = this.toR3ComponentMeta(metaObj, version); - return compileComponentFromMetadata(meta, constantPool, makeBindingParser()); + // TODO + const meta = this.toR3ComponentMeta(metaObj as any, version); + return compileComponentFromMetadata(meta, constantPool, makeBindingParser(), []); } /** diff --git a/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts b/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts index 5289aeade34c..d042793fbee0 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/component/src/handler.ts @@ -443,6 +443,7 @@ export class ComponentDecoratorHandler analyze( node: ClassDeclaration, decorator: Readonly, + symbols: string[], ): AnalysisOutput { this.perf.eventCount(PerfEvent.AnalyzeComponent); const containingFile = node.getSourceFile().fileName; @@ -904,6 +905,7 @@ export class ComponentDecoratorHandler explicitlyDeferredTypes, schemas, decorator: (decorator?.node as ts.Decorator | null) ?? null, + symbols, }, diagnostics, }; @@ -958,6 +960,7 @@ export class ComponentDecoratorHandler ngContentSelectors: analysis.template.ngContentSelectors, preserveWhitespaces: analysis.template.preserveWhitespaces ?? false, isExplicitlyDeferred: false, + symbols: analysis.symbols, }); this.resourceRegistry.registerResources(analysis.resources, node); @@ -1602,6 +1605,7 @@ export class ComponentDecoratorHandler analysis: Readonly, resolution: Readonly, pool: ConstantPool, + symbols: string[], ): CompileResult[] { if (analysis.template.errors !== null && analysis.template.errors.length > 0) { return []; @@ -1621,7 +1625,7 @@ export class ComponentDecoratorHandler removeDeferrableTypesFromComponentDecorator(analysis, perComponentDeferredDeps); } - const def = compileComponentFromMetadata(meta, pool, makeBindingParser()); + const def = compileComponentFromMetadata(meta, pool, makeBindingParser(), symbols); const inputTransformFields = compileInputTransformFields(analysis.inputs); const classMetadata = analysis.classMetadata !== null @@ -1728,6 +1732,7 @@ export class ComponentDecoratorHandler analysis: Readonly, resolution: Readonly>, pool: ConstantPool, + symbols: string[], ): CompileResult[] { // In the local compilation mode we can only rely on the information available // within the `@Component.deferredImports` array, because in this mode compiler @@ -1745,7 +1750,7 @@ export class ComponentDecoratorHandler } const fac = compileNgFactoryDefField(toFactoryMetadata(meta, FactoryTarget.Component)); - const def = compileComponentFromMetadata(meta, pool, makeBindingParser()); + const def = compileComponentFromMetadata(meta, pool, makeBindingParser(), symbols); const inputTransformFields = compileInputTransformFields(analysis.inputs); const classMetadata = analysis.classMetadata !== null @@ -1787,6 +1792,7 @@ export class ComponentDecoratorHandler node: ClassDeclaration, analysis: Readonly, resolution: Readonly, + symbols: string[], ): ts.FunctionDeclaration | null { if (analysis.template.errors !== null && analysis.template.errors.length > 0) { return null; @@ -1800,7 +1806,7 @@ export class ComponentDecoratorHandler defer: this.compileDeferBlocks(resolution), }; const fac = compileNgFactoryDefField(toFactoryMetadata(meta, FactoryTarget.Component)); - const def = compileComponentFromMetadata(meta, pool, makeBindingParser()); + const def = compileComponentFromMetadata(meta, pool, makeBindingParser(), symbols); const classMetadata = analysis.classMetadata !== null ? compileComponentClassMetadata(analysis.classMetadata, null).toStmt() diff --git a/packages/compiler-cli/src/ngtsc/annotations/component/src/metadata.ts b/packages/compiler-cli/src/ngtsc/annotations/component/src/metadata.ts index bf0ff585e2cc..40f9fc84f6f3 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/component/src/metadata.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/component/src/metadata.ts @@ -94,6 +94,8 @@ export interface ComponentAnalysisData { rawDeferredImports: ts.Expression | null; resolvedDeferredImports: Reference[] | null; + symbols: string[] | null; + /** * Map of symbol name -> import path for types from `@Component.deferredImports` field. */ diff --git a/packages/compiler-cli/src/ngtsc/annotations/directive/src/handler.ts b/packages/compiler-cli/src/ngtsc/annotations/directive/src/handler.ts index c2e1a181f397..a1ddc82de1b0 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/directive/src/handler.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/directive/src/handler.ts @@ -284,6 +284,7 @@ export class DirectiveDecoratorHandler // Instead, we statically analyze their imports to make a direct determination. assumedToExportProviders: false, isExplicitlyDeferred: false, + symbols: null, }); this.injectableRegistry.registerInjectable(node, { diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/api.ts b/packages/compiler-cli/src/ngtsc/metadata/src/api.ts index 93d998978a7b..7291f144bbe7 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/api.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/src/api.ts @@ -261,6 +261,11 @@ export interface DirectiveMeta extends T2DirectiveMeta, DirectiveTypeCheckMeta { */ deferredImports: Reference[] | null; + /** + * Imported symbols in the module where the directive is declared + */ + symbols: string[] | null; + /** * For standalone components, the list of schemas declared. */ diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/dts.ts b/packages/compiler-cli/src/ngtsc/metadata/src/dts.ts index 33716418f649..815b39c7fc2c 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/dts.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/src/dts.ts @@ -219,6 +219,7 @@ export class DtsMetadataReader implements MetadataReader { // used to increase the accuracy of a diagnostic. preserveWhitespaces: false, isExplicitlyDeferred: false, + symbols: null, }; } diff --git a/packages/compiler-cli/src/ngtsc/transform/src/api.ts b/packages/compiler-cli/src/ngtsc/transform/src/api.ts index 6b75a8b3cfb8..ae49c5e634c5 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/api.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/api.ts @@ -112,7 +112,7 @@ export interface DecoratorHandler { * builds. Any side effects required for compilation (e.g. registration of metadata) should happen * in the `register` phase, which is guaranteed to run even for incremental builds. */ - analyze(node: ClassDeclaration, metadata: Readonly): AnalysisOutput; + analyze(node: ClassDeclaration, metadata: Readonly, symbols: string[]): AnalysisOutput; /** * React to a change in a resource file by updating the `analysis` or `resolution`, under the @@ -141,7 +141,7 @@ export interface DecoratorHandler { * Registration always occurs for a given decorator/class, regardless of whether analysis was * performed directly or whether the analysis results were reused from the previous program. */ - register?(node: ClassDeclaration, analysis: A): void; + register?(node: ClassDeclaration, analysis: A, symbols: string[]): void; /** * Registers information about the decorator for the indexing phase in a @@ -199,6 +199,7 @@ export interface DecoratorHandler { analysis: Readonly, resolution: Readonly, constantPool: ConstantPool, + symbols: string[], ): CompileResult | CompileResult[]; /** @@ -213,6 +214,7 @@ export interface DecoratorHandler { node: ClassDeclaration, analysis: Readonly, resolution: Readonly, + symbols: string[], ): CompileResult | CompileResult[]; /** @@ -222,6 +224,7 @@ export interface DecoratorHandler { node: ClassDeclaration, analysis: Readonly, resolution: Readonly, + symbols: string[], ): ts.FunctionDeclaration | null; /** @@ -233,6 +236,7 @@ export interface DecoratorHandler { analysis: Readonly, resolution: Readonly>, constantPool: ConstantPool, + symbols: string[], ): CompileResult | CompileResult[]; } diff --git a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts index b9d3a2f72ec1..91114259ecbd 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts @@ -170,9 +170,11 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { return; } + const symbols = getImportedSymbols(sf); + const visit = (node: ts.Node): void => { if (this.reflector.isClass(node)) { - this.analyzeClass(node, preanalyze ? promises : null); + this.analyzeClass(node, preanalyze ? promises : null, symbols); } ts.forEachChild(node, visit); }; @@ -245,7 +247,7 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { const symbol = this.makeSymbolForTrait(handler, record.node, priorTrait.analysis); trait = trait.toAnalyzed(priorTrait.analysis, priorTrait.analysisDiagnostics, symbol); if (trait.analysis !== null && trait.handler.register !== undefined) { - trait.handler.register(record.node, trait.analysis); + trait.handler.register(record.node, trait.analysis, []); } } else if (priorTrait.state === TraitState.Skipped) { trait = trait.toSkipped(); @@ -411,7 +413,11 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { return symbol; } - private analyzeClass(clazz: ClassDeclaration, preanalyzeQueue: Promise[] | null): void { + private analyzeClass( + clazz: ClassDeclaration, + preanalyzeQueue: Promise[] | null, + symbols: string[], + ): void { const traits = this.scanClassForTraits(clazz); if (traits === null) { @@ -420,7 +426,7 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { } for (const trait of traits) { - const analyze = () => this.analyzeTrait(clazz, trait); + const analyze = () => this.analyzeTrait(clazz, trait, symbols); let preanalysis: Promise | null = null; if (preanalyzeQueue !== null && trait.handler.preanalyze !== undefined) { @@ -448,6 +454,7 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { private analyzeTrait( clazz: ClassDeclaration, trait: Trait, + symbols: string[], ): void { if (trait.state !== TraitState.Pending) { throw new Error( @@ -462,7 +469,7 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { // Attempt analysis. This could fail with a `FatalDiagnosticError`; catch it if it does. let result: AnalysisOutput; try { - result = trait.handler.analyze(clazz, trait.detected.metadata); + result = trait.handler.analyze(clazz, trait.detected.metadata, symbols); } catch (err) { if (err instanceof FatalDiagnosticError) { trait.toAnalyzed(null, [err.toDiagnostic()], null); @@ -474,7 +481,7 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { const symbol = this.makeSymbolForTrait(trait.handler, clazz, result.analysis ?? null); if (result.analysis !== undefined && trait.handler.register !== undefined) { - trait.handler.register(clazz, result.analysis); + trait.handler.register(clazz, result.analysis, symbols); } trait = trait.toAnalyzed(result.analysis ?? null, result.diagnostics ?? null, symbol); } @@ -647,7 +654,11 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { } } - compile(clazz: DeclarationNode, constantPool: ConstantPool): CompileResult[] | null { + compile( + clazz: DeclarationNode, + constantPool: ConstantPool, + symbols: string[], + ): CompileResult[] | null { const original = ts.getOriginalNode(clazz) as typeof clazz; if ( !this.reflector.isClass(clazz) || @@ -682,6 +693,7 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { trait.analysis!, trait.resolution!, constantPool, + symbols, ); } else { // `trait.resolution` is non-null asserted below because TypeScript does not recognize that @@ -691,13 +703,14 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { this.compilationMode === CompilationMode.PARTIAL && trait.handler.compilePartial !== undefined ) { - compileRes = trait.handler.compilePartial(clazz, trait.analysis, trait.resolution!); + compileRes = trait.handler.compilePartial(clazz, trait.analysis, trait.resolution!, []); } else { compileRes = trait.handler.compileFull( clazz, trait.analysis, trait.resolution!, constantPool, + symbols, ); } } @@ -745,7 +758,13 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { !containsErrors(trait.analysisDiagnostics) && !containsErrors(trait.resolveDiagnostics) ) { - return trait.handler.compileHmrUpdateDeclaration(clazz, trait.analysis, trait.resolution!); + // TODO + return trait.handler.compileHmrUpdateDeclaration( + clazz, + trait.analysis, + trait.resolution!, + [], + ); } } @@ -808,3 +827,36 @@ function containsErrors(diagnostics: ts.Diagnostic[] | null): boolean { diagnostics.some((diag) => diag.category === ts.DiagnosticCategory.Error) ); } + +function getImportedSymbols(sourceFile: ts.SourceFile): string[] { + const importedSymbols: string[] = []; + + // Traverse the AST + const visit = (node: ts.Node) => { + if (ts.isImportDeclaration(node) && node.importClause) { + const importClause = node.importClause; + + // Default import (e.g., `import foo from 'module';`) + if (importClause.name) { + importedSymbols.push(importClause.name.text); + } + + // Named imports (e.g., `import { bar, baz } from 'module';`) + if (importClause.namedBindings && ts.isNamedImports(importClause.namedBindings)) { + for (const element of importClause.namedBindings.elements) { + importedSymbols.push(element.name.text); + } + } + + // Namespace import (e.g., `import * as ns from 'module';`) + if (importClause.namedBindings && ts.isNamespaceImport(importClause.namedBindings)) { + importedSymbols.push(importClause.namedBindings.name.text); + } + } + + ts.forEachChild(node, visit); // Recursively process child nodes + }; + + visit(sourceFile); + return importedSymbols; +} diff --git a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts index 64f6ac38668e..97f8a8fd25de 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts @@ -56,6 +56,7 @@ export function ivyTransformFactory( const recordWrappedNode = createRecorderFn(defaultImportTracker); return (context: ts.TransformationContext): ts.Transformer => { return (file: ts.SourceFile): ts.SourceFile => { + const symbols = getTopLevelDeclarations(file); return perf.inPhase(PerfPhase.Compile, () => transformIvySourceFile( compilation, @@ -67,6 +68,7 @@ export function ivyTransformFactory( isCore, isClosureCompilerEnabled, recordWrappedNode, + symbols, ), ); }; @@ -85,6 +87,7 @@ class IvyCompilationVisitor extends Visitor { constructor( private compilation: TraitCompiler, private constantPool: ConstantPool, + private symbols: string[], ) { super(); } @@ -94,7 +97,7 @@ class IvyCompilationVisitor extends Visitor { ): VisitListEntryResult { // Determine if this class has an Ivy field that needs to be added, and compile the field // to an expression if so. - const result = this.compilation.compile(node, this.constantPool); + const result = this.compilation.compile(node, this.constantPool, this.symbols); if (result !== null) { this.classCompilationMap.set(node, result); @@ -369,6 +372,7 @@ function transformIvySourceFile( isCore: boolean, isClosureCompilerEnabled: boolean, recordWrappedNode: RecordWrappedNodeFn, + symbols: string[], ): ts.SourceFile { const constantPool = new ConstantPool(isClosureCompilerEnabled); const importManager = new ImportManager({ @@ -387,7 +391,7 @@ function transformIvySourceFile( // components declared in the same file. // Step 1. Go though all classes in AST, perform compilation and collect the results. - const compilationVisitor = new IvyCompilationVisitor(compilation, constantPool); + const compilationVisitor = new IvyCompilationVisitor(compilation, constantPool, symbols); visit(file, compilationVisitor, context); // Step 2. Scan through the AST again and perform transformations based on Ivy compilation @@ -550,3 +554,43 @@ function nodeArrayFromDecoratorsArray( return array; } + +function getTopLevelDeclarations(sourceFile: ts.SourceFile): string[] { + const symbols: string[] = []; + + // Traverse the AST + const visit = (node: ts.Node) => { + if (ts.isImportDeclaration(node) && node.importClause) { + const importClause = node.importClause; + + // Default import (e.g., `import foo from 'module';`) + if (importClause.name) { + symbols.push(importClause.name.text); + } + + // Named imports (e.g., `import { bar, baz } from 'module';`) + if (importClause.namedBindings && ts.isNamedImports(importClause.namedBindings)) { + for (const element of importClause.namedBindings.elements) { + symbols.push(element.name.text); + } + } + + // Namespace import (e.g., `import * as ns from 'module';`) + if (importClause.namedBindings && ts.isNamespaceImport(importClause.namedBindings)) { + symbols.push(importClause.namedBindings.name.text); + } + } + + if (ts.isDeclarationStatement(node) || ts.isVariableDeclaration(node)) { + const name = (node as ts.NamedDeclaration).name; + if (name && ts.isIdentifier(name)) { + symbols.push(name.text); + } + } + + ts.forEachChild(node, visit); // Recursively process child nodes + }; + + visit(sourceFile); + return symbols; +} diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index c93aed57433a..0e8671fbd3ed 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -347,7 +347,8 @@ export class CompilerFacadeImpl implements CompilerFacade { ): any { const constantPool = new ConstantPool(); const bindingParser = makeBindingParser(meta.interpolation); - const res = compileComponentFromMetadata(meta, constantPool, bindingParser); + // TODO + const res = compileComponentFromMetadata(meta, constantPool, bindingParser, []); return this.jitExpression( res.expression, angularCoreEnv, diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index dfb3935c873d..100d3c582362 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -184,6 +184,7 @@ export function compileComponentFromMetadata( meta: R3ComponentMetadata, constantPool: ConstantPool, bindingParser: BindingParser, + symbols: string[], ): R3CompiledExpression { const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser); addFeatures(definitionMap, meta); @@ -234,6 +235,7 @@ export function compileComponentFromMetadata( meta.i18nUseExternalIds, meta.defer, allDeferrableDepsFn, + symbols, ); // Then the IR is transformed to prepare it for cod egeneration. diff --git a/packages/compiler/src/template/pipeline/src/compilation.ts b/packages/compiler/src/template/pipeline/src/compilation.ts index 20755754262b..746289aef7e1 100644 --- a/packages/compiler/src/template/pipeline/src/compilation.ts +++ b/packages/compiler/src/template/pipeline/src/compilation.ts @@ -73,6 +73,7 @@ export class ComponentCompilationJob extends CompilationJob { readonly i18nUseExternalIds: boolean, readonly deferMeta: R3ComponentDeferMetadata, readonly allDeferrableDepsFn: o.ReadVarExpr | null, + readonly symbols: string[], ) { super(componentName, pool, compatibility); this.root = new ViewCompilationUnit(this, this.allocateXrefId(), null); diff --git a/packages/compiler/src/template/pipeline/src/emit.ts b/packages/compiler/src/template/pipeline/src/emit.ts index 2524dc24ebcd..ebd63446e979 100644 --- a/packages/compiler/src/template/pipeline/src/emit.ts +++ b/packages/compiler/src/template/pipeline/src/emit.ts @@ -83,6 +83,7 @@ import {wrapI18nIcus} from './phases/wrap_icus'; import {optimizeStoreLet} from './phases/store_let_optimization'; import {removeIllegalLetReferences} from './phases/remove_illegal_let_references'; import {generateLocalLetReferences} from './phases/generate_local_let_references'; +import {accessModuleScope} from './phases/lexical_context'; type Phase = | { @@ -128,6 +129,7 @@ const phases: Phase[] = [ {kind: Kind.Tmpl, fn: generateVariables}, {kind: Kind.Tmpl, fn: saveAndRestoreView}, {kind: Kind.Both, fn: deleteAnyCasts}, + {kind: Kind.Both, fn: accessModuleScope}, {kind: Kind.Both, fn: resolveDollarEvent}, {kind: Kind.Tmpl, fn: generateTrackVariables}, {kind: Kind.Tmpl, fn: removeIllegalLetReferences}, diff --git a/packages/compiler/src/template/pipeline/src/ingest.ts b/packages/compiler/src/template/pipeline/src/ingest.ts index 402573a7ccdc..419252feac90 100644 --- a/packages/compiler/src/template/pipeline/src/ingest.ts +++ b/packages/compiler/src/template/pipeline/src/ingest.ts @@ -58,6 +58,7 @@ export function ingestComponent( i18nUseExternalIds: boolean, deferMeta: R3ComponentDeferMetadata, allDeferrableDepsFn: o.ReadVarExpr | null, + symbols: string[], ): ComponentCompilationJob { const job = new ComponentCompilationJob( componentName, @@ -67,6 +68,7 @@ export function ingestComponent( i18nUseExternalIds, deferMeta, allDeferrableDepsFn, + symbols, ); ingestNodes(job.root, template); return job; diff --git a/packages/compiler/src/template/pipeline/src/phases/lexical_context.ts b/packages/compiler/src/template/pipeline/src/phases/lexical_context.ts new file mode 100644 index 000000000000..338ff935185f --- /dev/null +++ b/packages/compiler/src/template/pipeline/src/phases/lexical_context.ts @@ -0,0 +1,37 @@ +/** + * @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.dev/license + */ + +import * as o from '../../../../output/output_ast'; +import * as ir from '../../ir'; +import {CompilationJob, ComponentCompilationJob} from '../compilation'; + +/** + * Find any access to `globalThis` and replace them with `globalThis` to allow global scope access + */ +export function accessModuleScope(job: CompilationJob): void { + if (job instanceof ComponentCompilationJob) { + for (const unit of job.units) { + for (const op of unit.ops()) { + ir.transformExpressionsInOp( + op, + (expr) => replaceGlobalThis(expr, job.symbols), + ir.VisitorContextFlag.None, + ); + } + } + } +} + +function replaceGlobalThis(expr: o.Expression, symbols: string[]): o.Expression { + if (expr instanceof ir.LexicalReadExpr) { + if (symbols.includes(expr.name)) { + return new o.ReadVarExpr(expr.name); + } + } + return expr; +}