forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): add dynamic queries schematic (angular#32231)
Adds a schematic that will remove the explicit `static: false` flag from dynamic queries. E.g. ```ts import { Directive, ViewChild, ContentChild, ElementRef } from '@angular/core'; @directive() export class MyDirective { @ViewChild('child', { static: false }) child: any; @ViewChild('secondChild', { read: ElementRef, static: false }) secondChild: ElementRef; @ContentChild('thirdChild', { static: false }) thirdChild: any; } ``` ```ts import { Directive, ViewChild, ContentChild, ElementRef } from '@angular/core'; @directive() export class MyDirective { @ViewChild('child') child: any; @ViewChild('secondChild', { read: ElementRef }) secondChild: ElementRef; @ContentChild('thirdChild') thirdChild: any; } ``` PR Close angular#32231
- Loading branch information
Showing
11 changed files
with
604 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
packages/core/schematics/migrations/dynamic-queries/BUILD.bazel
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
load("//tools:defaults.bzl", "ts_library") | ||
|
||
ts_library( | ||
name = "dynamic-queries", | ||
srcs = glob(["**/*.ts"]), | ||
tsconfig = "//packages/core/schematics:tsconfig.json", | ||
visibility = [ | ||
"//packages/core/schematics:__pkg__", | ||
"//packages/core/schematics/migrations/google3:__pkg__", | ||
"//packages/core/schematics/test:__pkg__", | ||
], | ||
deps = [ | ||
"//packages/core/schematics/utils", | ||
"@npm//@angular-devkit/schematics", | ||
"@npm//@types/node", | ||
"@npm//typescript", | ||
], | ||
) |
28 changes: 28 additions & 0 deletions
28
packages/core/schematics/migrations/dynamic-queries/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
## Dynamic queries migration | ||
|
||
Automatically migrates dynamic queries to remove their `static` flag. This flag will no | ||
longer be necessary in version 9 for dynamic queries, as `false` is the default value. | ||
|
||
#### Before | ||
```ts | ||
import { Directive, ViewChild, ContentChild, ElementRef } from '@angular/core'; | ||
|
||
@Directive() | ||
export class MyDirective { | ||
@ViewChild('child', { static: false }) child: any; | ||
@ViewChild('secondChild', { read: ElementRef, static: false }) secondChild: ElementRef; | ||
@ContentChild('thirdChild', { static: false }) thirdChild: any; | ||
} | ||
``` | ||
|
||
#### After | ||
```ts | ||
import { Directive, ViewChild, ContentChild, ElementRef } from '@angular/core'; | ||
|
||
@Directive() | ||
export class MyDirective { | ||
@ViewChild('child') child: any; | ||
@ViewChild('secondChild', { read: ElementRef }) secondChild: ElementRef; | ||
@ContentChild('thirdChild') thirdChild: any; | ||
} | ||
``` |
85 changes: 85 additions & 0 deletions
85
packages/core/schematics/migrations/dynamic-queries/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/** | ||
* @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 {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics'; | ||
import {dirname, relative} from 'path'; | ||
import * as ts from 'typescript'; | ||
|
||
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths'; | ||
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig'; | ||
import {identifyDynamicQueryNodes, removeOptionsParameter, removeStaticFlag} from './util'; | ||
|
||
|
||
/** | ||
* Runs the dynamic queries migration for all TypeScript projects in the current CLI workspace. | ||
*/ | ||
export default function(): Rule { | ||
return (tree: Tree, ctx: SchematicContext) => { | ||
const {buildPaths, testPaths} = getProjectTsConfigPaths(tree); | ||
const basePath = process.cwd(); | ||
const allPaths = [...buildPaths, ...testPaths]; | ||
|
||
ctx.logger.info('------ Dynamic queries migration ------'); | ||
|
||
if (!allPaths.length) { | ||
throw new SchematicsException( | ||
'Could not find any tsconfig file. Cannot migrate dynamic queries.'); | ||
} | ||
|
||
for (const tsconfigPath of allPaths) { | ||
runDynamicQueryMigration(tree, tsconfigPath, basePath); | ||
} | ||
}; | ||
} | ||
|
||
function runDynamicQueryMigration(tree: Tree, tsconfigPath: string, basePath: string) { | ||
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. Otherwise | ||
// if we run the migration for multiple tsconfig files which have intersecting | ||
// source files, it can end up updating query definitions multiple times. | ||
host.readFile = fileName => { | ||
const buffer = tree.read(relative(basePath, fileName)); | ||
// Strip BOM as otherwise TSC methods (Ex: getWidth) will return an offset which | ||
// which breaks the CLI UpdateRecorder. | ||
// See: https://github.com/angular/angular/pull/30719 | ||
return buffer ? buffer.toString().replace(/^\uFEFF/, '') : undefined; | ||
}; | ||
|
||
const program = ts.createProgram(parsed.fileNames, parsed.options, host); | ||
const typeChecker = program.getTypeChecker(); | ||
const sourceFiles = program.getSourceFiles().filter( | ||
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f)); | ||
const printer = ts.createPrinter(); | ||
|
||
sourceFiles.forEach(sourceFile => { | ||
const result = identifyDynamicQueryNodes(typeChecker, sourceFile); | ||
|
||
if (result.removeProperty.length || result.removeParameter.length) { | ||
const update = tree.beginUpdate(relative(basePath, sourceFile.fileName)); | ||
|
||
result.removeProperty.forEach(node => { | ||
update.remove(node.getStart(), node.getWidth()); | ||
update.insertRight( | ||
node.getStart(), | ||
printer.printNode(ts.EmitHint.Unspecified, removeStaticFlag(node), sourceFile)); | ||
}); | ||
|
||
result.removeParameter.forEach(node => { | ||
update.remove(node.getStart(), node.getWidth()); | ||
update.insertRight( | ||
node.getStart(), | ||
printer.printNode(ts.EmitHint.Unspecified, removeOptionsParameter(node), sourceFile)); | ||
}); | ||
|
||
tree.commitUpdate(update); | ||
} | ||
}); | ||
} |
77 changes: 77 additions & 0 deletions
77
packages/core/schematics/migrations/dynamic-queries/util.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/** | ||
* @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 {getAngularDecorators} from '../../utils/ng_decorators'; | ||
|
||
/** | ||
* Identifies the nodes that should be migrated by the dynamic | ||
* queries schematic. Splits the nodes into the following categories: | ||
* - `removeProperty` - queries from which we should only remove the `static` property of the | ||
* `options` parameter (e.g. `@ViewChild('child', {static: false, read: ElementRef})`). | ||
* - `removeParameter` - queries from which we should drop the entire `options` parameter. | ||
* (e.g. `@ViewChild('child', {static: false})`). | ||
*/ | ||
export function identifyDynamicQueryNodes(typeChecker: ts.TypeChecker, sourceFile: ts.SourceFile) { | ||
const removeProperty: ts.ObjectLiteralExpression[] = []; | ||
const removeParameter: ts.CallExpression[] = []; | ||
|
||
sourceFile.forEachChild(function walk(node: ts.Node) { | ||
if (ts.isClassDeclaration(node)) { | ||
node.members.forEach(member => { | ||
const angularDecorators = | ||
member.decorators && getAngularDecorators(typeChecker, member.decorators); | ||
|
||
if (angularDecorators) { | ||
angularDecorators | ||
// Filter out the queries that can have the `static` flag. | ||
.filter(decorator => { | ||
return decorator.name === 'ViewChild' || decorator.name === 'ContentChild'; | ||
}) | ||
// Filter out the queries where the `static` flag is explicitly set to `false`. | ||
.filter(decorator => { | ||
const options = decorator.node.expression.arguments[1]; | ||
return options && ts.isObjectLiteralExpression(options) && | ||
options.properties.some( | ||
property => ts.isPropertyAssignment(property) && | ||
property.initializer.kind === ts.SyntaxKind.FalseKeyword); | ||
}) | ||
.forEach(decorator => { | ||
const options = | ||
decorator.node.expression.arguments[1] as ts.ObjectLiteralExpression; | ||
|
||
// At this point we know that at least one property is the `static` flag. If this is | ||
// the only property we can drop the entire object literal, otherwise we have to | ||
// drop only the property. | ||
if (options.properties.length === 1) { | ||
removeParameter.push(decorator.node.expression); | ||
} else { | ||
removeProperty.push(options); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
node.forEachChild(walk); | ||
}); | ||
|
||
return {removeProperty, removeParameter}; | ||
} | ||
|
||
/** Removes the `options` parameter from the call expression of a query decorator. */ | ||
export function removeOptionsParameter(node: ts.CallExpression): ts.CallExpression { | ||
return ts.updateCall(node, node.expression, node.typeArguments, [node.arguments[0]]); | ||
} | ||
|
||
/** Removes the `static` property from an object literal expression. */ | ||
export function removeStaticFlag(node: ts.ObjectLiteralExpression): ts.ObjectLiteralExpression { | ||
return ts.updateObjectLiteral( | ||
node, | ||
node.properties.filter(property => property.name && property.name.getText() !== 'static')); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
packages/core/schematics/migrations/google3/dynamicQueriesRule.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/** | ||
* @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 {identifyDynamicQueryNodes, removeOptionsParameter, removeStaticFlag} from '../dynamic-queries/util'; | ||
|
||
const RULE_NAME = 'dynamic-queries'; | ||
const FAILURE_MESSAGE = | ||
'The static flag defaults to false, so setting it false manually is unnecessary.'; | ||
|
||
/** | ||
* TSLint rule that removes the `static` flag from dynamic queries. | ||
*/ | ||
export class Rule extends Rules.TypedRule { | ||
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] { | ||
const printer = ts.createPrinter(); | ||
const failures: RuleFailure[] = []; | ||
const result = identifyDynamicQueryNodes(program.getTypeChecker(), sourceFile); | ||
|
||
result.removeProperty.forEach(node => { | ||
failures.push(new RuleFailure( | ||
sourceFile, node.getStart(), node.getEnd(), FAILURE_MESSAGE, RULE_NAME, | ||
new Replacement( | ||
node.getStart(), node.getWidth(), | ||
printer.printNode(ts.EmitHint.Unspecified, removeStaticFlag(node), sourceFile)))); | ||
}); | ||
|
||
result.removeParameter.forEach(node => { | ||
failures.push(new RuleFailure( | ||
sourceFile, node.getStart(), node.getEnd(), FAILURE_MESSAGE, RULE_NAME, | ||
new Replacement( | ||
node.getStart(), node.getWidth(), | ||
printer.printNode( | ||
ts.EmitHint.Unspecified, removeOptionsParameter(node), sourceFile)))); | ||
}); | ||
|
||
return failures; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.