
Currently if there are multiple source files within a given TypeScript source file, only the last template in the source file is checked as we store templates in a `Map` with the source file paths as keys. This is problematic as multiple templates can live within the same source file and we therefore accidentally overwrite existing entries in the resolved templates map. PR Close #29841
101 lines
3.9 KiB
TypeScript
101 lines
3.9 KiB
TypeScript
/**
|
|
* @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 {logging, normalize} from '@angular-devkit/core';
|
|
import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics';
|
|
import {dirname, relative} from 'path';
|
|
import * as ts from 'typescript';
|
|
|
|
import {NgComponentTemplateVisitor} from '../../utils/ng_component_template';
|
|
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
|
|
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
|
|
import {visitAllNodes} from '../../utils/typescript/visit_nodes';
|
|
|
|
import {analyzeResolvedTemplate} from './analyze_template';
|
|
|
|
type Logger = logging.LoggerApi;
|
|
|
|
const README_URL =
|
|
'https://github.com/angular/angular/tree/master/packages/core/schematics/migrations/template-var-assignment/README.md';
|
|
const FAILURE_MESSAGE = `Found assignment to template variable.`;
|
|
|
|
/** Entry point for the V8 template variable assignment schematic. */
|
|
export default function(): Rule {
|
|
return (tree: Tree, context: SchematicContext) => {
|
|
const projectTsConfigPaths = getProjectTsConfigPaths(tree);
|
|
const basePath = process.cwd();
|
|
|
|
if (!projectTsConfigPaths.length) {
|
|
throw new SchematicsException(
|
|
'Could not find any tsconfig file. Cannot check templates for template variable ' +
|
|
'assignments.');
|
|
}
|
|
|
|
for (const tsconfigPath of projectTsConfigPaths) {
|
|
runTemplateVariableAssignmentCheck(tree, tsconfigPath, basePath, context.logger);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Runs the template variable assignment check. Warns developers
|
|
* if values are assigned to template variables within output bindings.
|
|
*/
|
|
function runTemplateVariableAssignmentCheck(
|
|
tree: Tree, tsconfigPath: string, basePath: string, logger: Logger) {
|
|
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
|
|
const host = ts.createCompilerHost(parsed.options, true);
|
|
|
|
// We need to overwrite the host "readFile" method, as we want the TypeScript
|
|
// program to be based on the file contents in the virtual file tree.
|
|
host.readFile = fileName => {
|
|
const buffer = tree.read(relative(basePath, fileName));
|
|
return buffer ? buffer.toString() : undefined;
|
|
};
|
|
|
|
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
|
|
const typeChecker = program.getTypeChecker();
|
|
const templateVisitor = new NgComponentTemplateVisitor(typeChecker);
|
|
const rootSourceFiles = program.getRootFileNames().map(f => program.getSourceFile(f) !);
|
|
|
|
// Analyze source files by detecting HTML templates.
|
|
rootSourceFiles.forEach(sourceFile => visitAllNodes(sourceFile, [templateVisitor]));
|
|
|
|
const {resolvedTemplates} = templateVisitor;
|
|
const collectedFailures: string[] = [];
|
|
|
|
// Analyze each resolved template and print a warning for property writes to
|
|
// template variables.
|
|
resolvedTemplates.forEach(template => {
|
|
const filePath = template.filePath;
|
|
const nodes = analyzeResolvedTemplate(template);
|
|
|
|
if (!nodes) {
|
|
return;
|
|
}
|
|
|
|
const displayFilePath = normalize(relative(basePath, filePath));
|
|
|
|
nodes.forEach(n => {
|
|
const {line, character} = template.getCharacterAndLineOfPosition(n.start);
|
|
collectedFailures.push(`${displayFilePath}@${line + 1}:${character + 1}: ${FAILURE_MESSAGE}`);
|
|
});
|
|
});
|
|
|
|
if (collectedFailures.length) {
|
|
logger.info('---- Template Variable Assignment schematic ----');
|
|
logger.info('Assignments to template variables will no longer work with Ivy as');
|
|
logger.info('template variables are effectively constants in Ivy. Read more about');
|
|
logger.info(`this change here: ${README_URL}`);
|
|
logger.info('');
|
|
logger.info('The following template assignments were found:');
|
|
collectedFailures.forEach(failure => logger.warn(`⮑ ${failure}`));
|
|
logger.info('------------------------------------------------');
|
|
}
|
|
}
|