refactor(core): move usage detection into usage strategy (#29815)
In order to support multiple strategies for detecting the query timing, the query usage logic has been moved into a query usage strategy. PR Close #29815
This commit is contained in:
parent
7a7781e925
commit
205a45e9a8
@ -7,48 +7,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {isFunctionLikeDeclaration} from '../../../utils/typescript/functions';
|
|
||||||
import {hasModifier} from '../../../utils/typescript/nodes';
|
|
||||||
import {getPropertyNameText} from '../../../utils/typescript/property_name';
|
|
||||||
|
|
||||||
import {FunctionContext} from './declaration_usage_visitor';
|
|
||||||
import {ClassMetadataMap} from './ng_query_visitor';
|
import {ClassMetadataMap} from './ng_query_visitor';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the specified function context to map abstract super-class class members
|
* Gets all chained super-class TypeScript declarations for the given class
|
||||||
* to their implementation TypeScript nodes. This allows us to run the declaration visitor
|
* by using the specified class metadata map.
|
||||||
* for the super class with the context of the "baseClass" (e.g. with implemented abstract
|
|
||||||
* class members)
|
|
||||||
*/
|
*/
|
||||||
export function updateSuperClassAbstractMembersContext(
|
export function getSuperClassDeclarations(
|
||||||
baseClass: ts.ClassDeclaration, context: FunctionContext, classMetadataMap: ClassMetadataMap) {
|
|
||||||
getSuperClassDeclarations(baseClass, classMetadataMap).forEach(superClassDecl => {
|
|
||||||
superClassDecl.members.forEach(superClassMember => {
|
|
||||||
if (!superClassMember.name || !hasModifier(superClassMember, ts.SyntaxKind.AbstractKeyword)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the matching implementation of the abstract declaration from the super class.
|
|
||||||
const baseClassImpl = baseClass.members.find(
|
|
||||||
baseClassMethod => !!baseClassMethod.name &&
|
|
||||||
getPropertyNameText(baseClassMethod.name) ===
|
|
||||||
getPropertyNameText(superClassMember.name !));
|
|
||||||
|
|
||||||
if (!baseClassImpl || !isFunctionLikeDeclaration(baseClassImpl) || !baseClassImpl.body) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!context.has(superClassMember)) {
|
|
||||||
context.set(superClassMember, baseClassImpl);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Gets all super-class TypeScript declarations for the given class. */
|
|
||||||
function getSuperClassDeclarations(
|
|
||||||
classDecl: ts.ClassDeclaration, classMetadataMap: ClassMetadataMap) {
|
classDecl: ts.ClassDeclaration, classMetadataMap: ClassMetadataMap) {
|
||||||
const declarations: ts.ClassDeclaration[] = [];
|
const declarations: ts.ClassDeclaration[] = [];
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {NgComponentTemplateVisitor} from '../../../utils/ng_component_template';
|
import {NgComponentTemplateVisitor} from '../../../utils/ng_component_template';
|
||||||
import {visitAllNodes} from '../../../utils/typescript/visit_nodes';
|
import {visitAllNodes} from '../../../utils/typescript/visit_nodes';
|
||||||
import {analyzeNgQueryUsage} from '../angular/analyze_query_usage';
|
|
||||||
import {NgQueryResolveVisitor} from '../angular/ng_query_visitor';
|
import {NgQueryResolveVisitor} from '../angular/ng_query_visitor';
|
||||||
import {QueryTiming} from '../angular/query-definition';
|
import {QueryTiming} from '../angular/query-definition';
|
||||||
|
import {QueryUsageStrategy} from '../strategies/usage_strategy/usage_strategy';
|
||||||
import {getTransformedQueryCallExpr} from '../transform';
|
import {getTransformedQueryCallExpr} from '../transform';
|
||||||
|
|
||||||
const FAILURE_MESSAGE = 'Query does not explicitly specify its timing. Read more here: ' +
|
const FAILURE_MESSAGE = 'Query does not explicitly specify its timing. Read more here: ' +
|
||||||
@ -52,6 +52,7 @@ export class Rule extends Rules.TypedRule {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const queries = resolvedQueries.get(sourceFile);
|
const queries = resolvedQueries.get(sourceFile);
|
||||||
|
const usageStrategy = new QueryUsageStrategy(classMetadata, typeChecker);
|
||||||
|
|
||||||
// No queries detected for the given source file.
|
// No queries detected for the given source file.
|
||||||
if (!queries) {
|
if (!queries) {
|
||||||
@ -62,7 +63,7 @@ export class Rule extends Rules.TypedRule {
|
|||||||
// query definitions to explicitly declare the query timing (static or dynamic)
|
// query definitions to explicitly declare the query timing (static or dynamic)
|
||||||
queries.forEach(q => {
|
queries.forEach(q => {
|
||||||
const queryExpr = q.decorator.node.expression;
|
const queryExpr = q.decorator.node.expression;
|
||||||
const timing = analyzeNgQueryUsage(q, classMetadata, typeChecker);
|
const timing = usageStrategy.detectTiming(q);
|
||||||
const transformedNode = getTransformedQueryCallExpr(q, timing);
|
const transformedNode = getTransformedQueryCallExpr(q, timing);
|
||||||
|
|
||||||
if (!transformedNode) {
|
if (!transformedNode) {
|
||||||
|
@ -15,8 +15,8 @@ import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
|
|||||||
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
|
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
|
||||||
import {visitAllNodes} from '../../utils/typescript/visit_nodes';
|
import {visitAllNodes} from '../../utils/typescript/visit_nodes';
|
||||||
|
|
||||||
import {analyzeNgQueryUsage} from './angular/analyze_query_usage';
|
|
||||||
import {NgQueryResolveVisitor} from './angular/ng_query_visitor';
|
import {NgQueryResolveVisitor} from './angular/ng_query_visitor';
|
||||||
|
import {QueryUsageStrategy} from './strategies/usage_strategy/usage_strategy';
|
||||||
import {getTransformedQueryCallExpr} from './transform';
|
import {getTransformedQueryCallExpr} from './transform';
|
||||||
|
|
||||||
|
|
||||||
@ -83,6 +83,8 @@ function runStaticQueryMigration(tree: Tree, tsconfigPath: string, basePath: str
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const usageStrategy = new QueryUsageStrategy(classMetadata, typeChecker);
|
||||||
|
|
||||||
// Walk through all source files that contain resolved queries and update
|
// Walk through all source files that contain resolved queries and update
|
||||||
// the source files if needed. Note that we need to update multiple queries
|
// the source files if needed. Note that we need to update multiple queries
|
||||||
// within a source file within the same recorder in order to not throw off
|
// within a source file within the same recorder in order to not throw off
|
||||||
@ -94,7 +96,7 @@ function runStaticQueryMigration(tree: Tree, tsconfigPath: string, basePath: str
|
|||||||
// query definitions to explicitly declare the query timing (static or dynamic)
|
// query definitions to explicitly declare the query timing (static or dynamic)
|
||||||
queries.forEach(q => {
|
queries.forEach(q => {
|
||||||
const queryExpr = q.decorator.node.expression;
|
const queryExpr = q.decorator.node.expression;
|
||||||
const timing = analyzeNgQueryUsage(q, classMetadata, typeChecker);
|
const timing = usageStrategy.detectTiming(q);
|
||||||
const transformedNode = getTransformedQueryCallExpr(q, timing);
|
const transformedNode = getTransformedQueryCallExpr(q, timing);
|
||||||
|
|
||||||
if (!transformedNode) {
|
if (!transformedNode) {
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {isFunctionLikeDeclaration, unwrapExpression} from '../../../utils/typescript/functions';
|
import {isFunctionLikeDeclaration, unwrapExpression} from '../../../../utils/typescript/functions';
|
||||||
|
|
||||||
export type FunctionContext = Map<ts.Node, ts.Node>;
|
export type FunctionContext = Map<ts.Node, ts.Node>;
|
||||||
|
|
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* @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 {isFunctionLikeDeclaration} from '../../../../utils/typescript/functions';
|
||||||
|
import {hasModifier} from '../../../../utils/typescript/nodes';
|
||||||
|
import {getPropertyNameText} from '../../../../utils/typescript/property_name';
|
||||||
|
import {ClassMetadataMap} from '../../angular/ng_query_visitor';
|
||||||
|
import {getSuperClassDeclarations} from '../../angular/super_class';
|
||||||
|
|
||||||
|
import {FunctionContext} from './declaration_usage_visitor';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the specified function context to map abstract super-class class members
|
||||||
|
* to their implementation TypeScript nodes. This allows us to run the declaration visitor
|
||||||
|
* for the super class with the context of the "baseClass" (e.g. with implemented abstract
|
||||||
|
* class members)
|
||||||
|
*/
|
||||||
|
export function updateSuperClassAbstractMembersContext(
|
||||||
|
baseClass: ts.ClassDeclaration, context: FunctionContext, classMetadataMap: ClassMetadataMap) {
|
||||||
|
getSuperClassDeclarations(baseClass, classMetadataMap).forEach(superClassDecl => {
|
||||||
|
superClassDecl.members.forEach(superClassMember => {
|
||||||
|
if (!superClassMember.name || !hasModifier(superClassMember, ts.SyntaxKind.AbstractKeyword)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the matching implementation of the abstract declaration from the super class.
|
||||||
|
const baseClassImpl = baseClass.members.find(
|
||||||
|
baseClassMethod => !!baseClassMethod.name &&
|
||||||
|
getPropertyNameText(baseClassMethod.name) ===
|
||||||
|
getPropertyNameText(superClassMember.name !));
|
||||||
|
|
||||||
|
if (!baseClassImpl || !isFunctionLikeDeclaration(baseClassImpl) || !baseClassImpl.body) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context.has(superClassMember)) {
|
||||||
|
context.set(superClassMember, baseClassImpl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -13,7 +13,7 @@ import {BoundAttribute, BoundEvent, BoundText, Element, Node, NullVisitor, Templ
|
|||||||
* AST visitor that traverses the Render3 HTML AST in order to check if the given
|
* AST visitor that traverses the Render3 HTML AST in order to check if the given
|
||||||
* query property is accessed statically in the template.
|
* query property is accessed statically in the template.
|
||||||
*/
|
*/
|
||||||
export class QueryReadHtmlVisitor extends NullVisitor {
|
export class TemplateUsageVisitor extends NullVisitor {
|
||||||
private hasQueryTemplateReference = false;
|
private hasQueryTemplateReference = false;
|
||||||
private expressionAstVisitor = new ExpressionAstVisitor(this.queryPropertyName);
|
private expressionAstVisitor = new ExpressionAstVisitor(this.queryPropertyName);
|
||||||
|
|
@ -8,14 +8,15 @@
|
|||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {parseHtmlGracefully} from '../../../utils/parse_html';
|
import {parseHtmlGracefully} from '../../../../utils/parse_html';
|
||||||
import {hasPropertyNameText} from '../../../utils/typescript/property_name';
|
import {hasPropertyNameText} from '../../../../utils/typescript/property_name';
|
||||||
|
import {ClassMetadataMap} from '../../angular/ng_query_visitor';
|
||||||
|
import {NgQueryDefinition, QueryTiming, QueryType} from '../../angular/query-definition';
|
||||||
|
import {TimingStrategy} from '../../timing-strategy';
|
||||||
|
|
||||||
import {DeclarationUsageVisitor, FunctionContext} from './declaration_usage_visitor';
|
import {DeclarationUsageVisitor, FunctionContext} from './declaration_usage_visitor';
|
||||||
import {ClassMetadataMap} from './ng_query_visitor';
|
import {updateSuperClassAbstractMembersContext} from './super_class_context';
|
||||||
import {NgQueryDefinition, QueryTiming, QueryType} from './query-definition';
|
import {TemplateUsageVisitor} from './template_usage_visitor';
|
||||||
import {QueryReadHtmlVisitor} from './query_read_html_visitor';
|
|
||||||
import {updateSuperClassAbstractMembersContext} from './super_class';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,17 +30,25 @@ const STATIC_QUERY_LIFECYCLE_HOOKS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analyzes the usage of the given query and determines the query timing based
|
* Query timing strategy that determines the timing of a given query by inspecting how
|
||||||
* on the current usage of the query.
|
* the query is accessed within the project's TypeScript source files. Read more about
|
||||||
|
* this strategy here: https://hackmd.io/s/Hymvc2OKE
|
||||||
*/
|
*/
|
||||||
export function analyzeNgQueryUsage(
|
export class QueryUsageStrategy implements TimingStrategy {
|
||||||
query: NgQueryDefinition, classMetadata: ClassMetadataMap,
|
constructor(private classMetadata: ClassMetadataMap, private typeChecker: ts.TypeChecker) {}
|
||||||
typeChecker: ts.TypeChecker): QueryTiming {
|
|
||||||
return isQueryUsedStatically(query.container, query, classMetadata, typeChecker, []) ?
|
/**
|
||||||
QueryTiming.STATIC :
|
* Analyzes the usage of the given query and determines the query timing based
|
||||||
QueryTiming.DYNAMIC;
|
* on the current usage of the query.
|
||||||
|
*/
|
||||||
|
detectTiming(query: NgQueryDefinition): QueryTiming {
|
||||||
|
return isQueryUsedStatically(query.container, query, this.classMetadata, this.typeChecker, []) ?
|
||||||
|
QueryTiming.STATIC :
|
||||||
|
QueryTiming.DYNAMIC;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a given query is used statically within the given class, its super
|
* Checks whether a given query is used statically within the given class, its super
|
||||||
* class or derived classes.
|
* class or derived classes.
|
||||||
@ -79,7 +88,7 @@ function isQueryUsedStatically(
|
|||||||
if (classMetadata.template && hasPropertyNameText(query.property.name)) {
|
if (classMetadata.template && hasPropertyNameText(query.property.name)) {
|
||||||
const template = classMetadata.template;
|
const template = classMetadata.template;
|
||||||
const parsedHtml = parseHtmlGracefully(template.content, template.filePath);
|
const parsedHtml = parseHtmlGracefully(template.content, template.filePath);
|
||||||
const htmlVisitor = new QueryReadHtmlVisitor(query.property.name.text);
|
const htmlVisitor = new TemplateUsageVisitor(query.property.name.text);
|
||||||
|
|
||||||
if (parsedHtml && htmlVisitor.isQueryUsedStatically(parsedHtml)) {
|
if (parsedHtml && htmlVisitor.isQueryUsedStatically(parsedHtml)) {
|
||||||
return true;
|
return true;
|
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @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 {NgQueryDefinition, QueryTiming} from './angular/query-definition';
|
||||||
|
|
||||||
|
export interface TimingStrategy { detectTiming(query: NgQueryDefinition): QueryTiming; }
|
Loading…
x
Reference in New Issue
Block a user