refactor(core): allow developers to select static-query migration strategy (#29876)

Currently there are two available migration strategies for the `static-query`
schematic. Both have benefits and negatives which depend on what the
developer prefers. Since we can't decide which migration strategy is the
best for a given project, the developer should be able to select a specific
strategy through a simple choice prompt.

In order to be able to use prompts in a migration schematic, we need to
take advantage of the "inquirer" package which is also used by the CLI
schematic prompts (schematic prompts are usually only statically defined
in the schema). Additionally the schematic needs to be made "async"
because with prompts the schematic can no longer execute synchronously
without implementing some logic that blocks the execution.

PR Close #29876
This commit is contained in:
Paul Gschwendtner 2019-04-13 00:14:33 +02:00 committed by Ben Lesh
parent 2ba799ddc7
commit ca591641c7
7 changed files with 371 additions and 172 deletions

View File

@ -49,6 +49,7 @@
"@types/diff": "^3.5.1",
"@types/fs-extra": "4.0.2",
"@types/hammerjs": "2.0.35",
"@types/inquirer": "^0.0.44",
"@types/jasmine": "^2.8.8",
"@types/jasminewd2": "^2.0.6",
"@types/minimist": "^1.2.0",

View File

@ -15,6 +15,7 @@ ts_library(
"//packages/core/schematics/utils",
"@npm//@angular-devkit/schematics",
"@npm//@types/node",
"@npm//rxjs",
"@npm//typescript",
],
)

View File

@ -6,13 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
import {logging} from '@angular-devkit/core';
import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics';
import {dirname, relative} from 'path';
import {from} from 'rxjs';
import * as ts from 'typescript';
import {NgComponentTemplateVisitor} from '../../utils/ng_component_template';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {getInquirer, supportsPrompt} from '../../utils/schematics_prompt';
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
import {TypeScriptVisitor, visitAllNodes} from '../../utils/typescript/visit_nodes';
@ -22,34 +23,83 @@ import {TimingStrategy} from './strategies/timing-strategy';
import {QueryUsageStrategy} from './strategies/usage_strategy/usage_strategy';
import {getTransformedQueryCallExpr} from './transform';
type Logger = logging.LoggerApi;
/** Entry point for the V8 static-query migration. */
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 migrate queries ' +
'to explicit timing.');
}
for (const tsconfigPath of projectTsConfigPaths) {
runStaticQueryMigration(tree, tsconfigPath, basePath, context.logger);
}
// We need to cast the returned "Observable" to "any" as there is a
// RxJS version mismatch that breaks the TS compilation.
return from(runMigration(tree, context).then(() => tree)) as any;
};
}
/** Runs the V8 migration static-query migration for all determined TypeScript projects. */
async function runMigration(tree: Tree, context: SchematicContext) {
const projectTsConfigPaths = getProjectTsConfigPaths(tree);
const basePath = process.cwd();
const logger = context.logger;
logger.info('------ Static Query migration ------');
logger.info('In preparation for Ivy, developers can now explicitly specify the');
logger.info('timing of their queries. Read more about this here:');
logger.info('https://github.com/angular/angular/pull/28810');
logger.info('');
if (!projectTsConfigPaths.length) {
throw new SchematicsException(
'Could not find any tsconfig file. Cannot migrate queries ' +
'to explicit timing.');
}
// In case prompts are supported, determine the desired migration strategy
// by creating a choice prompt. By default the template strategy is used.
let isUsageStrategy = false;
if (supportsPrompt()) {
logger.info('There are two available migration strategies that can be selected:');
logger.info(' • Template strategy - migration tool (short-term gains, rare corrections)');
logger.info(' • Usage strategy - best practices (long-term gains, manual corrections)');
logger.info('For an easy migration, the template strategy is recommended. The usage');
logger.info('strategy can be used for best practices and a code base that will be more');
logger.info('flexible to changes going forward.');
const {strategyName} = await getInquirer().prompt<{strategyName: string}>({
type: 'list',
name: 'strategyName',
message: 'What migration strategy do you want to use?',
choices: [
{name: 'Template strategy', value: 'template'}, {name: 'Usage strategy', value: 'usage'}
],
default: 'template',
});
logger.info('');
isUsageStrategy = strategyName === 'usage';
} else {
// In case prompts are not supported, we still want to allow developers to opt
// into the usage strategy by specifying an environment variable. The tests also
// use the environment variable as there is no headless way to select via prompt.
isUsageStrategy = !!process.env['NG_STATIC_QUERY_USAGE_STRATEGY'];
}
const failures = [];
for (const tsconfigPath of projectTsConfigPaths) {
failures.push(...await runStaticQueryMigration(tree, tsconfigPath, basePath, isUsageStrategy));
}
if (failures.length) {
logger.info('Some queries cannot be migrated automatically. Please go through');
logger.info('those manually and apply the appropriate timing:');
failures.forEach(failure => logger.warn(`${failure}`));
}
logger.info('------------------------------------------------');
}
/**
* Runs the static query migration for the given TypeScript project. The schematic
* analyzes all queries within the project and sets up the query timing based on
* the current usage of the query property. e.g. a view query that is not used in any
* lifecycle hook does not need to be static and can be set up with "static: false".
*/
function runStaticQueryMigration(
tree: Tree, tsconfigPath: string, basePath: string, logger: Logger) {
async function runStaticQueryMigration(
tree: Tree, tsconfigPath: string, basePath: string, isUsageStrategy: boolean) {
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
const host = ts.createCompilerHost(parsed.options, true);
@ -62,7 +112,6 @@ function runStaticQueryMigration(
return buffer ? buffer.toString() : undefined;
};
const isUsageStrategy = !!process.env['NG_STATIC_QUERY_USAGE_STRATEGY'];
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
const typeChecker = program.getTypeChecker();
const queryVisitor = new NgQueryResolveVisitor(typeChecker);
@ -100,13 +149,13 @@ function runStaticQueryMigration(
const strategy: TimingStrategy = isUsageStrategy ?
new QueryUsageStrategy(classMetadata, typeChecker) :
new QueryTemplateStrategy(tsconfigPath, classMetadata, host);
const detectionMessages: string[] = [];
const failureMessages: string[] = [];
// In case the strategy could not be set up properly, we just exit the
// migration. We don't want to throw an exception as this could mean
// that other migrations are interrupted.
if (!strategy.setup()) {
return;
return [];
}
// Walk through all source files that contain resolved queries and update
@ -135,23 +184,15 @@ function runStaticQueryMigration(
update.remove(queryExpr.getStart(), queryExpr.getWidth());
update.insertRight(queryExpr.getStart(), newText);
const {line, character} =
ts.getLineAndCharacterOfPosition(sourceFile, q.decorator.node.getStart());
detectionMessages.push(`${relativePath}@${line + 1}:${character + 1}: ${message}`);
if (message) {
const {line, character} =
ts.getLineAndCharacterOfPosition(sourceFile, q.decorator.node.getStart());
failureMessages.push(`${relativePath}@${line + 1}:${character + 1}: ${message}`);
}
});
tree.commitUpdate(update);
});
if (detectionMessages.length) {
logger.info('------ Static Query migration ------');
logger.info('In preparation for Ivy, developers can now explicitly specify the');
logger.info('timing of their queries. Read more about this here:');
logger.info('https://github.com/angular/angular/pull/28810');
logger.info('');
logger.info('Some queries cannot be migrated automatically. Please go through');
logger.info('those manually and apply the appropriate timing:');
detectionMessages.forEach(failure => logger.warn(`${failure}`));
logger.info('------------------------------------------------');
}
return failureMessages;
}

View File

