refactor(core): add tslint rule entry-point for static-query migration (#29258)

In order to be able to use the static-query migration logic within
Google, we need to provide a TSLint rule entry-point that wires up
the schematic logic and provides reporting and automatic fixes.

PR Close #29258
This commit is contained in:
Paul Gschwendtner
2019-03-13 16:29:25 +01:00
committed by Matias Niemelä
parent 7b70760c8d
commit 8ef46f38f4
10 changed files with 332 additions and 117 deletions

View File

@ -0,0 +1,12 @@
load("//tools:defaults.bzl", "ts_library")
ts_library(
name = "google3",
srcs = glob(["**/*.ts"]),
tsconfig = "//packages/core/schematics:tsconfig.json",
visibility = ["//packages/core/schematics/test:__pkg__"],
deps = [
"//packages/core/schematics/migrations/static-queries",
"@npm//tslint",
],
)

View File

@ -0,0 +1,66 @@
/**
* @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 {Replacement, RuleFailure, Rules} from 'tslint';
import * as ts from 'typescript';
import {analyzeNgQueryUsage} from '../angular/analyze_query_usage';
import {NgQueryResolveVisitor} from '../angular/ng_query_visitor';
import {QueryTiming} from '../angular/query-definition';
import {getTransformedQueryCallExpr} from '../transform';
/**
* Rule that reports if an Angular "ViewChild" or "ContentChild" query is not explicitly
* specifying its timing. The rule also provides TSLint automatic replacements that can
* be applied in order to automatically migrate to the explicit query timing API.
*/
export class Rule extends Rules.TypedRule {
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] {
const typeChecker = program.getTypeChecker();
const queryVisitor = new NgQueryResolveVisitor(program.getTypeChecker());
const rootSourceFiles = program.getRootFileNames().map(f => program.getSourceFile(f) !);
const printer = ts.createPrinter();
const failures: RuleFailure[] = [];
// Analyze source files by detecting queries and class relations.
rootSourceFiles.forEach(sourceFile => queryVisitor.visitNode(sourceFile));
const {resolvedQueries, classMetadata} = queryVisitor;
const queries = resolvedQueries.get(sourceFile);
// No queries detected for the given source file.
if (!queries) {
return [];
}
// Compute the query usage for all resolved queries and update the
// query definitions to explicitly declare the query timing (static or dynamic)
queries.forEach(q => {
const queryExpr = q.decorator.node.expression;
const timing = analyzeNgQueryUsage(q, classMetadata, typeChecker);
const transformedNode = getTransformedQueryCallExpr(q, timing);
if (!transformedNode) {
return;
}
const newText = printer.printNode(ts.EmitHint.Unspecified, transformedNode, sourceFile);
// Replace the existing query decorator call expression with the
// updated call expression node.
const fix = new Replacement(queryExpr.getStart(), queryExpr.getWidth(), newText);
const timingStr = timing === QueryTiming.STATIC ? 'static' : 'dynamic';
failures.push(new RuleFailure(
sourceFile, queryExpr.getStart(), queryExpr.getWidth(),
`Query is not explicitly marked as "${timingStr}"`, this.ruleName, fix));
});
return failures;
}
}