mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-28 11:10:12 +08:00
refactor(@ngtools/webpack): use type guard based narrowing with TS AST
By leveraging TypeScript's AST type guards, function parameter assumptions and casting can be removed. Many of these cases caused errors when enabling TypeScript's strict option. This is preliminary work to support enabling full TypeScript strict mode within the project.
This commit is contained in:
parent
2ab3136636
commit
e852b62fce
@ -8,7 +8,7 @@
|
||||
|
||||
import { Path, join } from '@angular-devkit/core';
|
||||
import * as ts from 'typescript';
|
||||
import { TypeScriptFileRefactor } from './refactor';
|
||||
import { TypeScriptFileRefactor, findAstNodes } from './refactor';
|
||||
|
||||
|
||||
function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,
|
||||
@ -16,8 +16,8 @@ function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,
|
||||
host: ts.CompilerHost,
|
||||
program: ts.Program): string | null {
|
||||
// Check this file.
|
||||
const hasSymbol = refactor.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
|
||||
.some((cd: ts.ClassDeclaration) => {
|
||||
const hasSymbol = findAstNodes(null, refactor.sourceFile, ts.isClassDeclaration)
|
||||
.some((cd) => {
|
||||
return cd.name != undefined && cd.name.text == symbolName;
|
||||
});
|
||||
if (hasSymbol) {
|
||||
@ -69,8 +69,8 @@ function _recursiveSymbolExportLookup(refactor: TypeScriptFileRefactor,
|
||||
|
||||
// Create the source and verify that the symbol is at least a class.
|
||||
const source = new TypeScriptFileRefactor(module, host, program);
|
||||
const hasSymbol = source.findAstNodes(null, ts.SyntaxKind.ClassDeclaration)
|
||||
.some((cd: ts.ClassDeclaration) => {
|
||||
const hasSymbol = findAstNodes(null, source.sourceFile, ts.isClassDeclaration)
|
||||
.some((cd) => {
|
||||
return cd.name != undefined && cd.name.text == symbolName;
|
||||
});
|
||||
|
||||
|
@ -57,24 +57,21 @@ export function findLazyRoutes(
|
||||
}
|
||||
const sf: ts.SourceFile = sourceFile;
|
||||
|
||||
return findAstNodes(null, sourceFile, ts.SyntaxKind.ObjectLiteralExpression, true)
|
||||
return findAstNodes(null, sourceFile, ts.isObjectLiteralExpression, true)
|
||||
// Get all their property assignments.
|
||||
.map((node: ts.ObjectLiteralExpression) => {
|
||||
return findAstNodes(node, sf, ts.SyntaxKind.PropertyAssignment, false);
|
||||
})
|
||||
.map((node) => findAstNodes(node, sf, ts.isPropertyAssignment, false))
|
||||
// Take all `loadChildren` elements.
|
||||
.reduce((acc: ts.PropertyAssignment[], props: ts.PropertyAssignment[]) => {
|
||||
.reduce((acc, props) => {
|
||||
return acc.concat(props.filter(literal => {
|
||||
return _getContentOfKeyLiteral(sf, literal.name) == 'loadChildren';
|
||||
}));
|
||||
}, [])
|
||||
// Get only string values.
|
||||
.filter((node: ts.PropertyAssignment) => node.initializer.kind == ts.SyntaxKind.StringLiteral)
|
||||
// Get the string value.
|
||||
.map((node: ts.PropertyAssignment) => (node.initializer as ts.StringLiteral).text)
|
||||
.map((node) => node.initializer)
|
||||
.filter(ts.isStringLiteral)
|
||||
// Map those to either [path, absoluteModulePath], or [path, null] if the module pointing to
|
||||
// does not exist.
|
||||
.map((routePath: string) => {
|
||||
.map(({ text: routePath }) => {
|
||||
const moduleName = routePath.split('#')[0];
|
||||
const compOptions = (program && program.getCompilerOptions()) || compilerOptions || {};
|
||||
const resolvedModuleName: ts.ResolvedModuleWithFailedLookupLocations = moduleName[0] == '.'
|
||||
@ -87,11 +84,11 @@ export function findLazyRoutes(
|
||||
&& host.fileExists(resolvedModuleName.resolvedModule.resolvedFileName)) {
|
||||
return [routePath, resolvedModuleName.resolvedModule.resolvedFileName];
|
||||
} else {
|
||||
return [routePath, null];
|
||||
return [routePath, null] as [string, null];
|
||||
}
|
||||
})
|
||||
// Reduce to the LazyRouteMap map.
|
||||
.reduce((acc: LazyRouteMap, [routePath, resolvedModuleName]: [string, string | null]) => {
|
||||
.reduce((acc: LazyRouteMap, [routePath, resolvedModuleName]) => {
|
||||
if (resolvedModuleName) {
|
||||
acc[routePath] = resolvedModuleName;
|
||||
}
|
||||
|
@ -19,11 +19,35 @@ import { forwardSlashPath } from './utils';
|
||||
* @param max The maximum number of items to return.
|
||||
* @return all nodes of kind, or [] if none is found
|
||||
*/
|
||||
// TODO: replace this with collectDeepNodes and add limits to collectDeepNodes
|
||||
export function findAstNodes<T extends ts.Node>(
|
||||
export function findAstNodes(
|
||||
node: ts.Node | null,
|
||||
sourceFile: ts.SourceFile,
|
||||
kind: ts.SyntaxKind,
|
||||
recursive?: boolean,
|
||||
max?: number,
|
||||
): ts.Node[];
|
||||
|
||||
/**
|
||||
* Find all nodes from the AST in the subtree of node of SyntaxKind kind.
|
||||
* @param node The root node to check, or null if the whole tree should be searched.
|
||||
* @param sourceFile The source file where the node is.
|
||||
* @param guard
|
||||
* @param recursive Whether to go in matched nodes to keep matching.
|
||||
* @param max The maximum number of items to return.
|
||||
* @return all nodes of kind, or [] if none is found
|
||||
*/
|
||||
export function findAstNodes<T extends ts.Node>(
|
||||
node: ts.Node | null,
|
||||
sourceFile: ts.SourceFile,
|
||||
guard: (node: ts.Node) => node is T,
|
||||
recursive?: boolean,
|
||||
max?: number,
|
||||
): T[];
|
||||
|
||||
export function findAstNodes<T extends ts.Node>(
|
||||
node: ts.Node | null,
|
||||
sourceFile: ts.SourceFile,
|
||||
kindOrGuard: ts.SyntaxKind | ((node: ts.Node) => node is T),
|
||||
recursive = false,
|
||||
max = Infinity,
|
||||
): T[] {
|
||||
@ -35,23 +59,28 @@ export function findAstNodes<T extends ts.Node>(
|
||||
node = sourceFile;
|
||||
}
|
||||
|
||||
const test =
|
||||
typeof kindOrGuard === 'function'
|
||||
? kindOrGuard
|
||||
: (node: ts.Node): node is T => node.kind === kindOrGuard;
|
||||
|
||||
const arr: T[] = [];
|
||||
if (node.kind === kind) {
|
||||
if (test(node)) {
|
||||
// If we're not recursively looking for children, stop here.
|
||||
if (!recursive) {
|
||||
return [node as T];
|
||||
}
|
||||
|
||||
arr.push(node as T);
|
||||
arr.push(node);
|
||||
max--;
|
||||
}
|
||||
|
||||
if (max > 0) {
|
||||
for (const child of node.getChildren(sourceFile)) {
|
||||
findAstNodes(child, sourceFile, kind, recursive, max)
|
||||
.forEach((node: ts.Node) => {
|
||||
findAstNodes(child, sourceFile, test, recursive, max)
|
||||
.forEach((node) => {
|
||||
if (max > 0) {
|
||||
arr.push(node as T);
|
||||
arr.push(node);
|
||||
}
|
||||
max--;
|
||||
});
|
||||
|
@ -27,7 +27,7 @@ export function findResources(sourceFile: ts.SourceFile): string[] {
|
||||
|
||||
ts.visitNodes(
|
||||
(args[0] as ts.ObjectLiteralExpression).properties,
|
||||
(node: ts.ObjectLiteralElementLike) => {
|
||||
(node) => {
|
||||
if (!ts.isPropertyAssignment(node) || ts.isComputedPropertyName(node.name)) {
|
||||
return node;
|
||||
}
|
||||
@ -47,7 +47,7 @@ export function findResources(sourceFile: ts.SourceFile): string[] {
|
||||
return node;
|
||||
}
|
||||
|
||||
ts.visitNodes(node.initializer.elements, (node: ts.Expression) => {
|
||||
ts.visitNodes(node.initializer.elements, (node) => {
|
||||
const url = getResourceUrl(node);
|
||||
|
||||
if (url) {
|
||||
|
@ -69,12 +69,13 @@ export function insertImport(
|
||||
// Find all imports.
|
||||
const allImports = collectDeepNodes(sourceFile, ts.SyntaxKind.ImportDeclaration);
|
||||
const maybeImports = allImports
|
||||
.filter((node: ts.ImportDeclaration) => {
|
||||
.filter(ts.isImportDeclaration)
|
||||
.filter((node) => {
|
||||
// Filter all imports that do not match the modulePath.
|
||||
return node.moduleSpecifier.kind == ts.SyntaxKind.StringLiteral
|
||||
&& (node.moduleSpecifier as ts.StringLiteral).text == modulePath;
|
||||
})
|
||||
.filter((node: ts.ImportDeclaration) => {
|
||||
.filter((node) => {
|
||||
// Filter out import statements that are either `import 'XYZ'` or `import * as X from 'XYZ'`.
|
||||
const clause = node.importClause as ts.ImportClause;
|
||||
if (!clause || clause.name || !clause.namedBindings) {
|
||||
@ -83,7 +84,7 @@ export function insertImport(
|
||||
|
||||
return clause.namedBindings.kind == ts.SyntaxKind.NamedImports;
|
||||
})
|
||||
.map((node: ts.ImportDeclaration) => {
|
||||
.map((node) => {
|
||||
// Return the `{ ... }` list of the named import.
|
||||
return (node.importClause as ts.ImportClause).namedBindings as ts.NamedImports;
|
||||
});
|
||||
|
@ -29,8 +29,10 @@ export function replaceResources(
|
||||
|
||||
const visitNode: ts.Visitor = (node: ts.Node) => {
|
||||
if (ts.isClassDeclaration(node)) {
|
||||
const decorators = ts.visitNodes(node.decorators, (node: ts.Decorator) =>
|
||||
visitDecorator(context, node, typeChecker, directTemplateLoading),
|
||||
const decorators = ts.visitNodes(node.decorators, (node) =>
|
||||
ts.isDecorator(node)
|
||||
? visitDecorator(context, node, typeChecker, directTemplateLoading)
|
||||
: node,
|
||||
);
|
||||
|
||||
return ts.updateClassDeclaration(
|
||||
@ -82,8 +84,10 @@ function visitDecorator(
|
||||
const styleReplacements: ts.Expression[] = [];
|
||||
|
||||
// visit all properties
|
||||
let properties = ts.visitNodes(objectExpression.properties, (node: ts.ObjectLiteralElementLike) =>
|
||||
visitComponentMetadata(context, node, styleReplacements, directTemplateLoading),
|
||||
let properties = ts.visitNodes(objectExpression.properties, (node) =>
|
||||
ts.isObjectLiteralElementLike(node)
|
||||
? visitComponentMetadata(context, node, styleReplacements, directTemplateLoading)
|
||||
: node,
|
||||
);
|
||||
|
||||
// replace properties with updated properties
|
||||
@ -137,7 +141,7 @@ function visitComponentMetadata(
|
||||
}
|
||||
|
||||
const isInlineStyles = name === 'styles';
|
||||
const styles = ts.visitNodes(node.initializer.elements, (node: ts.Expression) => {
|
||||
const styles = ts.visitNodes(node.initializer.elements, (node) => {
|
||||
if (!ts.isStringLiteral(node) && !ts.isNoSubstitutionTemplateLiteral(node)) {
|
||||
return node;
|
||||
}
|
||||
@ -161,7 +165,7 @@ function visitComponentMetadata(
|
||||
}
|
||||
}
|
||||
|
||||
export function getResourceUrl(node: ts.Expression, loader = ''): string | null {
|
||||
export function getResourceUrl(node: ts.Node, loader = ''): string | null {
|
||||
// only analyze strings
|
||||
if (!ts.isStringLiteral(node) && !ts.isNoSubstitutionTemplateLiteral(node)) {
|
||||
return null;
|
||||
|
Loading…
x
Reference in New Issue
Block a user