feat(core): add dynamic queries schematic (#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 #32231
This commit is contained in:
parent
4f033235b1
commit
f5982fd746
|
@ -10,6 +10,7 @@ npm_package(
|
|||
srcs = ["migrations.json"],
|
||||
visibility = ["//packages/core:__pkg__"],
|
||||
deps = [
|
||||
"//packages/core/schematics/migrations/dynamic-queries",
|
||||
"//packages/core/schematics/migrations/missing-injectable",
|
||||
"//packages/core/schematics/migrations/move-document",
|
||||
"//packages/core/schematics/migrations/renderer-to-renderer2",
|
||||
|
|
|
@ -34,6 +34,11 @@
|
|||
"version": "9-beta",
|
||||
"description": "Adds an Angular decorator to undecorated classes that have decorated fields",
|
||||
"factory": "./migrations/undecorated-classes-with-decorated-fields/index"
|
||||
},
|
||||
"migration-v9-dynamic-queries": {
|
||||
"version": "9-beta",
|
||||
"description": "Removes the `static` flag from dynamic queries.",
|
||||
"factory": "./migrations/dynamic-queries/index"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
)
|
|
@ -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;
|
||||
}
|
||||
```
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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'));
|
||||
}
|
|
@ -6,6 +6,7 @@ ts_library(
|
|||
tsconfig = "//packages/core/schematics:tsconfig.json",
|
||||
visibility = ["//packages/core/schematics/test/google3:__pkg__"],
|
||||
deps = [
|
||||
"//packages/core/schematics/migrations/dynamic-queries",
|
||||
"//packages/core/schematics/migrations/missing-injectable",
|
||||
"//packages/core/schematics/migrations/missing-injectable/google3",
|
||||
"//packages/core/schematics/migrations/renderer-to-renderer2",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ ts_library(
|
|||
"//packages/core/schematics:migrations.json",
|
||||
],
|
||||
deps = [
|
||||
"//packages/core/schematics/migrations/dynamic-queries",
|
||||
"//packages/core/schematics/migrations/missing-injectable",
|
||||
"//packages/core/schematics/migrations/move-document",
|
||||
"//packages/core/schematics/migrations/renderer-to-renderer2",
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
* @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 {getSystemPath, normalize, virtualFs} from '@angular-devkit/core';
|
||||
import {TempScopedNodeJsSyncHost} from '@angular-devkit/core/node/testing';
|
||||
import {HostTree} from '@angular-devkit/schematics';
|
||||
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
|
||||
import * as shx from 'shelljs';
|
||||
|
||||
describe('dynamic queries migration', () => {
|
||||
let runner: SchematicTestRunner;
|
||||
let host: TempScopedNodeJsSyncHost;
|
||||
let tree: UnitTestTree;
|
||||
let tmpDirPath: string;
|
||||
let previousWorkingDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
runner = new SchematicTestRunner('test', require.resolve('../migrations.json'));
|
||||
host = new TempScopedNodeJsSyncHost();
|
||||
tree = new UnitTestTree(new HostTree(host));
|
||||
|
||||
writeFile('/tsconfig.json', JSON.stringify({
|
||||
compilerOptions: {
|
||||
lib: ['es2015'],
|
||||
}
|
||||
}));
|
||||
writeFile('/angular.json', JSON.stringify({
|
||||
projects: {t: {architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}
|
||||
}));
|
||||
|
||||
previousWorkingDir = shx.pwd();
|
||||
tmpDirPath = getSystemPath(host.root);
|
||||
|
||||
// Switch into the temporary directory path. This allows us to run
|
||||
// the schematic against our custom unit test tree.
|
||||
shx.cd(tmpDirPath);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
shx.cd(previousWorkingDir);
|
||||
shx.rm('-r', tmpDirPath);
|
||||
});
|
||||
|
||||
it('should remove the options object from a dynamic ViewChild query that only has one property',
|
||||
async() => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ViewChild } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyDirective {
|
||||
@ViewChild('child', { static: false }) child: any;
|
||||
}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
expect(tree.readContent('/index.ts')).toContain(`@ViewChild('child') child: any;`);
|
||||
});
|
||||
|
||||
it('should remove the options object from a dynamic ContentChild query that only has one property',
|
||||
async() => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ContentChild } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyComponent {
|
||||
@ContentChild('child', { static: false }) child: any;
|
||||
}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
expect(tree.readContent('/index.ts')).toContain(`@ContentChild('child') child: any;`);
|
||||
});
|
||||
|
||||
it('should only remove the `static` flag from a ViewChild query if it has more than one property',
|
||||
async() => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ViewChild, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyDirective {
|
||||
@ViewChild('child', { read: ElementRef, static: false }) child: ElementRef;
|
||||
}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
expect(tree.readContent('/index.ts'))
|
||||
.toContain(`@ViewChild('child', { read: ElementRef }) child: ElementRef;`);
|
||||
});
|
||||
|
||||
it('should only remove the `static` flag from a ContentChild query if it has more than one property',
|
||||
async() => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ContentChild, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyDirective {
|
||||
@ContentChild('child', { static: false, read: ElementRef }) child: ElementRef;
|
||||
}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
expect(tree.readContent('/index.ts'))
|
||||
.toContain(`@ContentChild('child', { read: ElementRef }) child: ElementRef;`);
|
||||
});
|
||||
|
||||
it('should not change static ViewChild queries', async() => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ViewChild, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyDirective {
|
||||
@ViewChild('child', { read: ElementRef, static: true }) child: ElementRef;
|
||||
}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
expect(tree.readContent('/index.ts'))
|
||||
.toContain(`@ViewChild('child', { read: ElementRef, static: true }) child: ElementRef;`);
|
||||
});
|
||||
|
||||
it('should not change static ContentChild queries', async() => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ContentChild, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyDirective {
|
||||
@ContentChild('child', { static: true, read: ElementRef }) child: ElementRef;
|
||||
}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
expect(tree.readContent('/index.ts'))
|
||||
.toContain(`@ContentChild('child', { static: true, read: ElementRef }) child: ElementRef;`);
|
||||
});
|
||||
|
||||
it('should migrate dynamic queries on a setter', async() => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ContentChild, ViewChild } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyDirective {
|
||||
@ContentChild('child', { static: false }) set child(c: any) {}
|
||||
@ViewChild('otherChild', { static: false }) set otherChild(c: any) {}
|
||||
}
|
||||
`);
|
||||
|
||||
await runMigration();
|
||||
const content = tree.readContent('/index.ts');
|
||||
expect(content).toContain(`@ContentChild('child') set child(c: any) {}`);
|
||||
expect(content).toContain(`@ViewChild('otherChild') set otherChild(c: any) {}`);
|
||||
});
|
||||
|
||||
function writeFile(filePath: string, contents: string) {
|
||||
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
|
||||
}
|
||||
|
||||
function runMigration() {
|
||||
runner.runSchematicAsync('migration-v9-dynamic-queries', {}, tree).toPromise();
|
||||
}
|
||||
});
|
|
@ -0,0 +1,177 @@
|
|||
/**
|
||||
* @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 {readFileSync, writeFileSync} from 'fs';
|
||||
import {dirname, join} from 'path';
|
||||
import * as shx from 'shelljs';
|
||||
import {Configuration, Linter} from 'tslint';
|
||||
|
||||
describe('Google3 dynamic queries TSLint rule', () => {
|
||||
const rulesDirectory = dirname(require.resolve('../../migrations/google3/dynamicQueriesRule'));
|
||||
|
||||
let tmpDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = join(process.env['TEST_TMPDIR'] !, 'google3-test');
|
||||
shx.mkdir('-p', tmpDir);
|
||||
|
||||
writeFile('tsconfig.json', JSON.stringify({compilerOptions: {module: 'es2015'}}));
|
||||
});
|
||||
|
||||
afterEach(() => shx.rm('-r', tmpDir));
|
||||
|
||||
function runTSLint(fix = true) {
|
||||
const program = Linter.createProgram(join(tmpDir, 'tsconfig.json'));
|
||||
const linter = new Linter({fix, rulesDirectory: [rulesDirectory]}, program);
|
||||
const config = Configuration.parseConfigFile(
|
||||
{rules: {'dynamic-queries': true}, linterOptions: {typeCheck: true}});
|
||||
|
||||
program.getRootFileNames().forEach(fileName => {
|
||||
linter.lint(fileName, program.getSourceFile(fileName) !.getFullText(), config);
|
||||
});
|
||||
|
||||
return linter;
|
||||
}
|
||||
|
||||
function writeFile(fileName: string, content: string) {
|
||||
writeFileSync(join(tmpDir, fileName), content);
|
||||
}
|
||||
|
||||
function getFile(fileName: string) { return readFileSync(join(tmpDir, fileName), 'utf8'); }
|
||||
|
||||
it('should flag dynamic queries', () => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ViewChild, ContentChild } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyDirective {
|
||||
@ViewChild('child', { static: false }) child: any;
|
||||
@ContentChild('otherChild', { static: false }) otherChild: any;
|
||||
}
|
||||
`);
|
||||
|
||||
const linter = runTSLint(false);
|
||||
const failures = linter.getResult().failures;
|
||||
|
||||
expect(failures.length).toBe(2);
|
||||
expect(failures[0].getFailure())
|
||||
.toMatch('The static flag defaults to false, so setting it false manually is unnecessary.');
|
||||
expect(failures[1].getFailure())
|
||||
.toMatch('The static flag defaults to false, so setting it false manually is unnecessary.');
|
||||
});
|
||||
|
||||
it('should remove the options object from a dynamic ViewChild query that only has one property',
|
||||
() => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ViewChild } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyDirective {
|
||||
@ViewChild('child', { static: false }) child: any;
|
||||
}
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
expect(getFile('/index.ts')).toContain(`@ViewChild('child') child: any;`);
|
||||
});
|
||||
|
||||
it('should remove the options object from a dynamic ContentChild query that only has one property',
|
||||
() => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ContentChild } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyComponent {
|
||||
@ContentChild('child', { static: false }) child: any;
|
||||
}
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
expect(getFile('/index.ts')).toContain(`@ContentChild('child') child: any;`);
|
||||
});
|
||||
|
||||
it('should only remove the `static` flag from a ViewChild query if it has more than one property',
|
||||
() => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ViewChild, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyDirective {
|
||||
@ViewChild('child', { read: ElementRef, static: false }) child: ElementRef;
|
||||
}
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
expect(getFile('/index.ts'))
|
||||
.toContain(`@ViewChild('child', { read: ElementRef }) child: ElementRef;`);
|
||||
});
|
||||
|
||||
it('should only remove the `static` flag from a ContentChild query if it has more than one property',
|
||||
() => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ContentChild, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyDirective {
|
||||
@ContentChild('child', { static: false, read: ElementRef }) child: ElementRef;
|
||||
}
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
expect(getFile('/index.ts'))
|
||||
.toContain(`@ContentChild('child', { read: ElementRef }) child: ElementRef;`);
|
||||
});
|
||||
|
||||
it('should not change static ViewChild queries', () => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ViewChild, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyDirective {
|
||||
@ViewChild('child', { read: ElementRef, static: true }) child: ElementRef;
|
||||
}
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
expect(getFile('/index.ts'))
|
||||
.toContain(`@ViewChild('child', { read: ElementRef, static: true }) child: ElementRef;`);
|
||||
});
|
||||
|
||||
it('should not change static ContentChild queries', () => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ContentChild, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyDirective {
|
||||
@ContentChild('child', { static: true, read: ElementRef }) child: ElementRef;
|
||||
}
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
expect(getFile('/index.ts'))
|
||||
.toContain(`@ContentChild('child', { static: true, read: ElementRef }) child: ElementRef;`);
|
||||
});
|
||||
|
||||
it('should migrate dynamic queries on a setter', () => {
|
||||
writeFile('/index.ts', `
|
||||
import { Directive, ContentChild, ViewChild } from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class MyDirective {
|
||||
@ContentChild('child', { static: false }) set child(c: any) {}
|
||||
@ViewChild('otherChild', { static: false }) set otherChild(c: any) {}
|
||||
}
|
||||
`);
|
||||
|
||||
runTSLint(true);
|
||||
const content = getFile('/index.ts');
|
||||
expect(content).toContain(`@ContentChild('child') set child(c: any) {}`);
|
||||
expect(content).toContain(`@ViewChild('otherChild') set otherChild(c: any) {}`);
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue