135 lines
4.9 KiB
TypeScript
Raw Normal View History

/**
* @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';
fix(ivy): ngcc - resolve `main` property paths correctly (#31509) There are two places in the ngcc processing where it needs to load the content of a file given by a general path: * when determining the format of an entry-point. To do this ngcc uses the value of the relevant property in package.json. But in the case of `main` it must parse the contents of the entry-point file to decide whether the format is UMD or CommonJS. * when parsing the source files for dependencies to determine the order in which compilation must occur. The relative imports in each file are parsed and followed recursively, looking for external imports. Previously, we naively assumed that the path would match the file name exactly. But actually we must consider the standard module resolution conventions. E.g. the extension (.js) may be missing, or the path may refer to a directory containing an index.js file. This commit fixes both places. This commit now requires the `DependencyHost` instances to check the existence of more files than before (at worst all the different possible post-fixes). This should not create a significant performance reduction for ngcc. Since the results of the checks will be cached, and similar work is done inside the TS compiler, so what we lose in doing it here, is saved later in the processing. The main performance loss would be where there are lots of files that need to be parsed for dependencies that do not end up being processed by TS. But compared to the main ngcc processing this dependency parsing is a small proportion of the work done and so should not impact much on the overall performance of ngcc. // FW-1444 PR Close #31509
2019-07-11 12:34:45 +01:00
import {AbsoluteFsPath, FileSystem, absoluteFrom} from '../../src/ngtsc/file_system';
import {EsmDependencyHost} from './dependencies/esm_dependency_host';
import {ModuleResolver} from './dependencies/module_resolver';
/**
* A list (`Array`) of partially ordered `T` items.
*
* The items in the list are partially ordered in the sense that any element has either the same or
* higher precedence than any element which appears later in the list. What "higher precedence"
* means and how it is determined is implementation-dependent.
*
* See [PartiallyOrderedSet](https://en.wikipedia.org/wiki/Partially_ordered_set) for more details.
* (Refraining from using the term "set" here, to avoid confusion with JavaScript's
* [Set](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Set).)
*
* NOTE: A plain `Array<T>` is not assignable to a `PartiallyOrderedList<T>`, but a
* `PartiallyOrderedList<T>` is assignable to an `Array<T>`.
*/
export interface PartiallyOrderedList<T> extends Array<T> {
_partiallyOrdered: true;
map<U>(callbackfn: (value: T, index: number, array: PartiallyOrderedList<T>) => U, thisArg?: any):
PartiallyOrderedList<U>;
slice(...args: Parameters<Array<T>['slice']>): PartiallyOrderedList<T>;
}
export function getOriginalSymbol(checker: ts.TypeChecker): (symbol: ts.Symbol) => ts.Symbol {
return function(symbol: ts.Symbol) {
return ts.SymbolFlags.Alias & symbol.flags ? checker.getAliasedSymbol(symbol) : symbol;
};
}
export function isDefined<T>(value: T | undefined | null): value is T {
return (value !== undefined) && (value !== null);
}
export function getNameText(name: ts.PropertyName | ts.BindingName): string {
return ts.isIdentifier(name) || ts.isLiteralExpression(name) ? name.text : name.getText();
}
/**
* Parse down the AST and capture all the nodes that satisfy the test.
* @param node The start node.
* @param test The function that tests whether a node should be included.
* @returns a collection of nodes that satisfy the test.
*/
export function findAll<T>(node: ts.Node, test: (node: ts.Node) => node is ts.Node & T): T[] {
const nodes: T[] = [];
findAllVisitor(node);
return nodes;
function findAllVisitor(n: ts.Node) {
if (test(n)) {
nodes.push(n);
} else {
n.forEachChild(child => findAllVisitor(child));
}
}
}
export function getOrDefault<K, V>(map: Map<K, V>, key: K, factory: (key: K) => V): V {
if (!map.has(key)) {
map.set(key, factory(key));
}
return map.get(key) !;
}
/**
* Does the given declaration have a name which is an identifier?
* @param declaration The declaration to test.
* @returns true if the declaration has an identifier for a name.
*/
export function hasNameIdentifier(declaration: ts.Declaration): declaration is ts.Declaration&
{name: ts.Identifier} {
fix(ivy): match microsyntax template directives correctly (#29698) Previously, Template.templateAttrs was introduced to capture attribute bindings which originated from microsyntax (e.g. bindings in *ngFor="..."). This means that a Template node can have two different structures, depending on whether it originated from microsyntax or from a literal <ng-template>. In the literal case, the node behaves much like an Element node, it has attributes, inputs, and outputs which determine which directives apply. In the microsyntax case, though, only the templateAttrs should be used to determine which directives apply. Previously, both the t2_binder and the TemplateDefinitionBuilder were using the wrong set of attributes to match directives - combining the attributes, inputs, outputs, and templateAttrs of the Template node regardless of its origin. In the TDB's case this wasn't a problem, since the TDB collects a global Set of directives used in the template, so it didn't matter whether the directive was also recognized on the <ng-template>. t2_binder's API distinguishes between directives on specific nodes, though, so it's more sensitive to mismatching. In particular, this showed up as an assertion failure in template type- checking in certain cases, when a directive was accidentally matched on a microsyntax template element and also had a binding which referenced a variable declared in the microsyntax. This resulted in the type-checker attempting to generate a reference to a variable that didn't exist in that scope. The fix is to distinguish between the two cases and select the appropriate set of attributes to match on accordingly. Testing strategy: tested in the t2_binder tests. PR Close #29698
2019-04-04 13:19:38 -07:00
const namedDeclaration: ts.Declaration&{name?: ts.Node} = declaration;
return namedDeclaration.name !== undefined && ts.isIdentifier(namedDeclaration.name);
}
export type PathMappings = {
baseUrl: string,
paths: {[key: string]: string[]}
};
/**
* Test whether a path is "relative".
*
* Relative paths start with `/`, `./` or `../`; or are simply `.` or `..`.
*/
export function isRelativePath(path: string): boolean {
return /^\/|^\.\.?($|\/)/.test(path);
}
fix(ivy): ngcc - resolve `main` property paths correctly (#31509) There are two places in the ngcc processing where it needs to load the content of a file given by a general path: * when determining the format of an entry-point. To do this ngcc uses the value of the relevant property in package.json. But in the case of `main` it must parse the contents of the entry-point file to decide whether the format is UMD or CommonJS. * when parsing the source files for dependencies to determine the order in which compilation must occur. The relative imports in each file are parsed and followed recursively, looking for external imports. Previously, we naively assumed that the path would match the file name exactly. But actually we must consider the standard module resolution conventions. E.g. the extension (.js) may be missing, or the path may refer to a directory containing an index.js file. This commit fixes both places. This commit now requires the `DependencyHost` instances to check the existence of more files than before (at worst all the different possible post-fixes). This should not create a significant performance reduction for ngcc. Since the results of the checks will be cached, and similar work is done inside the TS compiler, so what we lose in doing it here, is saved later in the processing. The main performance loss would be where there are lots of files that need to be parsed for dependencies that do not end up being processed by TS. But compared to the main ngcc processing this dependency parsing is a small proportion of the work done and so should not impact much on the overall performance of ngcc. // FW-1444 PR Close #31509
2019-07-11 12:34:45 +01:00
/**
* Attempt to resolve a `path` to a file by appending the provided `postFixes`
* to the `path` and checking if the file exists on disk.
* @returns An absolute path to the first matching existing file, or `null` if none exist.
*/
export function resolveFileWithPostfixes(
fs: FileSystem, path: AbsoluteFsPath, postFixes: string[]): AbsoluteFsPath|null {
for (const postFix of postFixes) {
const testPath = absoluteFrom(path + postFix);
if (fs.exists(testPath) && fs.stat(testPath).isFile()) {
return testPath;
}
}
return null;
}
/**
* An identifier may become repeated when bundling multiple source files into a single bundle, so
* bundlers have a strategy of suffixing non-unique identifiers with a suffix like $2. This function
* strips off such suffixes, so that ngcc deals with the canonical name of an identifier.
* @param value The value to strip any suffix of, if applicable.
* @returns The canonical representation of the value, without any suffix.
*/
export function stripDollarSuffix(value: string): string {
return value.replace(/\$\d+$/, '');
}
export function stripExtension(fileName: string): string {
return fileName.replace(/\..+$/, '');
}
export function createDtsDependencyHost(fileSystem: FileSystem, pathMappings?: PathMappings) {
return new EsmDependencyHost(
fileSystem, new ModuleResolver(fileSystem, pathMappings, ['', '.d.ts', '/index.d.ts']));
}