refactor(core): move schematic typescript logic to utility package (#29608)

PR Close #29608
This commit is contained in:
Paul Gschwendtner
2019-03-28 11:24:02 +01:00
committed by Jason Aden
parent 5a724b34bd
commit 780081def0
15 changed files with 10 additions and 20 deletions

View 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}));
}

View File

@ -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;
}

View 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);
}

View 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;
}
}

View 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
};
}

View 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, {});
}

View 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);
}