@ -93,11 +93,13 @@ describe('static-queries migration with template strategy', () => {
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
}
function runMigration() { runner.runSchematic('migration-v8-static-queries', {}, tree); }
async function runMigration() {
await runner.runSchematicAsync('migration-v8-static-queries', {}, tree).toPromise();
}
describe('ViewChild', () => {
it('should detect queries selecting elements through template reference', () => {
it('should detect queries selecting elements through template reference', async() => {
writeFile('/index.ts', `
import {Component, NgModule, ViewChild} from '@angular/core';
@ -118,7 +120,7 @@ describe('static-queries migration with template strategy', () => {
export class MyModule {}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ViewChild('myButton', { static: false }) query: any;`);
@ -126,7 +128,7 @@ describe('static-queries migration with template strategy', () => {
.toContain(`@ViewChild('myStaticButton', { static: true }) query2: any;`);
});
it('should detect queries selecting ng-template as static', () => {
it('should detect queries selecting ng-template as static', async() => {
writeFile('/index.ts', `
import {Component, NgModule, ViewChild} from '@angular/core';
@ -143,13 +145,13 @@ describe('static-queries migration with template strategy', () => {
export class MyModule {}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ViewChild('myTmpl', { static: true }) query: any;`);
});
it('should detect queries selecting component view providers through string token', () => {
it('should detect queries selecting component view providers through string token', async() => {
writeFile('/index.ts', `
import {Component, Directive, NgModule, ViewChild} from '@angular/core';
@ -186,7 +188,7 @@ describe('static-queries migration with template strategy', () => {
</ng-template>
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ViewChild('my-token', { static: true }) query: any;`);
@ -194,7 +196,7 @@ describe('static-queries migration with template strategy', () => {
.toContain(`@ViewChild('my-token-2', { static: false }) query2: any;`);
});
it('should detect queries selecting component view providers using class token', () => {
it('should detect queries selecting component view providers using class token', async() => {
writeFile('/index.ts', `
import {Component, Directive, NgModule, ViewChild} from '@angular/core';
@ -230,7 +232,7 @@ describe('static-queries migration with template strategy', () => {
</ng-template>
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ViewChild(MyService, { static: true }) query: any;`);
@ -238,7 +240,7 @@ describe('static-queries migration with template strategy', () => {
.toContain(`@ViewChild(MyService2, { static: false }) query2: any;`);
});
it('should detect queries selecting component', () => {
it('should detect queries selecting component', async() => {
writeFile('/index.ts', `
import {Component, NgModule, ViewChild} from '@angular/core';
import {HomeComponent, HomeComponent2} from './home-comp';
@ -276,7 +278,7 @@ describe('static-queries migration with template strategy', () => {
export class HomeComponent2 {}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ViewChild(HomeComponent, { static: true }) query: any;`);
@ -284,7 +286,7 @@ describe('static-queries migration with template strategy', () => {
.toContain(`@ViewChild(HomeComponent2, { static: false }) query2: any;`);
});
it('should detect queries selecting third-party component', () => {
it('should detect queries selecting third-party component', async() => {
writeFakeLibrary();
writeFile('/index.ts', `
import {Component, NgModule, ViewChild} from '@angular/core';
@ -303,13 +305,13 @@ describe('static-queries migration with template strategy', () => {
<my-lib-selector>My projected content</my-lib-selector>
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ViewChild(MyLibComponent, { static: true }) query: any;`);
});
it('should detect queries selecting third-party component with multiple selectors', () => {
it('should detect queries selecting third-party component with multiple selectors', async() => {
writeFakeLibrary('a-selector, test-selector');
writeFile('/index.ts', `
import {Component, NgModule, ViewChild} from '@angular/core';
@ -331,13 +333,13 @@ describe('static-queries migration with template strategy', () => {
</ng-template>
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ViewChild(MyLibComponent, { static: false }) query: any;`);
});
it('should detect queries within structural directive', () => {
it('should detect queries within structural directive', async() => {
writeFile('/index.ts', `
import {Component, Directive, NgModule, ViewChild} from '@angular/core';
@ -359,7 +361,7 @@ describe('static-queries migration with template strategy', () => {
<span *ngIf #myRef2>With asterisk</span>
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ViewChild('myRef', { static: true }) query: any;`);
@ -367,7 +369,7 @@ describe('static-queries migration with template strategy', () => {
.toContain(`@ViewChild('myRef2', { static: false }) query2: any;`);
});
it('should detect inherited queries', () => {
it('should detect inherited queries', async() => {
writeFile('/index.ts', `
import {Component, NgModule, ViewChild} from '@angular/core';
@ -386,13 +388,13 @@ describe('static-queries migration with template strategy', () => {
<span #myRef>My Ref</span>
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ViewChild('myRef', { static: true }) query: any;`);
});
it('should add a todo if a query is not declared in any component', () => {
it('should add a todo if a query is not declared in any component', async() => {
writeFile('/index.ts', `
import {Component, NgModule, ViewChild, SomeToken} from '@angular/core';
@ -401,7 +403,7 @@ describe('static-queries migration with template strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(
@ -412,7 +414,7 @@ describe('static-queries migration with template strategy', () => {
/^⮑ {3}index.ts@5:11:.+could not be determined.+not declared in any component/);
});
it('should add a todo if a query is used multiple times with different timing', () => {
it('should add a todo if a query is used multiple times with different timing', async() => {
writeFile('/index.ts', `
import {Component, NgModule, ViewChild} from '@angular/core';
@ -430,7 +432,7 @@ describe('static-queries migration with template strategy', () => {
export class MyModule {}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ViewChild('myRef', /* TODO: add static flag */ {}) query: any;`);
@ -440,7 +442,7 @@ describe('static-queries migration with template strategy', () => {
/^⮑ {3}index.ts@5:11: Multiple components use the query with different timings./);
});
it('should gracefully exit migration if queries could not be analyzed', () => {
it('should gracefully exit migration if queries could not be analyzed', async() => {
writeFile('/index.ts', `
import {Component, ViewChild} from '@angular/core';
@ -456,7 +458,7 @@ describe('static-queries migration with template strategy', () => {
// We don't expect an error to be thrown as this could interrupt other
// migrations which are scheduled with "ng update" in the CLI.
expect(() => runMigration()).not.toThrow();
await runMigration();
expect(console.error)
.toHaveBeenCalledWith('Could not create Angular AOT compiler to determine query timing.');
@ -465,7 +467,7 @@ describe('static-queries migration with template strategy', () => {
jasmine.stringMatching(/Cannot determine the module for class MyComp/));
});
it('should add a todo for content queries which are not detectable', () => {
it('should add a todo for content queries which are not detectable', async() => {
writeFile('/index.ts', `
import {Component, NgModule, ContentChild} from '@angular/core';
@ -478,7 +480,7 @@ describe('static-queries migration with template strategy', () => {
export class MyModule {}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ContentChild('myRef', /* TODO: add static flag */ {}) query: any;`);

View File

@ -52,7 +52,7 @@ describe('static-queries migration with usage strategy', () => {
describe('ViewChild', () => {
createQueryTests('ViewChild');
it('should mark view queries used in "ngAfterContentInit" as static', () => {
it('should mark view queries used in "ngAfterContentInit" as static', async() => {
writeFile('/index.ts', `
import {Component, ViewChild} from '@angular/core';
@ -66,13 +66,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ViewChild('test', { static: true }) query: any;`);
});
it('should mark view queries used in "ngAfterContentChecked" as static', () => {
it('should mark view queries used in "ngAfterContentChecked" as static', async() => {
writeFile('/index.ts', `
import {Component, ViewChild} from '@angular/core';
@ -86,7 +86,7 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ViewChild('test', { static: true }) query: any;`);
@ -96,7 +96,7 @@ describe('static-queries migration with usage strategy', () => {
describe('ContentChild', () => {
createQueryTests('ContentChild');
it('should not mark content queries used in "ngAfterContentInit" as static', () => {
it('should not mark content queries used in "ngAfterContentInit" as static', async() => {
writeFile('/index.ts', `
import {Component, ContentChild} from '@angular/core';
@ -110,13 +110,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ContentChild('test', { static: false }) query: any;`);
});
it('should not mark content queries used in "ngAfterContentChecked" as static', () => {
it('should not mark content queries used in "ngAfterContentChecked" as static', async() => {
writeFile('/index.ts', `
import {Component, ContentChild} from '@angular/core';
@ -130,7 +130,7 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@ContentChild('test', { static: false }) query: any;`);
@ -141,10 +141,12 @@ describe('static-queries migration with usage strategy', () => {
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
}
function runMigration() { runner.runSchematic('migration-v8-static-queries', {}, tree); }
async function runMigration() {
await runner.runSchematicAsync('migration-v8-static-queries', {}, tree).toPromise();
}
function createQueryTests(queryType: 'ViewChild' | 'ContentChild') {
it('should mark queries as dynamic', () => {
it('should mark queries as dynamic', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -159,7 +161,7 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: false }) unused: any;`);
@ -167,7 +169,7 @@ describe('static-queries migration with usage strategy', () => {
.toContain(`@${queryType}('dynamic', { static: false }) dynamic: any`);
});
it('should mark queries used in "ngOnChanges" as static', () => {
it('should mark queries used in "ngOnChanges" as static', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -181,13 +183,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should mark queries used in "ngOnInit" as static', () => {
it('should mark queries used in "ngOnInit" as static', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -201,13 +203,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should mark queries used in "ngDoCheck" as static', () => {
it('should mark queries used in "ngDoCheck" as static', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -221,13 +223,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should keep existing query options when updating timing', () => {
it('should keep existing query options when updating timing', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -241,13 +243,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { /* test */ read: null, static: true }) query: any;`);
});
it('should not overwrite existing explicit query timing', () => {
it('should not overwrite existing explicit query timing', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -257,13 +259,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', {static: /* untouched */ someVal}) query: any;`);
});
it('should detect queries used in deep method chain', () => {
it('should detect queries used in deep method chain', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -292,13 +294,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should properly exit if recursive function is analyzed', () => {
it('should properly exit if recursive function is analyzed', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -316,13 +318,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: false }) query: any;`);
});
it('should detect queries used in newly instantiated classes', () => {
it('should detect queries used in newly instantiated classes', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -353,7 +355,7 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
@ -361,7 +363,7 @@ describe('static-queries migration with usage strategy', () => {
.toContain(`@${queryType}('test', { static: true }) query2: any;`);
});
it('should detect queries used in parenthesized new expressions', () => {
it('should detect queries used in parenthesized new expressions', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -381,13 +383,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect queries in lifecycle hook with string literal name', () => {
it('should detect queries in lifecycle hook with string literal name', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -401,13 +403,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect static queries within nested inheritance', () => {
it('should detect static queries within nested inheritance', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -426,13 +428,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect static queries used within input setters', () => {
it('should detect static queries used within input setters', async() => {
writeFile('/index.ts', `
import {Component, Input, ${queryType}} from '@angular/core';
@ -448,13 +450,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect inputs defined in metadata', () => {
it('should detect inputs defined in metadata', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -474,13 +476,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect aliased inputs declared in metadata', () => {
it('should detect aliased inputs declared in metadata', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -497,13 +499,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should not mark query as static if query is used in non-input setter', () => {
it('should not mark query as static if query is used in non-input setter', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -517,13 +519,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: false }) query: any;`);
});
it('should detect input decorator on setter', () => {
it('should detect input decorator on setter', async() => {
writeFile('/index.ts', `
import {Input, Component, ${queryType}} from '@angular/core';
@ -542,13 +544,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect setter inputs in derived classes', () => {
it('should detect setter inputs in derived classes', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -567,13 +569,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should properly detect static query in external derived class', () => {
it('should properly detect static query in external derived class', async() => {
writeFile('/src/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -597,13 +599,13 @@ describe('static-queries migration with usage strategy', () => {
// recorded for TypeScript projects not at the schematic tree root.
host.sync.rename(normalize('/tsconfig.json'), normalize('/src/tsconfig.json'));
runMigration();
await runMigration();
expect(tree.readContent('/src/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should not mark queries used in promises as static', () => {
it('should not mark queries used in promises as static', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -637,7 +639,7 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: false }) query: any;`);
@ -645,7 +647,7 @@ describe('static-queries migration with usage strategy', () => {
.toContain(`@${queryType}('test', { static: true }) query2: any;`);
});
it('should handle function callbacks which statically access queries', () => {
it('should handle function callbacks which statically access queries', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -667,14 +669,15 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should handle class instantiations with specified callbacks that access queries', () => {
writeFile('/index.ts', `
it('should handle class instantiations with specified callbacks that access queries',
async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
import {External} from './external';
@ -688,7 +691,7 @@ describe('static-queries migration with usage strategy', () => {
}
`);
writeFile('/external.ts', `
writeFile('/external.ts', `
export class External {
constructor(cb: () => void) {
// Add extra parentheses to ensure that expression is unwrapped.
@ -697,13 +700,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should handle nested functions with arguments from parent closure', () => {
it('should handle nested functions with arguments from parent closure', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -726,13 +729,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should not mark queries used in setTimeout as static', () => {
it('should not mark queries used in setTimeout as static', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -757,7 +760,7 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: false }) query: any;`);
@ -767,7 +770,7 @@ describe('static-queries migration with usage strategy', () => {
.toContain(`@${queryType}('test', { static: false }) query3: any;`);
});
it('should not mark queries used in "addEventListener" as static', () => {
it('should not mark queries used in "addEventListener" as static', async() => {
writeFile('/index.ts', `
import {Component, ElementRef, ${queryType}} from '@angular/core';
@ -785,13 +788,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: false }) query: any;`);
});
it('should not mark queries used in "requestAnimationFrame" as static', () => {
it('should not mark queries used in "requestAnimationFrame" as static', async() => {
writeFile('/index.ts', `
import {Component, ElementRef, ${queryType}} from '@angular/core';
@ -809,13 +812,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: false }) query: any;`);
});
it('should mark queries used in immediately-invoked function expression as static', () => {
it('should mark queries used in immediately-invoked function expression as static', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -836,7 +839,7 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
@ -844,7 +847,7 @@ describe('static-queries migration with usage strategy', () => {
.toContain(`@${queryType}('test', { static: true }) query2: any;`);
});
it('should detect static queries used in external function-like declaration', () => {
it('should detect static queries used in external function-like declaration', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
import {externalFn} from './external';
@ -867,13 +870,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect static queries used through getter property access', () => {
it('should detect static queries used through getter property access', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -891,13 +894,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect static queries used through external getter access', () => {
it('should detect static queries used through external getter access', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
import {External} from './external';
@ -929,13 +932,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should not mark queries as static if a value is assigned to accessor property', () => {
it('should not mark queries as static if a value is assigned to accessor property', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -954,13 +957,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: false }) query: any;`);
});
it('should mark queries as static if non-input setter uses query', () => {
it('should mark queries as static if non-input setter uses query', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -979,13 +982,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should check setter and getter when using compound assignment', () => {
it('should check setter and getter when using compound assignment', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -1005,7 +1008,7 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
@ -1013,7 +1016,7 @@ describe('static-queries migration with usage strategy', () => {
.toContain(`@${queryType}('test', { static: true }) query2: any;`);
});
it('should check getters when using comparison operator in binary expression', () => {
it('should check getters when using comparison operator in binary expression', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -1032,13 +1035,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should check derived abstract class methods', () => {
it('should check derived abstract class methods', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -1068,13 +1071,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect queries accessed through deep abstract class method', () => {
it('should detect queries accessed through deep abstract class method', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -1100,13 +1103,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect queries accessed through abstract property getter', () => {
it('should detect queries accessed through abstract property getter', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -1126,13 +1129,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect queries accessed through abstract property setter', () => {
it('should detect queries accessed through abstract property setter', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -1153,13 +1156,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect query usage in abstract class methods accessing inherited query', () => {
it('should detect query usage in abstract class methods accessing inherited query', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -1189,13 +1192,13 @@ describe('static-queries migration with usage strategy', () => {
}
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect query usage within component template', () => {
it('should detect query usage within component template', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -1210,13 +1213,13 @@ describe('static-queries migration with usage strategy', () => {
<comp [dir]="query"></comp>
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should detect query usage with nested property read within component template', () => {
it('should detect query usage with nested property read within component template', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -1231,14 +1234,15 @@ describe('static-queries migration with usage strategy', () => {
<comp [dir]="query.someProperty"></comp>
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should not mark query as static if template has template reference with same name', () => {
writeFile('/index.ts', `
it('should not mark query as static if template has template reference with same name',
async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@Component({templateUrl: 'my-template.html'})
@ -1247,21 +1251,21 @@ describe('static-queries migration with usage strategy', () => {
}
`);
writeFile(`/my-template.html`, `
writeFile(`/my-template.html`, `
<foo #test></foo>
<same-name #query></same-name>
<!-- In that case the "query" from the component cannot be referenced. -->
<comp [dir]="query"></comp>
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: false }) query: any;`);
});
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: false }) query: any;`);
});
it('should not mark query as static if template has property read with query name but different receiver',
() => {
async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -1280,13 +1284,13 @@ describe('static-queries migration with usage strategy', () => {
<comp [dir]="myObject.someProp"></comp>
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: false }) someProp: any;`);
});
it('should ignore queries accessed within <ng-template> element', () => {
it('should ignore queries accessed within <ng-template> element', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -1304,13 +1308,13 @@ describe('static-queries migration with usage strategy', () => {
</ng-template>
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: false }) query: any;`);
});
it('should detect inherited queries used in templates', () => {
it('should detect inherited queries used in templates', async() => {
writeFile('/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -1327,13 +1331,13 @@ describe('static-queries migration with usage strategy', () => {
<my-comp [myInput]="query"></my-comp>
`);
runMigration();
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain(`@${queryType}('test', { static: true }) query: any;`);
});
it('should properly handle multiple tsconfig files', () => {
it('should properly handle multiple tsconfig files', async() => {
writeFile('/src/index.ts', `
import {Component, ${queryType}} from '@angular/core';
@ -1352,7 +1356,7 @@ describe('static-queries migration with usage strategy', () => {
// The migration runs for "/tsconfig.json" and "/src/tsconfig.json" which both
// contain the "src/index.ts" file. This test ensures that we don't incorrectly
// apply the code transformation multiple times with outdated offsets.
runMigration();
await runMigration();
expect(tree.readContent('/src/index.ts'))
.toContain(`@${queryType}('test', { static: false }) query: any;`);

View File

@ -0,0 +1,34 @@
/**
* @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
*/
type Inquirer = typeof import('inquirer');
let resolvedInquirerModule: Inquirer|null;
try {
// "inquirer" is the prompt module also used by the devkit schematics CLI
// in order to show prompts for schematics. We transitively depend on this
// module, but don't want to throw an exception if the module is not
// installed for some reason. In that case prompts are just not supported.
resolvedInquirerModule = require('inquirer');
} catch (e) {
resolvedInquirerModule = null;
}
/** Whether prompts are currently supported. */
export function supportsPrompt(): boolean {
return !!resolvedInquirerModule && !!process.stdin.isTTY;
}
/**
* Gets the resolved instance of "inquirer" which can be used to programmatically
* create prompts.
*/
export function getInquirer(): Inquirer {
return resolvedInquirerModule !;
}

116
yarn.lock
View File

@ -455,6 +455,14 @@
resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.35.tgz#7b7c950c7d54593e23bffc8d2b4feba9866a7277"
integrity sha512-4mUIMSZ2U4UOWq1b+iV7XUTE4w+Kr3x+Zb/Qz5ROO6BTZLw2c8/ftjq0aRgluguLs4KRuBnrOy/s389HVn1/zA==
"@types/inquirer@^0.0.44":
version "0.0.44"
resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-0.0.44.tgz#60ce954581cfdf44ad3899ec4cdc5fbe3fef1694"
integrity sha512-ugbhy1yBtCz5iTWYF+AGRS/UcMcWicdyHhxl9VaeFYc3ueg0CCssthQLB3rIcIOeGtfG6WPEvHdLu/IjKYfefg==
dependencies:
"@types/rx" "*"
"@types/through" "*"
"@types/jasmine@*":
version "3.3.5"
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.3.5.tgz#3738ffbf34dffae9ecaac4503d7d969744f0e1d7"
@ -524,6 +532,107 @@
resolved "https://registry.yarnpkg.com/@types/q/-/q-0.0.32.tgz#bd284e57c84f1325da702babfc82a5328190c0c5"
integrity sha1-vShOV8hPEyXacCur/IKlMoGQwMU=
"@types/rx-core-binding@*":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/rx-core-binding/-/rx-core-binding-4.0.4.tgz#d969d32f15a62b89e2862c17b3ee78fe329818d3"
integrity sha512-5pkfxnC4w810LqBPUwP5bg7SFR/USwhMSaAeZQQbEHeBp57pjKXRlXmqpMrLJB4y1oglR/c2502853uN0I+DAQ==
dependencies:
"@types/rx-core" "*"
"@types/rx-core@*":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@types/rx-core/-/rx-core-4.0.3.tgz#0b3354b1238cedbe2b74f6326f139dbc7a591d60"
integrity sha1-CzNUsSOM7b4rdPYybxOdvHpZHWA=
"@types/rx-lite-aggregates@*":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@types/rx-lite-aggregates/-/rx-lite-aggregates-4.0.3.tgz#6efb2b7f3d5f07183a1cb2bd4b1371d7073384c2"
integrity sha512-MAGDAHy8cRatm94FDduhJF+iNS5//jrZ/PIfm+QYw9OCeDgbymFHChM8YVIvN2zArwsRftKgE33QfRWvQk4DPg==
dependencies:
"@types/rx-lite" "*"
"@types/rx-lite-async@*":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/rx-lite-async/-/rx-lite-async-4.0.2.tgz#27fbf0caeff029f41e2d2aae638b05e91ceb600c"
integrity sha512-vTEv5o8l6702ZwfAM5aOeVDfUwBSDOs+ARoGmWAKQ6LOInQ8J4/zjM7ov12fuTpktUKdMQjkeCp07Vd73mPkxw==
dependencies:
"@types/rx-lite" "*"
"@types/rx-lite-backpressure@*":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@types/rx-lite-backpressure/-/rx-lite-backpressure-4.0.3.tgz#05abb19bdf87cc740196c355e5d0b37bb50b5d56"
integrity sha512-Y6aIeQCtNban5XSAF4B8dffhIKu6aAy/TXFlScHzSxh6ivfQBQw6UjxyEJxIOt3IT49YkS+siuayM2H/Q0cmgA==
dependencies:
"@types/rx-lite" "*"
"@types/rx-lite-coincidence@*":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@types/rx-lite-coincidence/-/rx-lite-coincidence-4.0.3.tgz#80bd69acc4054a15cdc1638e2dc8843498cd85c0"
integrity sha512-1VNJqzE9gALUyMGypDXZZXzR0Tt7LC9DdAZQ3Ou/Q0MubNU35agVUNXKGHKpNTba+fr8GdIdkC26bRDqtCQBeQ==
dependencies:
"@types/rx-lite" "*"
"@types/rx-lite-experimental@*":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/rx-lite-experimental/-/rx-lite-experimental-4.0.1.tgz#c532f5cbdf3f2c15da16ded8930d1b2984023cbd"
integrity sha1-xTL1y98/LBXaFt7Ykw0bKYQCPL0=
dependencies:
"@types/rx-lite" "*"
"@types/rx-lite-joinpatterns@*":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/rx-lite-joinpatterns/-/rx-lite-joinpatterns-4.0.1.tgz#f70fe370518a8432f29158cc92ffb56b4e4afc3e"
integrity sha1-9w/jcFGKhDLykVjMkv+1a05K/D4=
dependencies:
"@types/rx-lite" "*"
"@types/rx-lite-testing@*":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/rx-lite-testing/-/rx-lite-testing-4.0.1.tgz#21b19d11f4dfd6ffef5a9d1648e9c8879bfe21e9"
integrity sha1-IbGdEfTf1v/vWp0WSOnIh5v+Iek=
dependencies:
"@types/rx-lite-virtualtime" "*"
"@types/rx-lite-time@*":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@types/rx-lite-time/-/rx-lite-time-4.0.3.tgz#0eda65474570237598f3448b845d2696f2dbb1c4"
integrity sha512-ukO5sPKDRwCGWRZRqPlaAU0SKVxmWwSjiOrLhoQDoWxZWg6vyB9XLEZViKOzIO6LnTIQBlk4UylYV0rnhJLxQw==
dependencies:
"@types/rx-lite" "*"
"@types/rx-lite-virtualtime@*":
version "4.0.3"
resolved "https://registry.yarnpkg.com/@types/rx-lite-virtualtime/-/rx-lite-virtualtime-4.0.3.tgz#4b30cacd0fe2e53af29f04f7438584c7d3959537"
integrity sha512-3uC6sGmjpOKatZSVHI2xB1+dedgml669ZRvqxy+WqmGJDVusOdyxcKfyzjW0P3/GrCiN4nmRkLVMhPwHCc5QLg==
dependencies:
"@types/rx-lite" "*"
"@types/rx-lite@*":
version "4.0.6"
resolved "https://registry.yarnpkg.com/@types/rx-lite/-/rx-lite-4.0.6.tgz#3c02921c4244074234f26b772241bcc20c18c253"
integrity sha512-oYiDrFIcor9zDm0VDUca1UbROiMYBxMLMaM6qzz4ADAfOmA9r1dYEcAFH+2fsPI5BCCjPvV9pWC3X3flbrvs7w==
dependencies:
"@types/rx-core" "*"
"@types/rx-core-binding" "*"
"@types/rx@*":
version "4.1.1"
resolved "https://registry.yarnpkg.com/@types/rx/-/rx-4.1.1.tgz#598fc94a56baed975f194574e0f572fd8e627a48"
integrity sha1-WY/JSla67ZdfGUV04PVy/Y5iekg=
dependencies:
"@types/rx-core" "*"
"@types/rx-core-binding" "*"
"@types/rx-lite" "*"
"@types/rx-lite-aggregates" "*"
"@types/rx-lite-async" "*"
"@types/rx-lite-backpressure" "*"
"@types/rx-lite-coincidence" "*"
"@types/rx-lite-experimental" "*"
"@types/rx-lite-joinpatterns" "*"
"@types/rx-lite-testing" "*"
"@types/rx-lite-time" "*"
"@types/rx-lite-virtualtime" "*"
"@types/selenium-webdriver@3.0.7":
version "3.0.7"
resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.7.tgz#5d3613d1ab3ca08b74d19683a3a7c573129ab18f"
@ -554,6 +663,13 @@
resolved "https://registry.yarnpkg.com/@types/systemjs/-/systemjs-0.19.32.tgz#e9204c4cdbc8e275d645c00e6150e68fc5615a24"
integrity sha1-6SBMTNvI4nXWRcAOYVDmj8VhWiQ=
"@types/through@*":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.29.tgz#72943aac922e179339c651fa34a4428a4d722f93"
integrity sha512-9a7C5VHh+1BKblaYiq+7Tfc+EOmjMdZaD1MYtkQjSoxgB69tBjW98ry6SKsi4zEIWztLOMRuL87A3bdT/Fc/4w==
dependencies:
"@types/node" "*"
"@types/yargs@^11.1.1":
version "11.1.1"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-11.1.1.tgz#2e724257167fd6b615dbe4e54301e65fe597433f"