Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class ModuleWithProvidersAnalyzer {
ngModule = {node: dtsNgModule, viaModule: null};
}
const dtsFile = dtsFn.getSourceFile();
const analysis = analyses.get(dtsFile) || [];
const analysis = analyses.has(dtsFile) ? analyses.get(dtsFile) : [];
analysis.push({declaration: dtsFn, ngModule});
analyses.set(dtsFile, analysis);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ export class PrivateDeclarationsAnalyzer {
if (exports) {
exports.forEach((declaration, exportedName) => {
if (hasNameIdentifier(declaration.node)) {
const privateDeclaration = privateDeclarations.get(declaration.node.name);
if (privateDeclaration) {
if (privateDeclarations.has(declaration.node.name)) {
const privateDeclaration = privateDeclarations.get(declaration.node.name) !;
if (privateDeclaration.node !== declaration.node) {
throw new Error(`${declaration.node.name.text} is declared multiple times.`);
}
Expand Down Expand Up @@ -96,7 +96,7 @@ export class PrivateDeclarationsAnalyzer {
return Array.from(privateDeclarations.keys()).map(id => {
const from = AbsoluteFsPath.fromSourceFile(id.getSourceFile());
const declaration = privateDeclarations.get(id) !;
const alias = exportAliasDeclarations.get(id) || null;
const alias = exportAliasDeclarations.has(id) ? exportAliasDeclarations.get(id) ! : null;
const dtsDeclaration = this.host.getDtsDeclaration(declaration.node);
const dtsFrom =
dtsDeclaration && AbsoluteFsPath.fromSourceFile(dtsDeclaration.getSourceFile());
Expand Down
28 changes: 16 additions & 12 deletions packages/compiler-cli/ngcc/src/dependencies/dependency_resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@
*/

import {DepGraph} from 'dependency-graph';

import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {Logger} from '../logging/logger';
import {EntryPoint, EntryPointJsonProperty, getEntryPointFormat} from '../packages/entry_point';

import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointFormat} from '../packages/entry_point';
import {DependencyHost} from './dependency_host';



/**
* Holds information about entry points that are removed because
* they have dependencies that are missing (directly or transitively).
Expand Down Expand Up @@ -68,7 +64,8 @@ export interface SortedEntryPointsInfo extends DependencyDiagnostics { entryPoin
* A class that resolves dependencies between entry-points.
*/
export class DependencyResolver {
constructor(private logger: Logger, private host: DependencyHost) {}
constructor(
private logger: Logger, private hosts: Partial<Record<EntryPointFormat, DependencyHost>>) {}
/**
* Sort the array of entry points so that the dependant entry points always come later than
* their dependencies in the array.
Expand Down Expand Up @@ -118,8 +115,13 @@ export class DependencyResolver {

// Now add the dependencies between them
angularEntryPoints.forEach(entryPoint => {
const entryPointPath = getEntryPointPath(entryPoint);
const {dependencies, missing, deepImports} = this.host.findDependencies(entryPointPath);
const formatInfo = getEntryPointFormatInfo(entryPoint);
const host = this.hosts[formatInfo.format];
if (!host) {
throw new Error(
`Could not find a suitable format for computing dependencies of entry-point: '${entryPoint.path}'.`);
}
const {dependencies, missing, deepImports} = host.findDependencies(formatInfo.path);

if (missing.size > 0) {
// This entry point has dependencies that are missing
Expand Down Expand Up @@ -164,18 +166,20 @@ export class DependencyResolver {
}
}

function getEntryPointPath(entryPoint: EntryPoint): AbsoluteFsPath {
function getEntryPointFormatInfo(entryPoint: EntryPoint):
{format: EntryPointFormat, path: AbsoluteFsPath} {
const properties = Object.keys(entryPoint.packageJson);
for (let i = 0; i < properties.length; i++) {
const property = properties[i] as EntryPointJsonProperty;
const format = getEntryPointFormat(property);

if (format === 'esm2015' || format === 'esm5') {
if (format === 'esm2015' || format === 'esm5' || format === 'umd') {
const formatPath = entryPoint.packageJson[property] !;
return AbsoluteFsPath.resolve(entryPoint.path, formatPath);
return {format, path: AbsoluteFsPath.resolve(entryPoint.path, formatPath)};
}
}
throw new Error(`There is no format with import statements in '${entryPoint.path}' entry-point.`);
throw new Error(
`There is no appropriate source code format in '${entryPoint.path}' entry-point.`);
}

interface DependencyGraph extends DependencyDiagnostics {
Expand Down
111 changes: 111 additions & 0 deletions packages/compiler-cli/ngcc/src/dependencies/umd_dependency_host.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* @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} from '../../../src/ngtsc/path';
import {FileSystem} from '../file_system/file_system';
import {getImportsOfUmdModule, parseStatementForUmdModule} from '../host/umd_host';

import {DependencyHost, DependencyInfo} from './dependency_host';
import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver';



/**
* Helper functions for computing dependencies.
*/
export class UmdDependencyHost implements DependencyHost {
constructor(private fs: FileSystem, private moduleResolver: ModuleResolver) {}

/**
* Find all the dependencies for the entry-point at the given path.
*
* @param entryPointPath The absolute path to the JavaScript file that represents an entry-point.
* @returns Information about the dependencies of the entry-point, including those that were
* missing or deep imports into other entry-points.
*/
findDependencies(entryPointPath: AbsoluteFsPath): DependencyInfo {
const dependencies = new Set<AbsoluteFsPath>();
const missing = new Set<string>();
const deepImports = new Set<string>();
const alreadySeen = new Set<AbsoluteFsPath>();
this.recursivelyFindDependencies(
entryPointPath, dependencies, missing, deepImports, alreadySeen);
return {dependencies, missing, deepImports};
}

/**
* Compute the dependencies of the given file.
*
* @param file An absolute path to the file whose dependencies we want to get.
* @param dependencies A set that will have the absolute paths of resolved entry points added to
* it.
* @param missing A set that will have the dependencies that could not be found added to it.
* @param deepImports A set that will have the import paths that exist but cannot be mapped to
* entry-points, i.e. deep-imports.
* @param alreadySeen A set that is used to track internal dependencies to prevent getting stuck
* in a
* circular dependency loop.
*/
private recursivelyFindDependencies(
file: AbsoluteFsPath, dependencies: Set<AbsoluteFsPath>, missing: Set<string>,
deepImports: Set<string>, alreadySeen: Set<AbsoluteFsPath>): void {
const fromContents = this.fs.readFile(file);
if (!this.hasRequireCalls(fromContents)) {
// Avoid parsing the source file as there are no require calls.
return;
}

// Parse the source into a TypeScript AST and then walk it looking for imports and re-exports.
const sf =
ts.createSourceFile(file, fromContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS);
if (sf.statements.length !== 1) {
return;
}

const umdModule = parseStatementForUmdModule(sf.statements[0]);
const umdImports = umdModule && getImportsOfUmdModule(umdModule);
if (umdImports === null) {
return;
}

umdImports.forEach(umdImport => {
const resolvedModule = this.moduleResolver.resolveModuleImport(umdImport.path, file);
if (resolvedModule) {
if (resolvedModule instanceof ResolvedRelativeModule) {
const internalDependency = resolvedModule.modulePath;
if (!alreadySeen.has(internalDependency)) {
alreadySeen.add(internalDependency);
this.recursivelyFindDependencies(
internalDependency, dependencies, missing, deepImports, alreadySeen);
}
} else {
if (resolvedModule instanceof ResolvedDeepImport) {
deepImports.add(resolvedModule.importPath);
} else {
dependencies.add(resolvedModule.entryPointPath);
}
}
} else {
missing.add(umdImport.path);
}
});
}

/**
* Check whether a source file needs to be parsed for imports.
* This is a performance short-circuit, which saves us from creating
* a TypeScript AST unnecessarily.
*
* @param source The content of the source file to check.
*
* @returns false if there are definitely no require calls
* in this file, true otherwise.
*/
hasRequireCalls(source: string): boolean { return /require\(['"]/.test(source); }
}
Loading