refactor(core): move schematic component template visitor to utils (#29713)

PR Close #29713
This commit is contained in:
Paul Gschwendtner
2019-04-04 14:04:31 +02:00
committed by Igor Minar
parent cc2e4b639b
commit b507d076be
5 changed files with 8 additions and 7 deletions

View File

@ -1,126 +0,0 @@
/**
* @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 {existsSync, readFileSync} from 'fs';
import {dirname, resolve} from 'path';
import * as ts from 'typescript';
import {computeLineStartsMap, getLineAndCharacterFromPosition} from '../../../utils/line_mappings';
import {getAngularDecorators} from '../../../utils/ng_decorators';
import {unwrapExpression} from '../../../utils/typescript/functions';
import {getPropertyNameText} from '../../../utils/typescript/property_name';
export interface ResolvedTemplate {
/** File content of the given template. */
content: string;
/** Start offset of the template content (e.g. in the inline source file) */
start: number;
/** Whether the given template is inline or not. */
inline: boolean;
/**
* Gets the character and line of a given position index in the template.
* If the template is declared inline within a TypeScript source file, the line and
* character are based on the full source file content.
*/
getCharacterAndLineOfPosition: (pos: number) => { character: number, line: number };
}
/**
* Visitor that can be used to determine Angular templates referenced within given
* TypeScript source files (inline templates or external referenced templates)
*/
export class NgComponentTemplateVisitor {
resolvedTemplates = new Map<string, ResolvedTemplate>();
constructor(public typeChecker: ts.TypeChecker) {}
visitNode(node: ts.Node) {
switch (node.kind) {
case ts.SyntaxKind.ClassDeclaration:
this.visitClassDeclaration(node as ts.ClassDeclaration);
break;
}
ts.forEachChild(node, node => this.visitNode(node));
}
private visitClassDeclaration(node: ts.ClassDeclaration) {
if (!node.decorators || !node.decorators.length) {
return;
}
const ngDecorators = getAngularDecorators(this.typeChecker, node.decorators);
const componentDecorator = ngDecorators.find(dec => dec.name === 'Component');
// In case no "@Component" decorator could be found on the current class, skip.
if (!componentDecorator) {
return;
}
const decoratorCall = componentDecorator.node.expression;
// In case the component decorator call is not valid, skip this class declaration.
if (decoratorCall.arguments.length !== 1) {
return;
}
const componentMetadata = unwrapExpression(decoratorCall.arguments[0]);
// Ensure that the component metadata is an object literal expression.
if (!ts.isObjectLiteralExpression(componentMetadata)) {
return;
}
const sourceFile = node.getSourceFile();
const sourceFileName = sourceFile.fileName;
// Walk through all component metadata properties and determine the referenced
// HTML templates (either external or inline)
componentMetadata.properties.forEach(property => {
if (!ts.isPropertyAssignment(property)) {
return;
}
const propertyName = getPropertyNameText(property.name);
// In case there is an inline template specified, ensure that the value is statically
// analyzable by checking if the initializer is a string literal-like node.
if (propertyName === 'template' && ts.isStringLiteralLike(property.initializer)) {
// Need to add an offset of one to the start because the template quotes are
// not part of the template content.
const templateStartIdx = property.initializer.getStart() + 1;
this.resolvedTemplates.set(resolve(sourceFileName), {
content: property.initializer.text,
inline: true,
start: templateStartIdx,
getCharacterAndLineOfPosition:
pos => ts.getLineAndCharacterOfPosition(sourceFile, pos + templateStartIdx)
});
}
if (propertyName === 'templateUrl' && ts.isStringLiteralLike(property.initializer)) {
const templatePath = resolve(dirname(sourceFileName), property.initializer.text);
// In case the template does not exist in the file system, skip this
// external template.
if (!existsSync(templatePath)) {
return;
}
const fileContent = readFileSync(templatePath, 'utf8');
const lineStartsMap = computeLineStartsMap(fileContent);
this.resolvedTemplates.set(templatePath, {
content: fileContent,
inline: false,
start: 0,
getCharacterAndLineOfPosition: pos => getLineAndCharacterFromPosition(lineStartsMap, pos),
});
}
});
}
}