refactor(core): move schematic typescript logic to utility package (#29608)
PR Close #29608
This commit is contained in:

committed by
Jason Aden

parent
5a724b34bd
commit
780081def0
26
packages/core/schematics/utils/ng_decorators.ts
Normal file
26
packages/core/schematics/utils/ng_decorators.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @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 {getCallDecoratorImport} from './typescript/decorators';
|
||||
|
||||
export interface NgDecorator {
|
||||
name: string;
|
||||
node: ts.Decorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all decorators which are imported from an Angular package (e.g. "@angular/core")
|
||||
* from a list of decorators.
|
||||
*/
|
||||
export function getAngularDecorators(
|
||||
typeChecker: ts.TypeChecker, decorators: ReadonlyArray<ts.Decorator>): NgDecorator[] {
|
||||
return decorators.map(node => ({node, importData: getCallDecoratorImport(typeChecker, node)}))
|
||||
.filter(({importData}) => importData && importData.importModule.startsWith('@angular/'))
|
||||
.map(({node, importData}) => ({node, name: importData !.name}));
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @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';
|
||||
|
||||
/** Determines the base type identifiers of a specified class declaration. */
|
||||
export function getBaseTypeIdentifiers(node: ts.ClassDeclaration): ts.Identifier[]|null {
|
||||
if (!node.heritageClauses) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return node.heritageClauses.filter(clause => clause.token === ts.SyntaxKind.ExtendsKeyword)
|
||||
.reduce((types, clause) => types.concat(clause.types), [] as ts.ExpressionWithTypeArguments[])
|
||||
.map(typeExpression => typeExpression.expression)
|
||||
.filter(ts.isIdentifier);
|
||||
}
|
||||
|
||||
/** Gets the first found parent class declaration of a given node. */
|
||||
export function findParentClassDeclaration(node: ts.Node): ts.ClassDeclaration|null {
|
||||
while (!ts.isClassDeclaration(node)) {
|
||||
if (ts.isSourceFile(node)) {
|
||||
return null;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return node;
|
||||
}
|
23
packages/core/schematics/utils/typescript/decorators.ts
Normal file
23
packages/core/schematics/utils/typescript/decorators.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @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 {Import, getImportOfIdentifier} from './imports';
|
||||
|
||||
export function getCallDecoratorImport(
|
||||
typeChecker: ts.TypeChecker, decorator: ts.Decorator): Import|null {
|
||||
// Note that this does not cover the edge case where decorators are called from
|
||||
// a namespace import: e.g. "@core.Component()". This is not handled by Ngtsc either.
|
||||
if (!ts.isCallExpression(decorator.expression) ||
|
||||
!ts.isIdentifier(decorator.expression.expression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const identifier = decorator.expression.expression;
|
||||
return getImportOfIdentifier(typeChecker, identifier);
|
||||
}
|
29
packages/core/schematics/utils/typescript/functions.ts
Normal file
29
packages/core/schematics/utils/typescript/functions.ts
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @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';
|
||||
|
||||
/** Checks whether a given node is a function like declaration. */
|
||||
export function isFunctionLikeDeclaration(node: ts.Node): node is ts.FunctionLikeDeclaration {
|
||||
return ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) ||
|
||||
ts.isArrowFunction(node) || ts.isFunctionExpression(node) ||
|
||||
ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps a given expression TypeScript node. Expressions can be wrapped within multiple
|
||||
* parentheses. e.g. "(((({exp}))))()". The function should return the TypeScript node
|
||||
* referring to the inner expression. e.g "exp".
|
||||
*/
|
||||
export function unwrapExpression(node: ts.Expression | ts.ParenthesizedExpression): ts.Expression {
|
||||
if (ts.isParenthesizedExpression(node)) {
|
||||
return unwrapExpression(node.expression);
|
||||
} else {
|
||||
return node;
|
||||
}
|
||||
}
|
42
packages/core/schematics/utils/typescript/imports.ts
Normal file
42
packages/core/schematics/utils/typescript/imports.ts
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @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';
|
||||
|
||||
export type Import = {
|
||||
name: string,
|
||||
importModule: string
|
||||
};
|
||||
|
||||
/** Gets import information about the specified identifier by using the Type checker. */
|
||||
export function getImportOfIdentifier(typeChecker: ts.TypeChecker, node: ts.Identifier): Import|
|
||||
null {
|
||||
const symbol = typeChecker.getSymbolAtLocation(node);
|
||||
|
||||
if (!symbol || !symbol.declarations.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const decl = symbol.declarations[0];
|
||||
|
||||
if (!ts.isImportSpecifier(decl)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const importDecl = decl.parent.parent.parent;
|
||||
|
||||
if (!ts.isStringLiteral(importDecl.moduleSpecifier)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
// Handles aliased imports: e.g. "import {Component as myComp} from ...";
|
||||
name: decl.propertyName ? decl.propertyName.text : decl.name.text,
|
||||
importModule: importDecl.moduleSpecifier.text
|
||||
};
|
||||
}
|
21
packages/core/schematics/utils/typescript/parse_tsconfig.ts
Normal file
21
packages/core/schematics/utils/typescript/parse_tsconfig.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @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';
|
||||
|
||||
export function parseTsconfigFile(tsconfigPath: string, basePath: string): ts.ParsedCommandLine {
|
||||
const {config} = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
||||
const parseConfigHost = {
|
||||
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
|
||||
fileExists: ts.sys.fileExists,
|
||||
readDirectory: ts.sys.readDirectory,
|
||||
readFile: ts.sys.readFile,
|
||||
};
|
||||
|
||||
return ts.parseJsonConfigFileContent(config, parseConfigHost, basePath, {});
|
||||
}
|
28
packages/core/schematics/utils/typescript/property_name.ts
Normal file
28
packages/core/schematics/utils/typescript/property_name.ts
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @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';
|
||||
|
||||
/** Type that describes a property name with an obtainable text. */
|
||||
type PropertyNameWithText = Exclude<ts.PropertyName, ts.ComputedPropertyName>;
|
||||
|
||||
/**
|
||||
* Gets the text of the given property name. Returns null if the property
|
||||
* name couldn't be determined statically.
|
||||
*/
|
||||
export function getPropertyNameText(node: ts.PropertyName): string|null {
|
||||
if (ts.isIdentifier(node) || ts.isStringLiteralLike(node)) {
|
||||
return node.text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Checks whether the given property name has a text. */
|
||||
export function hasPropertyNameText(node: ts.PropertyName): node is PropertyNameWithText {
|
||||
return ts.isStringLiteral(node) || ts.isNumericLiteral(node) || ts.isIdentifier(node);
|
||||
}
|
Reference in New Issue
Block a user