fix(ivy): ngcc - do not analyze files outside the current package (#30591)

Our module resolution prefers `.js` files over `.d.ts` files because
occasionally libraries publish their typings in the same directory
structure as the compiled JS files, i.e. adjacent to each other.

The standard TS module resolution would pick up the typings
file and add that to the `ts.Program` and so they would be
ignored by our analyzers. But we need those JS files, if they
are part of the current package.

But this meant that we also bring in JS files from external
imports from outside the package, which is not desired.
This was happening for the `@fire/storage` enty-point
that was importing the `firebase/storage` path.

In this commit we solve this problem, for the case of imports
coming from a completely different package, by saying that any
file that is outside the package root directory must be an external
import and so we do not analyze those files.

This does not solve the potential problem of imports between
secondary entry-points within a package but so far that does
not appear to be a problem.

PR Close #30591
This commit is contained in:
Pete Bacon Darwin 2019-05-25 21:34:40 +01:00 committed by Kara Erickson
parent 42036f4b79
commit f690a4e0af
16 changed files with 436 additions and 198 deletions

View File

@ -10,7 +10,7 @@ import * as ts from 'typescript';
import {BaseDefDecoratorHandler, ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader} from '../../../src/ngtsc/annotations';
import {CycleAnalyzer, ImportGraph} from '../../../src/ngtsc/cycles';
import {AbsoluteFsPath, FileSystem, LogicalFileSystem, absoluteFrom, dirname, resolve} from '../../../src/ngtsc/file_system';
import {FileSystem, LogicalFileSystem, absoluteFrom, dirname, resolve} from '../../../src/ngtsc/file_system';
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../../src/ngtsc/imports';
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, LocalMetadataRegistry} from '../../../src/ngtsc/metadata';
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
@ -20,6 +20,7 @@ import {CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '
import {NgccReflectionHost} from '../host/ngcc_host';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {isDefined} from '../utils';
import {isWithinPackage} from './util';
export interface AnalyzedFile {
sourceFile: ts.SourceFile;
@ -72,6 +73,7 @@ export class DecorationAnalyzer {
private host = this.bundle.src.host;
private typeChecker = this.bundle.src.program.getTypeChecker();
private rootDirs = this.bundle.rootDirs;
private packagePath = this.bundle.entryPoint.package;
private isCore = this.bundle.isCore;
resourceManager = new NgccResourceLoader(this.fs);
metaRegistry = new LocalMetadataRegistry();
@ -130,6 +132,7 @@ export class DecorationAnalyzer {
analyzeProgram(): DecorationAnalyses {
const decorationAnalyses = new DecorationAnalyses();
const analysedFiles = this.program.getSourceFiles()
.filter(sourceFile => isWithinPackage(this.packagePath, sourceFile))
.map(sourceFile => this.analyzeFile(sourceFile))
.filter(isDefined);
analysedFiles.forEach(analysedFile => this.resolveFile(analysedFile));

View File

@ -6,7 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
import {NgccReflectionHost, SwitchableVariableDeclaration} from '../host/ngcc_host';
import {isWithinPackage} from './util';
export interface SwitchMarkerAnalysis {
sourceFile: ts.SourceFile;
@ -21,7 +23,7 @@ export const SwitchMarkerAnalyses = Map;
* that will be replaced.
*/
export class SwitchMarkerAnalyzer {
constructor(private host: NgccReflectionHost) {}
constructor(private host: NgccReflectionHost, private packagePath: AbsoluteFsPath) {}
/**
* Analyze the files in the program to identify declarations that contain R3
* switch markers.
@ -32,12 +34,14 @@ export class SwitchMarkerAnalyzer {
*/
analyzeProgram(program: ts.Program): SwitchMarkerAnalyses {
const analyzedFiles = new SwitchMarkerAnalyses();
program.getSourceFiles().forEach(sourceFile => {
const declarations = this.host.getSwitchableDeclarations(sourceFile);
if (declarations.length) {
analyzedFiles.set(sourceFile, {sourceFile, declarations});
}
});
program.getSourceFiles()
.filter(sourceFile => isWithinPackage(this.packagePath, sourceFile))
.forEach(sourceFile => {
const declarations = this.host.getSwitchableDeclarations(sourceFile);
if (declarations.length) {
analyzedFiles.set(sourceFile, {sourceFile, declarations});
}
});
return analyzedFiles;
}
}

View File

@ -0,0 +1,13 @@
/**
* @license
* Copyright Google Inc. 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 * as ts from 'typescript';
import {AbsoluteFsPath, absoluteFromSourceFile, relative} from '../../../src/ngtsc/file_system';
export function isWithinPackage(packagePath: AbsoluteFsPath, sourceFile: ts.SourceFile): boolean {
return !relative(packagePath, absoluteFromSourceFile(sourceFile)).startsWith('..');
}

View File

@ -122,7 +122,8 @@ export class Transformer {
analyzeProgram(reflectionHost: NgccReflectionHost, bundle: EntryPointBundle): ProgramAnalyses {
const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
const switchMarkerAnalyzer = new SwitchMarkerAnalyzer(reflectionHost);
const switchMarkerAnalyzer =
new SwitchMarkerAnalyzer(reflectionHost, bundle.entryPoint.package);
const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program);
const decorationAnalyzer =

View File

@ -109,17 +109,19 @@ runInEachFileSystem(() => {
beforeEach(() => {
const TEST_PROGRAM = [
{
name: _('/index.js'),
name: _('/node_modules/test-package/index.js'),
contents: `
import * as test from './test';
import * as other from './other';
`,
},
{
name: _('/test.js'),
name: _('/node_modules/test-package/test.js'),
contents: `
import {Component, Directive, Injectable} from '@angular/core';
export class NoDecorators {}
export class MyComponent {}
MyComponent.decorators = [{type: Component}];
@ -128,10 +130,11 @@ runInEachFileSystem(() => {
export class MyService {}
MyService.decorators = [{type: Injectable}];
`,
},
{
name: _('/other.js'),
name: _('/node_modules/test-package/other.js'),
contents: `
import {Component} from '@angular/core';
@ -144,22 +147,16 @@ runInEachFileSystem(() => {
});
it('should return an object containing a reference to the original source file', () => {
const testFile = getSourceFileOrError(program, _('/test.js'));
const testFile = getSourceFileOrError(program, _('/node_modules/test-package/test.js'));
expect(result.get(testFile) !.sourceFile).toBe(testFile);
const otherFile = getSourceFileOrError(program, _('/other.js'));
const otherFile = getSourceFileOrError(program, _('/node_modules/test-package/other.js'));
expect(result.get(otherFile) !.sourceFile).toBe(otherFile);
});
it('should call detect on the decorator handlers with each class from the parsed file',
() => {
expect(testHandler.detect).toHaveBeenCalledTimes(11);
expect(testHandler.detect).toHaveBeenCalledTimes(5);
expect(testHandler.detect.calls.allArgs().map(args => args[1])).toEqual([
null,
null,
null,
null,
null,
null,
null,
jasmine.arrayContaining([jasmine.objectContaining({name: 'Component'})]),
jasmine.arrayContaining([jasmine.objectContaining({name: 'Directive'})]),
@ -169,7 +166,7 @@ runInEachFileSystem(() => {
});
it('should return an object containing the classes that were analyzed', () => {
const file1 = getSourceFileOrError(program, _('/test.js'));
const file1 = getSourceFileOrError(program, _('/node_modules/test-package/test.js'));
const compiledFile1 = result.get(file1) !;
expect(compiledFile1.compiledClasses.length).toEqual(2);
expect(compiledFile1.compiledClasses[0]).toEqual(jasmine.objectContaining({
@ -179,7 +176,7 @@ runInEachFileSystem(() => {
name: 'MyDirective', compilation: ['@Directive (compiled)'],
} as unknown as CompiledClass));
const file2 = getSourceFileOrError(program, _('/other.js'));
const file2 = getSourceFileOrError(program, _('/node_modules/test-package/other.js'));
const compiledFile2 = result.get(file2) !;
expect(compiledFile2.compiledClasses.length).toEqual(1);
expect(compiledFile2.compiledClasses[0]).toEqual(jasmine.objectContaining({
@ -190,13 +187,7 @@ runInEachFileSystem(() => {
it('should analyze, resolve and compile the classes that are detected', () => {
expect(logs).toEqual([
// Classes without decorators should also be detected.
'detect: ChangeDetectorRef (no decorators)',
'detect: ElementRef (no decorators)',
'detect: Injector (no decorators)',
'detect: TemplateRef (no decorators)',
'detect: ViewContainerRef (no decorators)',
'detect: Renderer2 (no decorators)',
'detect: ɵNgModuleFactory (no decorators)',
'detect: NoDecorators (no decorators)',
// First detect and (potentially) analyze.
'detect: MyComponent@Component',
'analyze: MyComponent@Component',
@ -221,7 +212,7 @@ runInEachFileSystem(() => {
beforeEach(() => {
const INTERNAL_COMPONENT_PROGRAM = [
{
name: _('/entrypoint.js'),
name: _('/node_modules/test-package/entrypoint.js'),
contents: `
import {Component, NgModule} from '@angular/core';
import {ImportedComponent} from './component';
@ -237,7 +228,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/component.js'),
name: _('/node_modules/test-package/component.js'),
contents: `
import {Component} from '@angular/core';
export class ImportedComponent {}
@ -253,7 +244,8 @@ runInEachFileSystem(() => {
// files is not yet solved.
it('should analyze an internally imported component, which is not publicly exported from the entry-point',
() => {
const file = getSourceFileOrError(program, _('/component.js'));
const file =
getSourceFileOrError(program, _('/node_modules/test-package/component.js'));
const analysis = result.get(file) !;
expect(analysis).toBeDefined();
const ImportedComponent =
@ -262,13 +254,58 @@ runInEachFileSystem(() => {
});
it('should analyze an internally defined component, which is not exported at all', () => {
const file = getSourceFileOrError(program, _('/entrypoint.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/entrypoint.js'));
const analysis = result.get(file) !;
expect(analysis).toBeDefined();
const LocalComponent = analysis.compiledClasses.find(f => f.name === 'LocalComponent') !;
expect(LocalComponent).toBeDefined();
});
});
describe('external components', () => {
beforeEach(() => {
const EXTERNAL_COMPONENT_PROGRAM: TestFile[] = [
{
name: _('/node_modules/test-package/entrypoint.js'),
contents: `
import {Component, NgModule} from '@angular/core';
import {ImportedComponent} from 'other/component';
export class LocalComponent {}
LocalComponent.decorators = [{type: Component}];
export class MyModule {}
MyModule.decorators = [{type: NgModule, args: [{
declarations: [ImportedComponent, LocalComponent],
exports: [ImportedComponent, LocalComponent],
},] }];
`
},
{
name: _('/node_modules/other/component.js'),
contents: `
import {Component} from '@angular/core';
export class ImportedComponent {}
ImportedComponent.decorators = [{type: Component}];
`,
isRoot: false,
},
{
name: _('/node_modules/other/component.d.ts'),
contents: `
import {Component} from '@angular/core';
export class ImportedComponent {}`
},
];
setUpAndAnalyzeProgram(EXTERNAL_COMPONENT_PROGRAM);
});
it('should ignore classes from an externally imported file', () => {
const file = program.getSourceFile(_('/node_modules/other/component.js')) !;
expect(result.has(file)).toBe(false);
});
});
});
});
});
});

View File

@ -32,7 +32,7 @@ runInEachFileSystem(() => {
const TEST_PROGRAM: TestFile[] = [
{
name: _('/src/entry-point.js'),
name: _('/node_modules/test-package/src/entry-point.js'),
contents: `
export * from './explicit';
export * from './any';
@ -42,7 +42,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/src/explicit.js'),
name: _('/node_modules/test-package/src/explicit.js'),
contents: `
import {ExternalModule} from './module';
import {LibraryModule} from 'some-library';
@ -88,7 +88,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/src/any.js'),
name: _('/node_modules/test-package/src/any.js'),
contents: `
import {ExternalModule} from './module';
import {LibraryModule} from 'some-library';
@ -134,7 +134,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/src/implicit.js'),
name: _('/node_modules/test-package/src/implicit.js'),
contents: `
import {ExternalModule} from './module';
import {LibraryModule} from 'some-library';
@ -180,7 +180,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/src/no-providers.js'),
name: _('/node_modules/test-package/src/no-providers.js'),
contents: `
import {ExternalModule} from './module';
import {LibraryModule} from 'some-library';
@ -215,7 +215,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/src/module.js'),
name: _('/node_modules/test-package/src/module.js'),
contents: `
export class ExternalModule {}
`
@ -227,7 +227,7 @@ runInEachFileSystem(() => {
];
const TEST_DTS_PROGRAM: TestFile[] = [
{
name: _('/typings/entry-point.d.ts'),
name: _('/node_modules/test-package/typings/entry-point.d.ts'),
contents: `
export * from './explicit';
export * from './any';
@ -237,7 +237,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/typings/explicit.d.ts'),
name: _('/node_modules/test-package/typings/explicit.d.ts'),
contents: `
import {ModuleWithProviders} from './core';
import {ExternalModule} from './module';
@ -254,7 +254,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/typings/any.d.ts'),
name: _('/node_modules/test-package/typings/any.d.ts'),
contents: `
import {ModuleWithProviders} from './core';
export declare class AnyInternalModule {}
@ -269,7 +269,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/typings/implicit.d.ts'),
name: _('/node_modules/test-package/typings/implicit.d.ts'),
contents: `
import {ExternalModule} from './module';
import {LibraryModule} from 'some-library';
@ -285,7 +285,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/typings/no-providers.d.ts'),
name: _('/node_modules/test-package/typings/no-providers.d.ts'),
contents: `
import {ModuleWithProviders} from './core';
import {ExternalModule} from './module';
@ -303,13 +303,13 @@ runInEachFileSystem(() => {
`
},
{
name: _('/typings/module.d.ts'),
name: _('/node_modules/test-package/typings/module.d.ts'),
contents: `
export declare class ExternalModule {}
`
},
{
name: _('/typings/core.d.ts'),
name: _('/node_modules/test-package/typings/core.d.ts'),
contents: `
export declare interface Type<T> {
@ -343,11 +343,14 @@ runInEachFileSystem(() => {
});
it('should ignore declarations that already have explicit NgModule type params', () => {
expect(getAnalysisDescription(analyses, _('/typings/explicit.d.ts'))).toEqual([]);
expect(
getAnalysisDescription(analyses, _('/node_modules/test-package/typings/explicit.d.ts')))
.toEqual([]);
});
it('should find declarations that use `any` for the NgModule type param', () => {
const anyAnalysis = getAnalysisDescription(analyses, _('/typings/any.d.ts'));
const anyAnalysis =
getAnalysisDescription(analyses, _('/node_modules/test-package/typings/any.d.ts'));
expect(anyAnalysis).toContain(['anyInternalFunction', 'AnyInternalModule', null]);
expect(anyAnalysis).toContain(['anyExternalFunction', 'ExternalModule', null]);
expect(anyAnalysis).toContain(['anyLibraryFunction', 'LibraryModule', 'some-library']);
@ -359,7 +362,8 @@ runInEachFileSystem(() => {
it('should track internal module references in the references registry', () => {
const declarations = referencesRegistry.getDeclarationMap();
const externalModuleDeclaration = getDeclaration(
program, absoluteFrom('/src/module.js'), 'ExternalModule', ts.isClassDeclaration);
program, absoluteFrom('/node_modules/test-package/src/module.js'), 'ExternalModule',
ts.isClassDeclaration);
const libraryModuleDeclaration = getDeclaration(
program, absoluteFrom('/node_modules/some-library/index.d.ts'), 'LibraryModule',
ts.isClassDeclaration);
@ -368,7 +372,8 @@ runInEachFileSystem(() => {
});
it('should find declarations that have implicit return types', () => {
const anyAnalysis = getAnalysisDescription(analyses, _('/typings/implicit.d.ts'));
const anyAnalysis =
getAnalysisDescription(analyses, _('/node_modules/test-package/typings/implicit.d.ts'));
expect(anyAnalysis).toContain(['implicitInternalFunction', 'ImplicitInternalModule', null]);
expect(anyAnalysis).toContain(['implicitExternalFunction', 'ExternalModule', null]);
expect(anyAnalysis).toContain(['implicitLibraryFunction', 'LibraryModule', 'some-library']);
@ -379,7 +384,8 @@ runInEachFileSystem(() => {
it('should find declarations that do not specify a `providers` property in the return type',
() => {
const anyAnalysis = getAnalysisDescription(analyses, _('/typings/no-providers.d.ts'));
const anyAnalysis = getAnalysisDescription(
analyses, _('/node_modules/test-package/typings/no-providers.d.ts'));
expect(anyAnalysis).not.toContain([
'noProvExplicitInternalFunction', 'NoProvidersInternalModule'
]);

View File

@ -27,7 +27,7 @@ runInEachFileSystem(() => {
const TEST_PROGRAM: TestFile[] = [
{
name: _('/src/entry_point.js'),
name: _('/node_modules/test-package/src/entry_point.js'),
isRoot: true,
contents: `
export {PublicComponent} from './a';
@ -36,7 +36,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/src/a.js'),
name: _('/node_modules/test-package/src/a.js'),
isRoot: false,
contents: `
import {Component} from '@angular/core';
@ -47,7 +47,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/src/b.js'),
name: _('/node_modules/test-package/src/b.js'),
isRoot: false,
contents: `
import {Component, NgModule} from '@angular/core';
@ -66,7 +66,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/src/c.js'),
name: _('/node_modules/test-package/src/c.js'),
isRoot: false,
contents: `
import {Component} from '@angular/core';
@ -81,7 +81,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/src/mod.js'),
name: _('/node_modules/test-package/src/mod.js'),
isRoot: false,
contents: `
import {Component, NgModule} from '@angular/core';
@ -100,7 +100,7 @@ runInEachFileSystem(() => {
];
const TEST_DTS_PROGRAM = [
{
name: _('/typings/entry_point.d.ts'),
name: _('/node_modules/test-package/typings/entry_point.d.ts'),
isRoot: true,
contents: `
export {PublicComponent} from './a';
@ -109,28 +109,28 @@ runInEachFileSystem(() => {
`
},
{
name: _('/typings/a.d.ts'),
name: _('/node_modules/test-package/typings/a.d.ts'),
isRoot: false,
contents: `
export declare class PublicComponent {}
`
},
{
name: _('/typings/b.d.ts'),
name: _('/node_modules/test-package/typings/b.d.ts'),
isRoot: false,
contents: `
export declare class ModuleB {}
`
},
{
name: _('/typings/c.d.ts'),
name: _('/node_modules/test-package/typings/c.d.ts'),
isRoot: false,
contents: `
export declare class InternalComponent1 {}
`
},
{
name: _('/typings/mod.d.ts'),
name: _('/node_modules/test-package/typings/mod.d.ts'),
isRoot: false,
contents: `
import {PublicComponent} from './a';
@ -142,22 +142,31 @@ runInEachFileSystem(() => {
];
const {program, referencesRegistry, analyzer} = setup(TEST_PROGRAM, TEST_DTS_PROGRAM);
addToReferencesRegistry(program, referencesRegistry, _('/src/a.js'), 'PublicComponent');
addToReferencesRegistry(
program, referencesRegistry, _('/src/b.js'), 'PrivateComponent1');
program, referencesRegistry, _('/node_modules/test-package/src/a.js'),
'PublicComponent');
addToReferencesRegistry(
program, referencesRegistry, _('/src/c.js'), 'InternalComponent1');
program, referencesRegistry, _('/node_modules/test-package/src/b.js'),
'PrivateComponent1');
addToReferencesRegistry(
program, referencesRegistry, _('/node_modules/test-package/src/c.js'),
'InternalComponent1');
const analyses = analyzer.analyzeProgram(program);
// Note that `PrivateComponent2` and `InternalComponent2` are not found because they are
// not added to the ReferencesRegistry (i.e. they were not declared in an NgModule).
expect(analyses.length).toEqual(2);
expect(analyses).toEqual([
{identifier: 'PrivateComponent1', from: _('/src/b.js'), dtsFrom: null, alias: null},
{
identifier: 'PrivateComponent1',
from: _('/node_modules/test-package/src/b.js'),
dtsFrom: null,
alias: null
},
{
identifier: 'InternalComponent1',
from: _('/src/c.js'),
dtsFrom: _('/typings/c.d.ts'),
from: _('/node_modules/test-package/src/c.js'),
dtsFrom: _('/node_modules/test-package/typings/c.d.ts'),
alias: null
},
]);
@ -167,7 +176,7 @@ runInEachFileSystem(() => {
const _ = absoluteFrom;
const ALIASED_EXPORTS_PROGRAM = [
{
name: _('/src/entry_point.js'),
name: _('/node_modules/test-package/src/entry_point.js'),
isRoot: true,
contents: `
// This component is only exported as an alias.
@ -177,7 +186,7 @@ runInEachFileSystem(() => {
`
},
{
name: _('/src/a.js'),
name: _('/node_modules/test-package/src/a.js'),
isRoot: false,
contents: `
import {Component} from '@angular/core';
@ -195,7 +204,7 @@ runInEachFileSystem(() => {
];
const ALIASED_EXPORTS_DTS_PROGRAM = [
{
name: _('/typings/entry_point.d.ts'),
name: _('/node_modules/test-package/typings/entry_point.d.ts'),
isRoot: true,
contents: `
export declare class aliasedComponentOne {}
@ -207,13 +216,15 @@ runInEachFileSystem(() => {
const {program, referencesRegistry, analyzer} =
setup(ALIASED_EXPORTS_PROGRAM, ALIASED_EXPORTS_DTS_PROGRAM);
addToReferencesRegistry(program, referencesRegistry, _('/src/a.js'), 'ComponentOne');
addToReferencesRegistry(program, referencesRegistry, _('/src/a.js'), 'ComponentTwo');
addToReferencesRegistry(
program, referencesRegistry, _('/node_modules/test-package/src/a.js'), 'ComponentOne');
addToReferencesRegistry(
program, referencesRegistry, _('/node_modules/test-package/src/a.js'), 'ComponentTwo');
const analyses = analyzer.analyzeProgram(program);
expect(analyses).toEqual([{
identifier: 'ComponentOne',
from: _('/src/a.js'),
from: _('/node_modules/test-package/src/a.js'),
dtsFrom: null,
alias: 'aliasedComponentOne',
}]);

View File

@ -11,54 +11,77 @@ import {loadTestFiles} from '../../../test/helpers';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {MockLogger} from '../helpers/mock_logger';
import {getRootFiles, makeTestBundleProgram} from '../helpers/utils';
import {makeTestEntryPointBundle} from '../helpers/utils';
runInEachFileSystem(() => {
describe('SwitchMarkerAnalyzer', () => {
let _: typeof absoluteFrom;
let TEST_PROGRAM: TestFile[];
beforeEach(() => {
_ = absoluteFrom;
TEST_PROGRAM = [
{
name: _('/node_modules/test/entrypoint.js'),
contents: `
import {a} from './a';
import {b} from './b';
import {x} from '../other/x';
`
},
{
name: _('/node_modules/test/a.js'),
contents: `
import {c} from './c';
export const a = 1;
`
},
{
name: _('/node_modules/test/b.js'),
contents: `
export const b = 42;
var factoryB = factory__PRE_R3__;
`
},
{
name: _('/node_modules/test/c.js'),
contents: `
export const c = 'So long, and thanks for all the fish!';
var factoryC = factory__PRE_R3__;
var factoryD = factory__PRE_R3__;
`
},
{
name: _('/node_modules/other/x.js'),
contents: `
export const x = 3.142;
var factoryX = factory__PRE_R3__;
`
},
{
name: _('/node_modules/other/x.d.ts'),
contents: `
export const x: number;
`
},
];
});
describe('analyzeProgram()', () => {
it('should check for switchable markers in all the files of the program', () => {
const _ = absoluteFrom;
const TEST_PROGRAM: TestFile[] = [
{
name: _('/entrypoint.js'),
contents: `
import {a} from './a';
import {b} from './b';
`
},
{
name: _('/a.js'),
contents: `
import {c} from './c';
export const a = 1;
`
},
{
name: _('/b.js'),
contents: `
export const b = 42;
var factoryB = factory__PRE_R3__;
`
},
{
name: _('/c.js'),
contents: `
export const c = 'So long, and thanks for all the fish!';
var factoryC = factory__PRE_R3__;
var factoryD = factory__PRE_R3__;
`
},
];
loadTestFiles(TEST_PROGRAM);
const {program} = makeTestBundleProgram(getRootFiles(TEST_PROGRAM)[0]);
const bundle = makeTestEntryPointBundle(
'test', 'esm2015', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
const program = bundle.src.program;
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const analyzer = new SwitchMarkerAnalyzer(host);
const analyzer = new SwitchMarkerAnalyzer(host, bundle.entryPoint.package);
const analysis = analyzer.analyzeProgram(program);
const entrypoint = getSourceFileOrError(program, _('/entrypoint.js'));
const a = getSourceFileOrError(program, _('/a.js'));
const b = getSourceFileOrError(program, _('/b.js'));
const c = getSourceFileOrError(program, _('/c.js'));
const entrypoint = getSourceFileOrError(program, _('/node_modules/test/entrypoint.js'));
const a = getSourceFileOrError(program, _('/node_modules/test/a.js'));
const b = getSourceFileOrError(program, _('/node_modules/test/b.js'));
const c = getSourceFileOrError(program, _('/node_modules/test/c.js'));
expect(analysis.size).toEqual(2);
expect(analysis.has(entrypoint)).toBe(false);
@ -76,6 +99,19 @@ runInEachFileSystem(() => {
'factoryD = factory__PRE_R3__',
]);
});
it('should ignore files that are outside the package', () => {
loadTestFiles(TEST_PROGRAM);
const bundle = makeTestEntryPointBundle(
'test', 'esm2015', 'esm2015', false, [_('/node_modules/test/entrypoint.js')]);
const program = bundle.src.program;
const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const analyzer = new SwitchMarkerAnalyzer(host, bundle.entryPoint.package);
const analysis = analyzer.analyzeProgram(program);
const x = getSourceFileOrError(program, _('/node_modules/other/x.js'));
expect(analysis.has(x)).toBe(false);
});
});
});
});
});

View File

@ -0,0 +1,31 @@
/**
* @license
* Copyright Google Inc. 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 * as ts from 'typescript';
import {absoluteFrom} from '../../../src/ngtsc/file_system';
import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {isWithinPackage} from '../../src/analysis/util';
runInEachFileSystem(() => {
describe('isWithinPackage', () => {
it('should return true if the source-file is contained in the package', () => {
const _ = absoluteFrom;
const file =
ts.createSourceFile(_('/node_modules/test/src/index.js'), '', ts.ScriptTarget.ES2015);
const packagePath = _('/node_modules/test');
expect(isWithinPackage(packagePath, file)).toBe(true);
});
it('should return false if the source-file is not contained in the package', () => {
const _ = absoluteFrom;
const file =
ts.createSourceFile(_('/node_modules/other/src/index.js'), '', ts.ScriptTarget.ES2015);
const packagePath = _('/node_modules/test');
expect(isWithinPackage(packagePath, file)).toBe(false);
});
});
});

View File

@ -5,6 +5,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 * as ts from 'typescript';
import {AbsoluteFsPath, NgtscCompilerHost, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
import {TestFile} from '../../../src/ngtsc/file_system/testing';
import {BundleProgram, makeBundleProgram} from '../../src/packages/bundle_program';
@ -49,8 +50,10 @@ export function makeTestEntryPointBundle(
export function makeTestBundleProgram(
path: AbsoluteFsPath, isCore: boolean = false): BundleProgram {
const fs = getFileSystem();
const options = {allowJs: true, checkJs: false};
const entryPointPath = fs.dirname(path);
const rootDir = fs.dirname(entryPointPath);
const options: ts.CompilerOptions =
{allowJs: true, maxNodeModuleJsDepth: Infinity, checkJs: false, rootDir, rootDirs: [rootDir]};
const host = new NgccSourcesCompilerHost(fs, options, entryPointPath);
return makeBundleProgram(fs, isCore, path, 'r3_symbols.js', options, host);
}

View File

@ -30,7 +30,7 @@ runInEachFileSystem(() => {
beforeEach(() => {
_ = absoluteFrom;
PROGRAM = {
name: _('/some/file.js'),
name: _('/node_modules/test-package/some/file.js'),
contents: `
/* A copyright notice */
require('some-side-effect');
@ -94,7 +94,7 @@ exports.BadIife = BadIife;`
};
PROGRAM_DECORATE_HELPER = {
name: _('/some/file.js'),
name: _('/node_modules/test-package/some/file.js'),
contents: `
var tslib_1 = require("tslib");
/* A copyright notice */
@ -156,8 +156,8 @@ exports.D = D;
const referencesRegistry = new NgccReferencesRegistry(host);
const decorationAnalyses =
new DecorationAnalyzer(fs, bundle, host, referencesRegistry).analyzeProgram();
const switchMarkerAnalyses =
new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host, bundle.entryPoint.package)
.analyzeProgram(bundle.src.program);
const renderer = new CommonJsRenderingFormatter(host, false);
const importManager = new ImportManager(new NoopImportRewriter(), 'i');
return {
@ -197,9 +197,21 @@ var i1 = require('@angular/common');`);
renderer.addExports(
output, _(PROGRAM.name.replace(/\.js$/, '')),
[
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA1'},
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA2'},
{from: _('/some/foo/b.js'), dtsFrom: _('/some/foo/b.d.ts'), identifier: 'ComponentB'},
{
from: _('/node_modules/test-package/some/a.js'),
dtsFrom: _('/node_modules/test-package/some/a.d.ts'),
identifier: 'ComponentA1'
},
{
from: _('/node_modules/test-package/some/a.js'),
dtsFrom: _('/node_modules/test-package/some/a.d.ts'),
identifier: 'ComponentA2'
},
{
from: _('/node_modules/test-package/some/foo/b.js'),
dtsFrom: _('/node_modules/test-package/some/foo/b.d.ts'),
identifier: 'ComponentB'
},
{from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'},
],
importManager, sourceFile);
@ -222,9 +234,21 @@ exports.TopLevelComponent = TopLevelComponent;`);
renderer.addExports(
output, _(PROGRAM.name.replace(/\.js$/, '')),
[
{from: _('/some/a.js'), alias: 'eComponentA1', identifier: 'ComponentA1'},
{from: _('/some/a.js'), alias: 'eComponentA2', identifier: 'ComponentA2'},
{from: _('/some/foo/b.js'), alias: 'eComponentB', identifier: 'ComponentB'},
{
from: _('/node_modules/test-package/some/a.js'),
alias: 'eComponentA1',
identifier: 'ComponentA1'
},
{
from: _('/node_modules/test-package/some/a.js'),
alias: 'eComponentA2',
identifier: 'ComponentA2'
},
{
from: _('/node_modules/test-package/some/foo/b.js'),
alias: 'eComponentB',
identifier: 'ComponentB'
},
{from: PROGRAM.name, alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'},
],
importManager, sourceFile);
@ -238,7 +262,7 @@ exports.TopLevelComponent = TopLevelComponent;`);
describe('addConstants', () => {
it('should insert the given constants after imports in the source file', () => {
const {renderer, program} = setup(PROGRAM);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.addConstants(output, 'var x = 3;', file);
expect(output.toString()).toContain(`
@ -250,7 +274,7 @@ var A = (function() {`);
it('should insert constants after inserted imports', () => {
const {renderer, program} = setup(PROGRAM);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.addConstants(output, 'var x = 3;', file);
renderer.addImports(output, [{specifier: '@angular/core', qualifier: 'i0'}], file);
@ -266,7 +290,7 @@ var A = (function() {`);
describe('rewriteSwitchableDeclarations', () => {
it('should switch marked declaration initializers', () => {
const {renderer, program, sourceFile, switchMarkerAnalyses} = setup(PROGRAM);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.rewriteSwitchableDeclarations(
output, file, switchMarkerAnalyses.get(sourceFile) !.declarations);
@ -311,14 +335,14 @@ SOME DEFINITION TEXT
const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'};
expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT'))
.toThrowError(
`Compiled class declaration is not inside an IIFE: NoIife in ${_('/some/file.js')}`);
`Compiled class declaration is not inside an IIFE: NoIife in ${_('/node_modules/test-package/some/file.js')}`);
const badIifeDeclaration = getDeclaration(
program, absoluteFromSourceFile(sourceFile), 'BadIife', ts.isVariableDeclaration);
const mockBadIifeClass: any = {declaration: badIifeDeclaration, name: 'BadIife'};
expect(() => renderer.addDefinitions(output, mockBadIifeClass, 'SOME DEFINITION TEXT'))
.toThrowError(
`Compiled class wrapper IIFE does not have a return statement: BadIife in ${_('/some/file.js')}`);
`Compiled class wrapper IIFE does not have a return statement: BadIife in ${_('/node_modules/test-package/some/file.js')}`);
});
});

View File

@ -99,7 +99,7 @@ runInEachFileSystem(() => {
beforeEach(() => {
_ = absoluteFrom;
INPUT_PROGRAM = {
name: _('/src/file.js'),
name: _('/node_modules/test-package/src/file.js'),
contents:
`import { Directive } from '@angular/core';\nexport class A {\n foo(x) {\n return x;\n }\n}\nA.decorators = [\n { type: Directive, args: [{ selector: '[a]' }] }\n];\n`
};
@ -139,8 +139,11 @@ runInEachFileSystem(() => {
createTestRenderer('test-package', [INPUT_PROGRAM], [INPUT_DTS_PROGRAM]);
// Add a mock export to trigger export rendering
privateDeclarationsAnalyses.push(
{identifier: 'ComponentB', from: _('/src/file.js'), dtsFrom: _('/typings/b.d.ts')});
privateDeclarationsAnalyses.push({
identifier: 'ComponentB',
from: _('/node_modules/test-package/src/file.js'),
dtsFrom: _('/typings/b.d.ts')
});
const result = renderer.renderProgram(
decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses);

View File

@ -32,7 +32,8 @@ function setup(file: {name: AbsoluteFsPath, contents: string}) {
const referencesRegistry = new NgccReferencesRegistry(host);
const decorationAnalyses =
new DecorationAnalyzer(fs, bundle, host, referencesRegistry).analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const switchMarkerAnalyses =
new SwitchMarkerAnalyzer(host, bundle.entryPoint.package).analyzeProgram(bundle.src.program);
const renderer = new Esm5RenderingFormatter(host, false);
const importManager = new ImportManager(new NoopImportRewriter(), IMPORT_PREFIX);
return {
@ -52,7 +53,7 @@ runInEachFileSystem(() => {
beforeEach(() => {
_ = absoluteFrom;
PROGRAM = {
name: _('/some/file.js'),
name: _('/node_modules/test-package/some/file.js'),
contents: `
/* A copyright notice */
import 'some-side-effect';
@ -112,7 +113,7 @@ export {A, B, C, NoIife, BadIife};`
};
PROGRAM_DECORATE_HELPER = {
name: _('/some/file.js'),
name: _('/node_modules/test-package/some/file.js'),
contents: `
import * as tslib_1 from "tslib";
/* A copyright notice */
@ -189,9 +190,21 @@ import * as i1 from '@angular/common';`);
renderer.addExports(
output, _(PROGRAM.name.replace(/\.js$/, '')),
[
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA1'},
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA2'},
{from: _('/some/foo/b.js'), dtsFrom: _('/some/foo/b.d.ts'), identifier: 'ComponentB'},
{
from: _('/node_modules/test-package/some/a.js'),
dtsFrom: _('/node_modules/test-package/some/a.d.ts'),
identifier: 'ComponentA1'
},
{
from: _('/node_modules/test-package/some/a.js'),
dtsFrom: _('/node_modules/test-package/some/a.d.ts'),
identifier: 'ComponentA2'
},
{
from: _('/node_modules/test-package/some/foo/b.js'),
dtsFrom: _('/node_modules/test-package/some/foo/b.d.ts'),
identifier: 'ComponentB'
},
{from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'},
],
importManager, sourceFile);
@ -209,9 +222,21 @@ export {TopLevelComponent};`);
renderer.addExports(
output, _(PROGRAM.name.replace(/\.js$/, '')),
[
{from: _('/some/a.js'), alias: 'eComponentA1', identifier: 'ComponentA1'},
{from: _('/some/a.js'), alias: 'eComponentA2', identifier: 'ComponentA2'},
{from: _('/some/foo/b.js'), alias: 'eComponentB', identifier: 'ComponentB'},
{
from: _('/node_modules/test-package/some/a.js'),
alias: 'eComponentA1',
identifier: 'ComponentA1'
},
{
from: _('/node_modules/test-package/some/a.js'),
alias: 'eComponentA2',
identifier: 'ComponentA2'
},
{
from: _('/node_modules/test-package/some/foo/b.js'),
alias: 'eComponentB',
identifier: 'ComponentB'
},
{from: PROGRAM.name, alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'},
],
importManager, sourceFile);
@ -225,7 +250,7 @@ export {TopLevelComponent};`);
describe('addConstants', () => {
it('should insert the given constants after imports in the source file', () => {
const {renderer, program} = setup(PROGRAM);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.addConstants(output, 'var x = 3;', file);
expect(output.toString()).toContain(`
@ -237,7 +262,7 @@ var A = (function() {`);
it('should insert constants after inserted imports', () => {
const {renderer, program} = setup(PROGRAM);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.addConstants(output, 'var x = 3;', file);
renderer.addImports(output, [{specifier: '@angular/core', qualifier: 'i0'}], file);
@ -253,7 +278,7 @@ var A = (function() {`);
describe('rewriteSwitchableDeclarations', () => {
it('should switch marked declaration initializers', () => {
const {renderer, program, sourceFile, switchMarkerAnalyses} = setup(PROGRAM);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.rewriteSwitchableDeclarations(
output, file, switchMarkerAnalyses.get(sourceFile) !.declarations);
@ -298,14 +323,14 @@ SOME DEFINITION TEXT
const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'};
expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT'))
.toThrowError(
`Compiled class declaration is not inside an IIFE: NoIife in ${_('/some/file.js')}`);
`Compiled class declaration is not inside an IIFE: NoIife in ${_('/node_modules/test-package/some/file.js')}`);
const badIifeDeclaration = getDeclaration(
program, absoluteFromSourceFile(sourceFile), 'BadIife', ts.isVariableDeclaration);
const mockBadIifeClass: any = {declaration: badIifeDeclaration, name: 'BadIife'};
expect(() => renderer.addDefinitions(output, mockBadIifeClass, 'SOME DEFINITION TEXT'))
.toThrowError(
`Compiled class wrapper IIFE does not have a return statement: BadIife in ${_('/some/file.js')}`);
`Compiled class wrapper IIFE does not have a return statement: BadIife in ${_('/node_modules/test-package/some/file.js')}`);
});
});

View File

@ -9,7 +9,7 @@ import MagicString from 'magic-string';
import * as ts from 'typescript';
import {NoopImportRewriter} from '../../../src/ngtsc/imports';
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system';
import {loadTestFiles} from '../../../test/helpers';
import {loadTestFiles, loadFakeCore} from '../../../test/helpers';
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {ImportManager} from '../../../src/ngtsc/translator';
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
@ -23,11 +23,12 @@ import {MockLogger} from '../helpers/mock_logger';
import {ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer';
function setup(files: TestFile[], dtsFiles?: TestFile[]) {
const fs = getFileSystem();
loadFakeCore(fs);
loadTestFiles(files);
if (dtsFiles) {
loadTestFiles(dtsFiles);
}
const fs = getFileSystem();
const logger = new MockLogger();
const bundle = makeTestEntryPointBundle(
'test-package', 'es2015', 'esm2015', false, getRootFiles(files),
@ -37,7 +38,8 @@ function setup(files: TestFile[], dtsFiles?: TestFile[]) {
const referencesRegistry = new NgccReferencesRegistry(host);
const decorationAnalyses =
new DecorationAnalyzer(fs, bundle, host, referencesRegistry).analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const switchMarkerAnalyses =
new SwitchMarkerAnalyzer(host, bundle.entryPoint.package).analyzeProgram(bundle.src.program);
const renderer = new EsmRenderingFormatter(host, false);
const importManager = new ImportManager(new NoopImportRewriter(), IMPORT_PREFIX);
return {
@ -58,7 +60,7 @@ runInEachFileSystem(() => {
_ = absoluteFrom;
PROGRAM = {
name: _('/some/file.js'),
name: _('/node_modules/test-package/some/file.js'),
contents: `
/* A copyright notice */
import 'some-side-effect';
@ -120,9 +122,21 @@ import * as i1 from '@angular/common';`);
renderer.addExports(
output, _(PROGRAM.name.replace(/\.js$/, '')),
[
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA1'},
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA2'},
{from: _('/some/foo/b.js'), dtsFrom: _('/some/foo/b.d.ts'), identifier: 'ComponentB'},
{
from: _('/node_modules/test-package/some/a.js'),
dtsFrom: _('/node_modules/test-package/some/a.d.ts'),
identifier: 'ComponentA1'
},
{
from: _('/node_modules/test-package/some/a.js'),
dtsFrom: _('/node_modules/test-package/some/a.d.ts'),
identifier: 'ComponentA2'
},
{
from: _('/node_modules/test-package/some/foo/b.js'),
dtsFrom: _('/node_modules/test-package/some/foo/b.d.ts'),
identifier: 'ComponentB'
},
{from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'},
],
importManager, sourceFile);
@ -140,9 +154,21 @@ export {TopLevelComponent};`);
renderer.addExports(
output, _(PROGRAM.name.replace(/\.js$/, '')),
[
{from: _('/some/a.js'), alias: 'eComponentA1', identifier: 'ComponentA1'},
{from: _('/some/a.js'), alias: 'eComponentA2', identifier: 'ComponentA2'},
{from: _('/some/foo/b.js'), alias: 'eComponentB', identifier: 'ComponentB'},
{
from: _('/node_modules/test-package/some/a.js'),
alias: 'eComponentA1',
identifier: 'ComponentA1'
},
{
from: _('/node_modules/test-package/some/a.js'),
alias: 'eComponentA2',
identifier: 'ComponentA2'
},
{
from: _('/node_modules/test-package/some/foo/b.js'),
alias: 'eComponentB',
identifier: 'ComponentB'
},
{from: PROGRAM.name, alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'},
],
importManager, sourceFile);
@ -156,7 +182,7 @@ export {TopLevelComponent};`);
describe('addConstants', () => {
it('should insert the given constants after imports in the source file', () => {
const {renderer, program} = setup([PROGRAM]);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.addConstants(output, 'const x = 3;', file);
expect(output.toString()).toContain(`
@ -168,7 +194,7 @@ export class A {}`);
it('should insert constants after inserted imports', () => {
const {renderer, program} = setup([PROGRAM]);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.addConstants(output, 'const x = 3;', file);
renderer.addImports(output, [{specifier: '@angular/core', qualifier: 'i0'}], file);
@ -184,7 +210,7 @@ export class A {`);
describe('rewriteSwitchableDeclarations', () => {
it('should switch marked declaration initializers', () => {
const {renderer, program, switchMarkerAnalyses, sourceFile} = setup([PROGRAM]);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.rewriteSwitchableDeclarations(
output, file, switchMarkerAnalyses.get(sourceFile) !.declarations);
@ -292,7 +318,7 @@ A.decorators = [
beforeEach(() => {
PROGRAM_DECORATE_HELPER = {
name: _('/some/file.js'),
name: _('/node_modules/test-package/some/file.js'),
contents: `
import * as tslib_1 from "tslib";
var D_1;

View File

@ -69,7 +69,8 @@ function createTestRenderer(
const referencesRegistry = new NgccReferencesRegistry(host);
const decorationAnalyses =
new DecorationAnalyzer(fs, bundle, host, referencesRegistry).analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const switchMarkerAnalyses =
new SwitchMarkerAnalyzer(host, bundle.entryPoint.package).analyzeProgram(bundle.src.program);
const privateDeclarationsAnalyses =
new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
const testFormatter = new TestRenderingFormatter();
@ -105,22 +106,22 @@ runInEachFileSystem(() => {
_ = absoluteFrom;
INPUT_PROGRAM = {
name: _('/src/file.js'),
name: _('/node_modules/test-package/src/file.js'),
contents:
`import { Directive } from '@angular/core';\nexport class A {\n foo(x) {\n return x;\n }\n}\nA.decorators = [\n { type: Directive, args: [{ selector: '[a]' }] }\n];\n`
};
COMPONENT_PROGRAM = {
name: _('/src/component.js'),
name: _('/node_modules/test-package/src/component.js'),
contents:
`import { Component } from '@angular/core';\nexport class A {}\nA.decorators = [\n { type: Component, args: [{ selector: 'a', template: '{{ person!.name }}' }] }\n];\n`
};
INPUT_PROGRAM_MAP = fromObject({
'version': 3,
'file': _('/src/file.js'),
'file': _('/node_modules/test-package/src/file.js'),
'sourceRoot': '',
'sources': [_('/src/file.ts')],
'sources': [_('/node_modules/test-package/src/file.ts')],
'names': [],
'mappings':
'AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,MAAM;IACF,GAAG,CAAC,CAAS;QACT,OAAO,CAAC,CAAC;IACb,CAAC;;AACM,YAAU,GAAG;IAChB,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE;CACnD,CAAC',
@ -142,7 +143,7 @@ runInEachFileSystem(() => {
OUTPUT_PROGRAM_MAP = fromObject({
'version': 3,
'file': 'file.js',
'sources': [_('/src/file.js')],
'sources': [_('/node_modules/test-package/src/file.js')],
'sourcesContent': [INPUT_PROGRAM.contents],
'names': [],
'mappings': ';;;;;;;;;;AAAA;;;;;;;;;'
@ -150,7 +151,7 @@ runInEachFileSystem(() => {
MERGED_OUTPUT_PROGRAM_MAP = fromObject({
'version': 3,
'sources': [_('/src/file.ts')],
'sources': [_('/node_modules/test-package/src/file.ts')],
'names': [],
'mappings': ';;;;;;;;;;AAAA',
'file': 'file.js',
@ -165,10 +166,10 @@ runInEachFileSystem(() => {
createTestRenderer('test-package', [INPUT_PROGRAM]);
const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
expect(result[0].path).toEqual(_('/src/file.js'));
expect(result[0].path).toEqual(_('/node_modules/test-package/src/file.js'));
expect(result[0].contents)
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('file.js.map'));
expect(result[1].path).toEqual(_('/src/file.js.map'));
expect(result[1].path).toEqual(_('/node_modules/test-package/src/file.js.map'));
expect(result[1].contents).toEqual(OUTPUT_PROGRAM_MAP.toJSON());
});
@ -254,9 +255,10 @@ runInEachFileSystem(() => {
it('should render classes without decorators if handler matches', () => {
const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
testFormatter} = createTestRenderer('test-package', [{
name: _('/src/file.js'),
contents: `
testFormatter} =
createTestRenderer('test-package', [{
name: _('/node_modules/test-package/src/file.js'),
contents: `
import { Directive, ViewChild } from '@angular/core';
export class UndecoratedBase { test = null; }
@ -268,7 +270,7 @@ runInEachFileSystem(() => {
}],
};
`
}]);
}]);
renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
@ -312,7 +314,7 @@ runInEachFileSystem(() => {
}]);
const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
expect(result[0].path).toEqual(_('/src/file.js'));
expect(result[0].path).toEqual(_('/node_modules/test-package/src/file.js'));
expect(result[0].contents)
.toEqual(RENDERED_CONTENTS + '\n' + MERGED_OUTPUT_PROGRAM_MAP.toComment());
expect(result[1]).toBeUndefined();
@ -331,10 +333,10 @@ runInEachFileSystem(() => {
createTestRenderer('test-package', sourceFiles, undefined, mappingFiles);
const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
expect(result[0].path).toEqual(_('/src/file.js'));
expect(result[0].path).toEqual(_('/node_modules/test-package/src/file.js'));
expect(result[0].contents)
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('file.js.map'));
expect(result[1].path).toEqual(_('/src/file.js.map'));
expect(result[1].path).toEqual(_('/node_modules/test-package/src/file.js.map'));
expect(JSON.parse(result[1].contents)).toEqual(MERGED_OUTPUT_PROGRAM_MAP.toObject());
});
});
@ -342,14 +344,14 @@ runInEachFileSystem(() => {
describe('@angular/core support', () => {
it('should render relative imports in ESM bundles', () => {
const CORE_FILE: TestFile = {
name: _('/src/core.js'),
name: _('/node_modules/test-package/src/core.js'),
contents:
`import { NgModule } from './ng_module';\nexport class MyModule {}\nMyModule.decorators = [\n { type: NgModule, args: [] }\n];\n`
};
const R3_SYMBOLS_FILE: TestFile = {
// r3_symbols in the file name indicates that this is the path to rewrite core imports
// to
name: _('/src/r3_symbols.js'),
name: _('/node_modules/test-package/src/r3_symbols.js'),
contents: `export const NgModule = () => null;`
};
// The package name of `@angular/core` indicates that we are compiling the core library.
@ -368,7 +370,7 @@ runInEachFileSystem(() => {
it('should render no imports in FESM bundles', () => {
const CORE_FILE: TestFile = {
name: _('/src/core.js'),
name: _('/node_modules/test-package/src/core.js'),
contents: `export const NgModule = () => null;
export class MyModule {}\nMyModule.decorators = [\n { type: NgModule, args: [] }\n];\n`
};

View File

@ -31,7 +31,8 @@ function setup(file: TestFile) {
const referencesRegistry = new NgccReferencesRegistry(host);
const decorationAnalyses =
new DecorationAnalyzer(fs, bundle, host, referencesRegistry).analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(src.program);
const switchMarkerAnalyses =
new SwitchMarkerAnalyzer(host, bundle.entryPoint.package).analyzeProgram(src.program);
const renderer = new UmdRenderingFormatter(host, false);
const importManager = new ImportManager(new NoopImportRewriter(), 'i');
return {
@ -54,7 +55,7 @@ runInEachFileSystem(() => {
_ = absoluteFrom;
PROGRAM = {
name: _('/some/file.js'),
name: _('/node_modules/test-package/some/file.js'),
contents: `
/* A copyright notice */
(function (global, factory) {
@ -123,7 +124,7 @@ exports.BadIife = BadIife;
PROGRAM_DECORATE_HELPER = {
name: _('/some/file.js'),
name: _('/node_modules/test-package/some/file.js'),
contents: `
/* A copyright notice */
(function (global, factory) {
@ -181,7 +182,7 @@ typeof define === 'function' && define.amd ? define('file', ['exports','/tslib',
describe('addImports', () => {
it('should append the given imports into the CommonJS factory call', () => {
const {renderer, program} = setup(PROGRAM);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.addImports(
output,
@ -197,7 +198,7 @@ typeof define === 'function' && define.amd ? define('file', ['exports','/tslib',
it('should append the given imports into the AMD initialization', () => {
const {renderer, program} = setup(PROGRAM);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.addImports(
output,
@ -213,7 +214,7 @@ typeof define === 'function' && define.amd ? define('file', ['exports','/tslib',
it('should append the given imports into the global initialization', () => {
const {renderer, program} = setup(PROGRAM);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.addImports(
output,
@ -230,7 +231,7 @@ typeof define === 'function' && define.amd ? define('file', ['exports','/tslib',
it('should append the given imports as parameters into the factory function definition',
() => {
const {renderer, program} = setup(PROGRAM);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.addImports(
output,
@ -253,9 +254,9 @@ typeof define === 'function' && define.amd ? define('file', ['exports','/tslib',
renderer.addExports(
output, PROGRAM.name.replace(/\.js$/, ''),
[
{from: _('/some/a.js'), identifier: 'ComponentA1'},
{from: _('/some/a.js'), identifier: 'ComponentA2'},
{from: _('/some/foo/b.js'), identifier: 'ComponentB'},
{from: _('/node_modules/test-package/some/a.js'), identifier: 'ComponentA1'},
{from: _('/node_modules/test-package/some/a.js'), identifier: 'ComponentA2'},
{from: _('/node_modules/test-package/some/foo/b.js'), identifier: 'ComponentB'},
{from: PROGRAM.name, identifier: 'TopLevelComponent'},
],
importManager, sourceFile);
@ -283,9 +284,21 @@ exports.TopLevelComponent = TopLevelComponent;
renderer.addExports(
output, PROGRAM.name.replace(/\.js$/, ''),
[
{from: _('/some/a.js'), alias: 'eComponentA1', identifier: 'ComponentA1'},
{from: _('/some/a.js'), alias: 'eComponentA2', identifier: 'ComponentA2'},
{from: _('/some/foo/b.js'), alias: 'eComponentB', identifier: 'ComponentB'},
{
from: _('/node_modules/test-package/some/a.js'),
alias: 'eComponentA1',
identifier: 'ComponentA1'
},
{
from: _('/node_modules/test-package/some/a.js'),
alias: 'eComponentA2',
identifier: 'ComponentA2'
},
{
from: _('/node_modules/test-package/some/foo/b.js'),
alias: 'eComponentB',
identifier: 'ComponentB'
},
{from: PROGRAM.name, alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'},
],
importManager, sourceFile);
@ -299,7 +312,7 @@ exports.TopLevelComponent = TopLevelComponent;
describe('addConstants', () => {
it('should insert the given constants after imports in the source file', () => {
const {renderer, program} = setup(PROGRAM);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.addConstants(output, 'var x = 3;', file);
expect(output.toString()).toContain(`
@ -319,7 +332,7 @@ var A = (function() {`);
describe('rewriteSwitchableDeclarations', () => {
it('should switch marked declaration initializers', () => {
const {renderer, program, sourceFile, switchMarkerAnalyses} = setup(PROGRAM);
const file = getSourceFileOrError(program, _('/some/file.js'));
const file = getSourceFileOrError(program, _('/node_modules/test-package/some/file.js'));
const output = new MagicString(PROGRAM.contents);
renderer.rewriteSwitchableDeclarations(
output, file, switchMarkerAnalyses.get(sourceFile) !.declarations);
@ -364,14 +377,14 @@ SOME DEFINITION TEXT
const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'};
expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT'))
.toThrowError(
`Compiled class declaration is not inside an IIFE: NoIife in ${_('/some/file.js')}`);
`Compiled class declaration is not inside an IIFE: NoIife in ${_('/node_modules/test-package/some/file.js')}`);
const badIifeDeclaration = getDeclaration(
program, absoluteFromSourceFile(sourceFile), 'BadIife', ts.isVariableDeclaration);
const mockBadIifeClass: any = {declaration: badIifeDeclaration, name: 'BadIife'};
expect(() => renderer.addDefinitions(output, mockBadIifeClass, 'SOME DEFINITION TEXT'))
.toThrowError(
`Compiled class wrapper IIFE does not have a return statement: BadIife in ${_('/some/file.js')}`);
`Compiled class wrapper IIFE does not have a return statement: BadIife in ${_('/node_modules/test-package/some/file.js')}`);
});
});