refactor(core): missing-injectable migration should respect providers of directives and components (#33011)
Currenly the `missing-injectable` migration only migrates providers referenced from `@NgModule` definitions. The schematic currently does not cover the migration for providers referenced in `@Directive` or `@Component` definitions. We need to handle the following keys for directives/components: - `@Directive` -> `providers` - `@Component` -> `providers` and `viewProviders`. This commit ensures that the migration handles providers for these definitions. PR Close #33011
This commit is contained in:
parent
52483bf680
commit
5557dec120
|
@ -9,14 +9,16 @@
|
||||||
import {RuleFailure, Rules} from 'tslint';
|
import {RuleFailure, Rules} from 'tslint';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {NgDefinitionCollector} from '../missing-injectable/definition_collector';
|
||||||
import {TslintUpdateRecorder} from '../missing-injectable/google3/tslint_update_recorder';
|
import {TslintUpdateRecorder} from '../missing-injectable/google3/tslint_update_recorder';
|
||||||
import {NgModuleCollector} from '../missing-injectable/module_collector';
|
|
||||||
import {MissingInjectableTransform} from '../missing-injectable/transform';
|
import {MissingInjectableTransform} from '../missing-injectable/transform';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TSLint rule that flags classes which are declared as providers in NgModules but
|
* TSLint rule that flags classes which are declared as providers in "NgModule",
|
||||||
* aren't decorated with any Angular decorator.
|
* "Directive" or "Component" definitions while not being decorated with any
|
||||||
|
* Angular decorator (e.g. "@Injectable").
|
||||||
*/
|
*/
|
||||||
export class Rule extends Rules.TypedRule {
|
export class Rule extends Rules.TypedRule {
|
||||||
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] {
|
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] {
|
||||||
|
@ -24,25 +26,25 @@ export class Rule extends Rules.TypedRule {
|
||||||
const typeChecker = program.getTypeChecker();
|
const typeChecker = program.getTypeChecker();
|
||||||
const sourceFiles = program.getSourceFiles().filter(
|
const sourceFiles = program.getSourceFiles().filter(
|
||||||
s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s));
|
s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s));
|
||||||
const moduleCollector = new NgModuleCollector(typeChecker);
|
const definitionCollector = new NgDefinitionCollector(typeChecker);
|
||||||
const failures: RuleFailure[] = [];
|
const failures: RuleFailure[] = [];
|
||||||
|
|
||||||
// Analyze source files by detecting all NgModule definitions.
|
// Analyze source files by detecting all "NgModule", "Directive" or
|
||||||
sourceFiles.forEach(sourceFile => moduleCollector.visitNode(sourceFile));
|
// "Component" definitions.
|
||||||
|
sourceFiles.forEach(sourceFile => definitionCollector.visitNode(sourceFile));
|
||||||
|
|
||||||
const {resolvedModules} = moduleCollector;
|
const {resolvedModules, resolvedDirectives} = definitionCollector;
|
||||||
const transformer = new MissingInjectableTransform(typeChecker, getUpdateRecorder);
|
const transformer = new MissingInjectableTransform(typeChecker, getUpdateRecorder);
|
||||||
const updateRecorders = new Map<ts.SourceFile, TslintUpdateRecorder>();
|
const updateRecorders = new Map<ts.SourceFile, TslintUpdateRecorder>();
|
||||||
|
|
||||||
resolvedModules.forEach(module => {
|
[...transformer.migrateModules(resolvedModules),
|
||||||
transformer.migrateModule(module).forEach(({message, node}) => {
|
...transformer.migrateDirectives(resolvedDirectives),
|
||||||
|
].forEach(({message, node}) => {
|
||||||
// Only report failures for the current source file that is visited.
|
// Only report failures for the current source file that is visited.
|
||||||
if (node.getSourceFile() === sourceFile) {
|
if (node.getSourceFile() === sourceFile) {
|
||||||
failures.push(
|
failures.push(new RuleFailure(node.getSourceFile(), node.getStart(), 0, message, ruleName));
|
||||||
new RuleFailure(node.getSourceFile(), node.getStart(), 0, message, ruleName));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Record the changes collected in the import manager and NgModule manager.
|
// Record the changes collected in the import manager and NgModule manager.
|
||||||
transformer.recordChanges();
|
transformer.recordChanges();
|
||||||
|
|
|
@ -18,12 +18,21 @@ export interface ResolvedNgModule {
|
||||||
providersExpr: ts.Expression|null;
|
providersExpr: ts.Expression|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ResolvedDirective {
|
||||||
|
name: string;
|
||||||
|
node: ts.ClassDeclaration;
|
||||||
|
decorator: NgDecorator;
|
||||||
|
providersExpr: ts.Expression|null;
|
||||||
|
viewProvidersExpr: ts.Expression|null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Visitor that walks through specified TypeScript nodes and collects all
|
* Visitor that walks through specified TypeScript nodes and collects all
|
||||||
* found NgModule definitions.
|
* found NgModule, Directive or Component definitions.
|
||||||
*/
|
*/
|
||||||
export class NgModuleCollector {
|
export class NgDefinitionCollector {
|
||||||
resolvedModules: ResolvedNgModule[] = [];
|
resolvedModules: ResolvedNgModule[] = [];
|
||||||
|
resolvedDirectives: ResolvedDirective[] = [];
|
||||||
|
|
||||||
constructor(public typeChecker: ts.TypeChecker) {}
|
constructor(public typeChecker: ts.TypeChecker) {}
|
||||||
|
|
||||||
|
@ -41,13 +50,40 @@ export class NgModuleCollector {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ngDecorators = getAngularDecorators(this.typeChecker, node.decorators);
|
const ngDecorators = getAngularDecorators(this.typeChecker, node.decorators);
|
||||||
|
const directiveDecorator =
|
||||||
|
ngDecorators.find(({name}) => name === 'Component' || name == 'Directive');
|
||||||
const ngModuleDecorator = ngDecorators.find(({name}) => name === 'NgModule');
|
const ngModuleDecorator = ngDecorators.find(({name}) => name === 'NgModule');
|
||||||
|
|
||||||
if (ngModuleDecorator) {
|
if (ngModuleDecorator) {
|
||||||
this._visitNgModuleClass(node, ngModuleDecorator);
|
this._visitNgModuleClass(node, ngModuleDecorator);
|
||||||
|
} else if (directiveDecorator) {
|
||||||
|
this._visitDirectiveClass(node, directiveDecorator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _visitDirectiveClass(node: ts.ClassDeclaration, decorator: NgDecorator) {
|
||||||
|
const decoratorCall = decorator.node.expression;
|
||||||
|
const metadata = decoratorCall.arguments[0];
|
||||||
|
|
||||||
|
if (!metadata || !ts.isObjectLiteralExpression(metadata)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const providersNode = metadata.properties.filter(ts.isPropertyAssignment)
|
||||||
|
.find(p => getPropertyNameText(p.name) === 'providers');
|
||||||
|
|
||||||
|
const viewProvidersNode = metadata.properties.filter(ts.isPropertyAssignment)
|
||||||
|
.find(p => getPropertyNameText(p.name) === 'viewProviders');
|
||||||
|
|
||||||
|
this.resolvedDirectives.push({
|
||||||
|
name: node.name ? node.name.text : 'default',
|
||||||
|
node,
|
||||||
|
decorator,
|
||||||
|
providersExpr: providersNode !== undefined ? providersNode.initializer : null,
|
||||||
|
viewProvidersExpr: viewProvidersNode !== undefined ? viewProvidersNode.initializer : null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _visitNgModuleClass(node: ts.ClassDeclaration, decorator: NgDecorator) {
|
private _visitNgModuleClass(node: ts.ClassDeclaration, decorator: NgDecorator) {
|
||||||
const decoratorCall = decorator.node.expression;
|
const decoratorCall = decorator.node.expression;
|
||||||
const metadata = decoratorCall.arguments[0];
|
const metadata = decoratorCall.arguments[0];
|
|
@ -16,13 +16,13 @@ export class TslintUpdateRecorder implements UpdateRecorder {
|
||||||
|
|
||||||
constructor(private ruleName: string, private sourceFile: ts.SourceFile) {}
|
constructor(private ruleName: string, private sourceFile: ts.SourceFile) {}
|
||||||
|
|
||||||
addClassDecorator(node: ts.ClassDeclaration, decoratorText: string, moduleName: string) {
|
addClassDecorator(node: ts.ClassDeclaration, decoratorText: string, className: string) {
|
||||||
// Adding a decorator should be the last replacement. Replacements/rule failures
|
// Adding a decorator should be the last replacement. Replacements/rule failures
|
||||||
// are handled in reverse and in case a decorator and import are inserted at
|
// are handled in reverse and in case a decorator and import are inserted at
|
||||||
// the start of the file, the class decorator should come after the import.
|
// the start of the file, the class decorator should come after the import.
|
||||||
this.failures.unshift(new RuleFailure(
|
this.failures.unshift(new RuleFailure(
|
||||||
this.sourceFile, node.getStart(), 0, `Class needs to be decorated with ` +
|
this.sourceFile, node.getStart(), 0, `Class needs to be decorated with ` +
|
||||||
`"${decoratorText}" because it has been provided by "${moduleName}".`,
|
`"${decoratorText}" because it has been provided by "${className}".`,
|
||||||
this.ruleName, Replacement.appendText(node.getStart(), `${decoratorText}\n`)));
|
this.ruleName, Replacement.appendText(node.getStart(), `${decoratorText}\n`)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ export class TslintUpdateRecorder implements UpdateRecorder {
|
||||||
`Import needs to be updated to import symbols: "${newNamedBindings}"`, this.ruleName, fix));
|
`Import needs to be updated to import symbols: "${newNamedBindings}"`, this.ruleName, fix));
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceDecorator(decorator: ts.Node, newText: string, moduleName: string): void {
|
replaceDecorator(decorator: ts.Node, newText: string, className: string): void {
|
||||||
const fix = [
|
const fix = [
|
||||||
Replacement.deleteText(decorator.getStart(), decorator.getWidth()),
|
Replacement.deleteText(decorator.getStart(), decorator.getWidth()),
|
||||||
Replacement.appendText(decorator.getStart(), newText),
|
Replacement.appendText(decorator.getStart(), newText),
|
||||||
|
@ -50,7 +50,7 @@ export class TslintUpdateRecorder implements UpdateRecorder {
|
||||||
this.failures.push(new RuleFailure(
|
this.failures.push(new RuleFailure(
|
||||||
this.sourceFile, decorator.getStart(), decorator.getEnd(),
|
this.sourceFile, decorator.getStart(), decorator.getEnd(),
|
||||||
`Decorator needs to be replaced with "${newText}" because it has been provided ` +
|
`Decorator needs to be replaced with "${newText}" because it has been provided ` +
|
||||||
`by "${moduleName}"`,
|
`by "${className}"`,
|
||||||
this.ruleName, fix));
|
this.ruleName, fix));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import * as ts from 'typescript';
|
||||||
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
|
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
|
||||||
import {createMigrationCompilerHost} from '../../utils/typescript/compiler_host';
|
import {createMigrationCompilerHost} from '../../utils/typescript/compiler_host';
|
||||||
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
|
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
|
||||||
import {NgModuleCollector} from './module_collector';
|
import {NgDefinitionCollector} from './definition_collector';
|
||||||
import {MissingInjectableTransform} from './transform';
|
import {MissingInjectableTransform} from './transform';
|
||||||
import {UpdateRecorder} from './update_recorder';
|
import {UpdateRecorder} from './update_recorder';
|
||||||
|
|
||||||
|
@ -53,26 +53,26 @@ function runMissingInjectableMigration(
|
||||||
|
|
||||||
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
|
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
|
||||||
const typeChecker = program.getTypeChecker();
|
const typeChecker = program.getTypeChecker();
|
||||||
const moduleCollector = new NgModuleCollector(typeChecker);
|
const definitionCollector = new NgDefinitionCollector(typeChecker);
|
||||||
const sourceFiles = program.getSourceFiles().filter(
|
const sourceFiles = program.getSourceFiles().filter(
|
||||||
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
|
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
|
||||||
|
|
||||||
// Analyze source files by detecting all modules.
|
// Analyze source files by detecting all modules, directives and components.
|
||||||
sourceFiles.forEach(sourceFile => moduleCollector.visitNode(sourceFile));
|
sourceFiles.forEach(sourceFile => definitionCollector.visitNode(sourceFile));
|
||||||
|
|
||||||
const {resolvedModules} = moduleCollector;
|
const {resolvedModules, resolvedDirectives} = definitionCollector;
|
||||||
const transformer = new MissingInjectableTransform(typeChecker, getUpdateRecorder);
|
const transformer = new MissingInjectableTransform(typeChecker, getUpdateRecorder);
|
||||||
const updateRecorders = new Map<ts.SourceFile, UpdateRecorder>();
|
const updateRecorders = new Map<ts.SourceFile, UpdateRecorder>();
|
||||||
|
|
||||||
resolvedModules.forEach(module => {
|
[...transformer.migrateModules(resolvedModules),
|
||||||
transformer.migrateModule(module).forEach(({message, node}) => {
|
...transformer.migrateDirectives(resolvedDirectives),
|
||||||
|
].forEach(({message, node}) => {
|
||||||
const nodeSourceFile = node.getSourceFile();
|
const nodeSourceFile = node.getSourceFile();
|
||||||
const relativeFilePath = relative(basePath, nodeSourceFile.fileName);
|
const relativeFilePath = relative(basePath, nodeSourceFile.fileName);
|
||||||
const {line, character} =
|
const {line, character} =
|
||||||
ts.getLineAndCharacterOfPosition(node.getSourceFile(), node.getStart());
|
ts.getLineAndCharacterOfPosition(node.getSourceFile(), node.getStart());
|
||||||
failures.push(`${relativeFilePath}@${line + 1}:${character + 1}: ${message}`);
|
failures.push(`${relativeFilePath}@${line + 1}:${character + 1}: ${message}`);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Record the changes collected in the import manager and transformer.
|
// Record the changes collected in the import manager and transformer.
|
||||||
transformer.recordChanges();
|
transformer.recordChanges();
|
||||||
|
|
|
@ -13,10 +13,11 @@ import * as ts from 'typescript';
|
||||||
|
|
||||||
import {getAngularDecorators} from '../../utils/ng_decorators';
|
import {getAngularDecorators} from '../../utils/ng_decorators';
|
||||||
|
|
||||||
|
import {ResolvedDirective, ResolvedNgModule} from './definition_collector';
|
||||||
import {ImportManager} from './import_manager';
|
import {ImportManager} from './import_manager';
|
||||||
import {ResolvedNgModule} from './module_collector';
|
|
||||||
import {UpdateRecorder} from './update_recorder';
|
import {UpdateRecorder} from './update_recorder';
|
||||||
|
|
||||||
|
|
||||||
/** Name of decorators which imply that a given class does not need to be migrated. */
|
/** Name of decorators which imply that a given class does not need to be migrated. */
|
||||||
const NO_MIGRATE_DECORATORS = ['Injectable', 'Directive', 'Component', 'Pipe'];
|
const NO_MIGRATE_DECORATORS = ['Injectable', 'Directive', 'Component', 'Pipe'];
|
||||||
|
|
||||||
|
@ -42,6 +43,24 @@ export class MissingInjectableTransform {
|
||||||
|
|
||||||
recordChanges() { this.importManager.recordChanges(); }
|
recordChanges() { this.importManager.recordChanges(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrates all specified NgModule's by walking through referenced providers
|
||||||
|
* and decorating them with "@Injectable" if needed.
|
||||||
|
*/
|
||||||
|
migrateModules(modules: ResolvedNgModule[]): AnalysisFailure[] {
|
||||||
|
return modules.reduce(
|
||||||
|
(failures, node) => failures.concat(this.migrateModule(node)), [] as AnalysisFailure[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrates all specified directives by walking through referenced providers
|
||||||
|
* and decorating them with "@Injectable" if needed.
|
||||||
|
*/
|
||||||
|
migrateDirectives(directives: ResolvedDirective[]): AnalysisFailure[] {
|
||||||
|
return directives.reduce(
|
||||||
|
(failures, node) => failures.concat(this.migrateDirective(node)), [] as AnalysisFailure[]);
|
||||||
|
}
|
||||||
|
|
||||||
/** Migrates a given NgModule by walking through the referenced providers. */
|
/** Migrates a given NgModule by walking through the referenced providers. */
|
||||||
migrateModule(module: ResolvedNgModule): AnalysisFailure[] {
|
migrateModule(module: ResolvedNgModule): AnalysisFailure[] {
|
||||||
if (module.providersExpr === null) {
|
if (module.providersExpr === null) {
|
||||||
|
@ -60,11 +79,43 @@ export class MissingInjectableTransform {
|
||||||
return this._visitProviderResolvedValue(evaluatedExpr, module);
|
return this._visitProviderResolvedValue(evaluatedExpr, module);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrates a given directive by walking through defined providers. This method
|
||||||
|
* also handles components with "viewProviders" defined.
|
||||||
|
*/
|
||||||
|
migrateDirective(directive: ResolvedDirective): AnalysisFailure[] {
|
||||||
|
const failures: AnalysisFailure[] = [];
|
||||||
|
|
||||||
|
// Migrate "providers" on directives and components if defined.
|
||||||
|
if (directive.providersExpr) {
|
||||||
|
const evaluatedExpr = this.partialEvaluator.evaluate(directive.providersExpr);
|
||||||
|
if (!Array.isArray(evaluatedExpr)) {
|
||||||
|
return [
|
||||||
|
{node: directive.providersExpr, message: `Providers are not statically analyzable.`}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
failures.push(...this._visitProviderResolvedValue(evaluatedExpr, directive));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate "viewProviders" on components if defined.
|
||||||
|
if (directive.viewProvidersExpr) {
|
||||||
|
const evaluatedExpr = this.partialEvaluator.evaluate(directive.viewProvidersExpr);
|
||||||
|
if (!Array.isArray(evaluatedExpr)) {
|
||||||
|
return [
|
||||||
|
{node: directive.viewProvidersExpr, message: `Providers are not statically analyzable.`}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
failures.push(...this._visitProviderResolvedValue(evaluatedExpr, directive));
|
||||||
|
}
|
||||||
|
return failures;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Migrates a given provider class if it is not decorated with
|
* Migrates a given provider class if it is not decorated with
|
||||||
* any Angular decorator.
|
* any Angular decorator.
|
||||||
*/
|
*/
|
||||||
migrateProviderClass(node: ts.ClassDeclaration, module: ResolvedNgModule) {
|
migrateProviderClass(node: ts.ClassDeclaration, context: ResolvedNgModule|ResolvedDirective) {
|
||||||
if (this.visitedProviderClasses.has(node)) {
|
if (this.visitedProviderClasses.has(node)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -93,9 +144,9 @@ export class MissingInjectableTransform {
|
||||||
const existingInjectDecorator =
|
const existingInjectDecorator =
|
||||||
ngDecorators !== null ? ngDecorators.find(d => d.name === 'Inject') : null;
|
ngDecorators !== null ? ngDecorators.find(d => d.name === 'Inject') : null;
|
||||||
if (existingInjectDecorator) {
|
if (existingInjectDecorator) {
|
||||||
updateRecorder.replaceDecorator(existingInjectDecorator.node, newDecoratorText, module.name);
|
updateRecorder.replaceDecorator(existingInjectDecorator.node, newDecoratorText, context.name);
|
||||||
} else {
|
} else {
|
||||||
updateRecorder.addClassDecorator(node, newDecoratorText, module.name);
|
updateRecorder.addClassDecorator(node, newDecoratorText, context.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import * as ts from 'typescript';
|
||||||
export interface UpdateRecorder {
|
export interface UpdateRecorder {
|
||||||
addNewImport(start: number, importText: string): void;
|
addNewImport(start: number, importText: string): void;
|
||||||
updateExistingImport(namedBindings: ts.NamedImports, newNamedBindings: string): void;
|
updateExistingImport(namedBindings: ts.NamedImports, newNamedBindings: string): void;
|
||||||
addClassDecorator(node: ts.ClassDeclaration, text: string, moduleName: string): void;
|
addClassDecorator(node: ts.ClassDeclaration, text: string, className: string): void;
|
||||||
replaceDecorator(node: ts.Decorator, newText: string, moduleName: string): void;
|
replaceDecorator(node: ts.Decorator, newText: string, className: string): void;
|
||||||
commitUpdate(): void;
|
commitUpdate(): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,14 +45,48 @@ describe('Google3 missing injectable tslint rule', () => {
|
||||||
|
|
||||||
function getFile(fileName: string) { return readFileSync(join(tmpDir, fileName), 'utf8'); }
|
function getFile(fileName: string) { return readFileSync(join(tmpDir, fileName), 'utf8'); }
|
||||||
|
|
||||||
|
describe('NgModule', () => createTests('NgModule', 'providers'));
|
||||||
|
describe('Directive', () => createTests('Directive', 'providers'));
|
||||||
|
|
||||||
|
describe('Component', () => {
|
||||||
|
createTests('Component', 'providers');
|
||||||
|
createTests('Component', 'viewProviders');
|
||||||
|
|
||||||
|
it('should migrate all providers defined in "viewProviders" and "providers" in the ' +
|
||||||
|
'same component',
|
||||||
|
() => {
|
||||||
|
writeFile('/index.ts', `
|
||||||
|
import {Component} from '@angular/core';
|
||||||
|
|
||||||
|
export class MyService {}
|
||||||
|
export class MySecondService {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
providers: [MyService],
|
||||||
|
viewProviders: [MySecondService],
|
||||||
|
})
|
||||||
|
export class TestClass {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const result = runTSLint().getResult();
|
||||||
|
|
||||||
|
expect(result.errorCount).toBe(0);
|
||||||
|
expect(getFile('/index.ts')).toMatch(/@Injectable\(\)\s+export class MyService/);
|
||||||
|
expect(getFile('/index.ts')).toMatch(/@Injectable\(\)\s+export class MySecondService/);
|
||||||
|
expect(getFile('/index.ts')).toContain(`{ Component, Injectable } from '@angular/core`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createTests(
|
||||||
|
type: 'NgModule' | 'Directive' | 'Component', propName: 'providers' | 'viewProviders') {
|
||||||
it('should create proper failures for missing injectable providers', () => {
|
it('should create proper failures for missing injectable providers', () => {
|
||||||
writeFile('index.ts', `
|
writeFile('index.ts', `
|
||||||
import { NgModule } from '@angular/core';
|
import { ${type} } from '@angular/core';
|
||||||
|
|
||||||
export class A {}
|
export class A {}
|
||||||
|
|
||||||
@NgModule({providers: [A]})
|
@${type}({${propName}: [A]})
|
||||||
export class AppModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const linter = runTSLint(false);
|
const linter = runTSLint(false);
|
||||||
|
@ -60,10 +94,11 @@ describe('Google3 missing injectable tslint rule', () => {
|
||||||
|
|
||||||
expect(failures.length).toBe(2);
|
expect(failures.length).toBe(2);
|
||||||
expect(failures[0].getFailure())
|
expect(failures[0].getFailure())
|
||||||
.toMatch(/Class needs to be decorated with "@Injectable\(\)".*provided by "AppModule"/);
|
.toMatch(/Class needs to be decorated with "@Injectable\(\)".*provided by "TestClass"/);
|
||||||
expect(failures[0].getStartPosition().getLineAndCharacter()).toEqual({line: 3, character: 6});
|
expect(failures[0].getStartPosition().getLineAndCharacter()).toEqual({line: 3, character: 8});
|
||||||
expect(failures[1].getFailure()).toMatch(/Import needs to be updated to import.*Injectable/);
|
expect(failures[1].getFailure()).toMatch(/Import needs to be updated to import.*Injectable/);
|
||||||
expect(failures[1].getStartPosition().getLineAndCharacter()).toEqual({line: 1, character: 13});
|
expect(failures[1].getStartPosition().getLineAndCharacter())
|
||||||
|
.toEqual({line: 1, character: 15});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update provider classes which need to be migrated in Ivy', () => {
|
it('should update provider classes which need to be migrated in Ivy', () => {
|
||||||
|
@ -87,7 +122,7 @@ describe('Google3 missing injectable tslint rule', () => {
|
||||||
export class MyServiceF {}
|
export class MyServiceF {}
|
||||||
export class MyServiceG {}
|
export class MyServiceG {}
|
||||||
|
|
||||||
@NgModule({providers: [
|
@${type}({${propName}: [
|
||||||
WithPipe,
|
WithPipe,
|
||||||
[
|
[
|
||||||
WithDirective,
|
WithDirective,
|
||||||
|
@ -101,7 +136,7 @@ describe('Google3 missing injectable tslint rule', () => {
|
||||||
{provide: MyServiceF, useFactory: () => null},
|
{provide: MyServiceF, useFactory: () => null},
|
||||||
{provide: MyServiceG, useValue: null},
|
{provide: MyServiceG, useValue: null},
|
||||||
]})
|
]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|
||||||
|
@ -121,41 +156,41 @@ describe('Google3 missing injectable tslint rule', () => {
|
||||||
expect(getFile('/index.ts')).toMatch(/MyServiceF {}\s+export class MyServiceG/);
|
expect(getFile('/index.ts')).toMatch(/MyServiceF {}\s+export class MyServiceG/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should migrate provider once if referenced in multiple NgModule definitions', () => {
|
it(`should migrate provider once if referenced in multiple ${type} definitions`, () => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
export class ServiceA {}
|
export class ServiceA {}
|
||||||
|
|
||||||
@NgModule({providers: [ServiceA]})
|
@${type}({${propName}: [ServiceA]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
writeFile('/second.ts', `
|
writeFile('/second.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
import {ServiceA} from './index';
|
import {ServiceA} from './index';
|
||||||
|
|
||||||
export class ServiceB {}
|
export class ServiceB {}
|
||||||
|
|
||||||
@NgModule({providers: [ServiceA, ServiceB]})
|
@${type}({${propName}: [ServiceA, ServiceB]})
|
||||||
export class SecondModule {}
|
export class TestClass2 {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
runTSLint();
|
runTSLint();
|
||||||
|
|
||||||
expect(getFile('/index.ts'))
|
expect(getFile('/index.ts'))
|
||||||
.toMatch(/@angular\/core';\s+@Injectable\(\)\s+export class ServiceA/);
|
.toMatch(/@angular\/core';\s+@Injectable\(\)\s+export class ServiceA/);
|
||||||
expect(getFile('/index.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(getFile('/index.ts')).toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
expect(getFile('/second.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
expect(getFile('/second.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
||||||
expect(getFile('/second.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(getFile('/second.ts')).toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should warn if a referenced provider could not be resolved', () => {
|
it('should warn if a referenced provider could not be resolved', () => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
@NgModule({providers: [NotPresent]})
|
@${type}({${propName}: [NotPresent]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const linter = runTSLint();
|
const linter = runTSLint();
|
||||||
|
@ -163,32 +198,34 @@ describe('Google3 missing injectable tslint rule', () => {
|
||||||
|
|
||||||
expect(failures.length).toBe(1);
|
expect(failures.length).toBe(1);
|
||||||
expect(failures[0].getFailure()).toMatch(/Provider is not statically analyzable./);
|
expect(failures[0].getFailure()).toMatch(/Provider is not statically analyzable./);
|
||||||
expect(failures[0].getStartPosition().getLineAndCharacter()).toEqual({line: 3, character: 29});
|
expect(failures[0].getStartPosition().getLineAndCharacter())
|
||||||
|
.toEqual({line: 3, character: 14 + type.length + propName.length});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should warn if the module providers could not be resolved', () => {
|
it(`should warn if the "${propName}" value could not be resolved`, () => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
@NgModule({providers: NOT_ANALYZABLE)
|
@${type}({${propName}: NOT_ANALYZABLE)
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const linter = runTSLint();
|
const linter = runTSLint();
|
||||||
const failures = linter.getResult().failures;
|
const failures = linter.getResult().failures;
|
||||||
|
|
||||||
expect(failures.length).toBe(1);
|
expect(failures.length).toBe(1);
|
||||||
expect(failures[0].getFailure()).toMatch(/Providers of module.*not statically analyzable./);
|
expect(failures[0].getFailure()).toMatch(/Providers.*not statically analyzable./);
|
||||||
expect(failures[0].getStartPosition().getLineAndCharacter()).toEqual({line: 3, character: 28});
|
expect(failures[0].getStartPosition().getLineAndCharacter())
|
||||||
|
.toEqual({line: 3, character: 13 + type.length + propName.length});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create new import for @Injectable when migrating provider', () => {
|
it('should create new import for @Injectable when migrating provider', () => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
import {MyService, MySecondService} from './service';
|
import {MyService, MySecondService} from './service';
|
||||||
|
|
||||||
@NgModule({providers: [MyService, MySecondService]})
|
@${type}({${propName}: [MyService, MySecondService]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
writeFile('/service.ts', `export class MyService {}
|
writeFile('/service.ts', `export class MyService {}
|
||||||
|
@ -205,11 +242,11 @@ describe('Google3 missing injectable tslint rule', () => {
|
||||||
|
|
||||||
it('should remove @Inject decorator for providers which are migrated', () => {
|
it('should remove @Inject decorator for providers which are migrated', () => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
import {MyService} from './service';
|
import {MyService} from './service';
|
||||||
|
|
||||||
@NgModule({providers: [MyService]})
|
@${type}({${propName}: [MyService]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
writeFile('/service.ts', `
|
writeFile('/service.ts', `
|
||||||
|
@ -222,6 +259,10 @@ describe('Google3 missing injectable tslint rule', () => {
|
||||||
runTSLint();
|
runTSLint();
|
||||||
|
|
||||||
expect(getFile('/service.ts')).toMatch(/core';\s+@Injectable\(\)\s+export class MyService/);
|
expect(getFile('/service.ts')).toMatch(/core';\s+@Injectable\(\)\s+export class MyService/);
|
||||||
expect(getFile('/service.ts')).toMatch(/import { Inject, Injectable } from '@angular\/core';/);
|
expect(getFile('/service.ts'))
|
||||||
|
.toMatch(/import { Inject, Injectable } from '@angular\/core';/);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -63,48 +63,86 @@ describe('Missing injectable migration', () => {
|
||||||
await runner.runSchematicAsync('migration-v9-missing-injectable', {}, tree).toPromise();
|
await runner.runSchematicAsync('migration-v9-missing-injectable', {}, tree).toPromise();
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should migrate type provider in NgModule', async() => {
|
describe('NgModule', () => createTests('NgModule', 'providers'));
|
||||||
|
describe('Directive', () => createTests('Directive', 'providers'));
|
||||||
|
|
||||||
|
describe('Component', () => {
|
||||||
|
createTests('Component', 'providers');
|
||||||
|
createTests('Component', 'viewProviders');
|
||||||
|
|
||||||
|
it('should migrate all providers defined in "viewProviders" and "providers" in the ' +
|
||||||
|
'same component',
|
||||||
|
async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
|
|
||||||
export class MyService {}
|
export class MyService {}
|
||||||
|
export class MySecondService {}
|
||||||
|
|
||||||
@NgModule({providers: [MyService]})
|
@Component({
|
||||||
export class MyModule {}
|
providers: [MyService],
|
||||||
|
viewProviders: [MySecondService],
|
||||||
|
})
|
||||||
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
|
||||||
expect(warnOutput.length).toBe(0);
|
expect(warnOutput.length).toBe(0);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class MyService/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class MyService/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(tree.readContent('/index.ts'))
|
||||||
|
.toMatch(/@Injectable\(\)\s+export class MySecondService/);
|
||||||
|
expect(tree.readContent('/index.ts'))
|
||||||
|
.toContain(`{ Component, Injectable } from '@angular/core`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should migrate object literal provider in NgModule', async() => {
|
function createTests(
|
||||||
|
type: 'NgModule' | 'Directive' | 'Component', propName: 'providers' | 'viewProviders') {
|
||||||
|
it(`should migrate type provider in ${type}`, async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
export class MyService {}
|
export class MyService {}
|
||||||
|
|
||||||
@NgModule({providers: [{provide: MyService}]})
|
@${type}({${propName}: [MyService]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
|
||||||
expect(warnOutput.length).toBe(0);
|
expect(warnOutput.length).toBe(0);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class MyService/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class MyService/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(tree.readContent('/index.ts'))
|
||||||
|
.toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not migrate object literal provider with "useValue" in NgModule', async() => {
|
it(`should migrate object literal provider in ${type}`, async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
export class MyService {}
|
export class MyService {}
|
||||||
|
|
||||||
@NgModule({providers: [{provide: MyService, useValue: null }]})
|
@${type}({${propName}: [{provide: MyService}]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
|
`);
|
||||||
|
|
||||||
|
await runMigration();
|
||||||
|
|
||||||
|
expect(warnOutput.length).toBe(0);
|
||||||
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class MyService/);
|
||||||
|
expect(tree.readContent('/index.ts'))
|
||||||
|
.toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should not migrate object literal provider with "useValue" in ${type}`, async() => {
|
||||||
|
writeFile('/index.ts', `
|
||||||
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
|
export class MyService {}
|
||||||
|
|
||||||
|
@${type}({${propName}: [{provide: MyService, useValue: null }]})
|
||||||
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -113,14 +151,14 @@ describe('Missing injectable migration', () => {
|
||||||
expect(tree.readContent('/index.ts')).not.toContain('@Injectable');
|
expect(tree.readContent('/index.ts')).not.toContain('@Injectable');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not migrate object literal provider with "useFactory" in NgModule', async() => {
|
it(`should not migrate object literal provider with "useFactory" in ${type}`, async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
export class MyService {}
|
export class MyService {}
|
||||||
|
|
||||||
@NgModule({providers: [{provide: MyService, useFactory: () => null }]})
|
@${type}({${propName}: [{provide: MyService, useFactory: () => null }]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -129,15 +167,15 @@ describe('Missing injectable migration', () => {
|
||||||
expect(tree.readContent('/index.ts')).not.toContain('@Injectable');
|
expect(tree.readContent('/index.ts')).not.toContain('@Injectable');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should migrate object literal provider with "useExisting" in NgModule', async() => {
|
it(`should migrate object literal provider with "useExisting" in ${type}`, async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
export class MyService {}
|
export class MyService {}
|
||||||
export class MyToken {}
|
export class MyToken {}
|
||||||
|
|
||||||
@NgModule({providers: [{provide: MyToken, useExisting: MyService}]})
|
@${type}({${propName}: [{provide: MyToken, useExisting: MyService}]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -145,18 +183,19 @@ describe('Missing injectable migration', () => {
|
||||||
expect(warnOutput.length).toBe(0);
|
expect(warnOutput.length).toBe(0);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class MyService/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class MyService/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/MyService {}\s+export class MyToken/);
|
expect(tree.readContent('/index.ts')).toMatch(/MyService {}\s+export class MyToken/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(tree.readContent('/index.ts'))
|
||||||
|
.toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should migrate object literal provider with "useClass" in NgModule', async() => {
|
it(`should migrate object literal provider with "useClass" in ${type}`, async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
export class MyService {}
|
export class MyService {}
|
||||||
export class MyToken {}
|
export class MyToken {}
|
||||||
|
|
||||||
@NgModule({providers: [{provide: MyToken, useClass: MyService}]})
|
@${type}({${propName}: [{provide: MyToken, useClass: MyService}]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -164,18 +203,19 @@ describe('Missing injectable migration', () => {
|
||||||
expect(warnOutput.length).toBe(0);
|
expect(warnOutput.length).toBe(0);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class MyService/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class MyService/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/MyService {}\s+export class MyToken/);
|
expect(tree.readContent('/index.ts')).toMatch(/MyService {}\s+export class MyToken/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(tree.readContent('/index.ts'))
|
||||||
|
.toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not migrate provider which is already decorated with @Injectable', async() => {
|
it('should not migrate provider which is already decorated with @Injectable', async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {Injectable, NgModule} from '@angular/core';
|
import {Injectable, ${type}} from '@angular/core';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MyService {}
|
export class MyService {}
|
||||||
|
|
||||||
@NgModule({providers: [MyService]})
|
@${type}({${propName}: [MyService]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -187,13 +227,13 @@ describe('Missing injectable migration', () => {
|
||||||
|
|
||||||
it('should not migrate provider which is already decorated with @Directive', async() => {
|
it('should not migrate provider which is already decorated with @Directive', async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {Directive, NgModule} from '@angular/core';
|
import {Directive, ${type}} from '@angular/core';
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class MyService {}
|
export class MyService {}
|
||||||
|
|
||||||
@NgModule({providers: [MyService]})
|
@${type}({${propName}: [MyService]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -204,13 +244,13 @@ describe('Missing injectable migration', () => {
|
||||||
|
|
||||||
it('should not migrate provider which is already decorated with @Component', async() => {
|
it('should not migrate provider which is already decorated with @Component', async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {Component, NgModule} from '@angular/core';
|
import {Component, ${type}} from '@angular/core';
|
||||||
|
|
||||||
@Component()
|
@Component()
|
||||||
export class MyService {}
|
export class MyService {}
|
||||||
|
|
||||||
@NgModule({providers: [MyService]})
|
@${type}({${propName}: [MyService]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -221,13 +261,13 @@ describe('Missing injectable migration', () => {
|
||||||
|
|
||||||
it('should not migrate provider which is already decorated with @Pipe', async() => {
|
it('should not migrate provider which is already decorated with @Pipe', async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {Pipe, NgModule} from '@angular/core';
|
import {Pipe, ${type}} from '@angular/core';
|
||||||
|
|
||||||
@Pipe()
|
@Pipe()
|
||||||
export class MyService {}
|
export class MyService {}
|
||||||
|
|
||||||
@NgModule({providers: [MyService]})
|
@${type}({${propName}: [MyService]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -236,15 +276,15 @@ describe('Missing injectable migration', () => {
|
||||||
expect(tree.readContent('/index.ts')).not.toContain('@Injectable');
|
expect(tree.readContent('/index.ts')).not.toContain('@Injectable');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should migrate multiple providers in same NgModule', async() => {
|
it(`should migrate multiple providers in same ${type}`, async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
export class ServiceA {}
|
export class ServiceA {}
|
||||||
export class ServiceB {}
|
export class ServiceB {}
|
||||||
|
|
||||||
@NgModule({providers: [ServiceA, ServiceB]})
|
@${type}({${propName}: [ServiceA, ServiceB]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -252,24 +292,25 @@ describe('Missing injectable migration', () => {
|
||||||
expect(warnOutput.length).toBe(0);
|
expect(warnOutput.length).toBe(0);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceA/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceA/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(tree.readContent('/index.ts'))
|
||||||
|
.toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should migrate multiple mixed providers in same NgModule', async() => {
|
it(`should migrate multiple mixed providers in same ${type}`, async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
export class ServiceA {}
|
export class ServiceA {}
|
||||||
export class ServiceB {}
|
export class ServiceB {}
|
||||||
export class ServiceC {}
|
export class ServiceC {}
|
||||||
|
|
||||||
@NgModule({
|
@${type}({
|
||||||
providers: [
|
${propName}: [
|
||||||
ServiceA,
|
ServiceA,
|
||||||
{provide: ServiceB},
|
{provide: ServiceB},
|
||||||
{provide: SomeToken, useClass: ServiceC},
|
{provide: SomeToken, useClass: ServiceC},
|
||||||
]})
|
]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -278,27 +319,28 @@ describe('Missing injectable migration', () => {
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceA/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceA/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceC/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceC/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(tree.readContent('/index.ts'))
|
||||||
|
.toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should migrate multiple nested providers in same NgModule', async() => {
|
it(`should migrate multiple nested providers in same ${type}`, async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
export class ServiceA {}
|
export class ServiceA {}
|
||||||
export class ServiceB {}
|
export class ServiceB {}
|
||||||
export class ServiceC {}
|
export class ServiceC {}
|
||||||
|
|
||||||
@NgModule({
|
@${type}({
|
||||||
providers: [
|
${propName}: [
|
||||||
ServiceA,
|
ServiceA,
|
||||||
[
|
[
|
||||||
{provide: ServiceB},
|
{provide: ServiceB},
|
||||||
ServiceC,
|
ServiceC,
|
||||||
],
|
],
|
||||||
]})
|
]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -307,22 +349,23 @@ describe('Missing injectable migration', () => {
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceA/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceA/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceC/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceC/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(tree.readContent('/index.ts'))
|
||||||
|
.toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should migrate providers referenced through identifier', async() => {
|
it('should migrate providers referenced through identifier', async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
export class ServiceA {}
|
export class ServiceA {}
|
||||||
export class ServiceB {}
|
export class ServiceB {}
|
||||||
|
|
||||||
const PROVIDERS = [ServiceA, ServiceB];
|
const PROVIDERS = [ServiceA, ServiceB];
|
||||||
|
|
||||||
@NgModule({
|
@${type}({
|
||||||
providers: PROVIDERS,
|
${propName}: PROVIDERS,
|
||||||
})
|
})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -330,12 +373,13 @@ describe('Missing injectable migration', () => {
|
||||||
expect(warnOutput.length).toBe(0);
|
expect(warnOutput.length).toBe(0);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceA/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceA/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(tree.readContent('/index.ts'))
|
||||||
|
.toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should migrate providers created through static analyzable function call', async() => {
|
it('should migrate providers created through static analyzable function call', async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
export class ServiceA {}
|
export class ServiceA {}
|
||||||
export class ServiceB {}
|
export class ServiceB {}
|
||||||
|
@ -344,10 +388,10 @@ describe('Missing injectable migration', () => {
|
||||||
return [ServiceA, x]
|
return [ServiceA, x]
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
@${type}({
|
||||||
providers: createProviders(ServiceB),
|
${propName}: createProviders(ServiceB),
|
||||||
})
|
})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -355,22 +399,23 @@ describe('Missing injectable migration', () => {
|
||||||
expect(warnOutput.length).toBe(0);
|
expect(warnOutput.length).toBe(0);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceA/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceA/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(tree.readContent('/index.ts'))
|
||||||
|
.toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should migrate providers which are computed through spread operator', async() => {
|
it('should migrate providers which are computed through spread operator', async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
export class ServiceA {}
|
export class ServiceA {}
|
||||||
export class ServiceB {}
|
export class ServiceB {}
|
||||||
|
|
||||||
const otherServices = [ServiceB];
|
const otherServices = [ServiceB];
|
||||||
|
|
||||||
@NgModule({
|
@${type}({
|
||||||
providers: [ServiceA, ...otherServices],
|
${propName}: [ServiceA, ...otherServices],
|
||||||
})
|
})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -378,27 +423,28 @@ describe('Missing injectable migration', () => {
|
||||||
expect(warnOutput.length).toBe(0);
|
expect(warnOutput.length).toBe(0);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceA/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceA/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
expect(tree.readContent('/index.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(tree.readContent('/index.ts'))
|
||||||
|
.toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should migrate provider once if referenced in multiple NgModule definitions', async() => {
|
it(`should migrate provider once if referenced in multiple ${type} definitions`, async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
export class ServiceA {}
|
export class ServiceA {}
|
||||||
|
|
||||||
@NgModule({providers: [ServiceA]})
|
@${type}({${propName}: [ServiceA]})
|
||||||
export class MyModule {}
|
export class TestClassA {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
writeFile('/second.ts', `
|
writeFile('/second.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
import {ServiceA} from './index';
|
import {ServiceA} from './index';
|
||||||
|
|
||||||
export class ServiceB {}
|
export class ServiceB {}
|
||||||
|
|
||||||
@NgModule({providers: [ServiceA, ServiceB]})
|
@${type}({${propName}: [ServiceA, ServiceB]})
|
||||||
export class SecondModule {}
|
export class TestClassB {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -406,18 +452,20 @@ describe('Missing injectable migration', () => {
|
||||||
expect(warnOutput.length).toBe(0);
|
expect(warnOutput.length).toBe(0);
|
||||||
expect(tree.readContent('/index.ts'))
|
expect(tree.readContent('/index.ts'))
|
||||||
.toMatch(/@angular\/core';\s+@Injectable\(\)\s+export class ServiceA/);
|
.toMatch(/@angular\/core';\s+@Injectable\(\)\s+export class ServiceA/);
|
||||||
expect(tree.readContent('/index.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(tree.readContent('/index.ts'))
|
||||||
|
.toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
expect(tree.readContent('/second.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
expect(tree.readContent('/second.ts')).toMatch(/@Injectable\(\)\s+export class ServiceB/);
|
||||||
expect(tree.readContent('/second.ts')).toMatch(/{ NgModule, Injectable } from '@angular\/core/);
|
expect(tree.readContent('/second.ts'))
|
||||||
|
.toContain(`{ ${type}, Injectable } from '@angular/core`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create new import for @Injectable when migrating provider', async() => {
|
it('should create new import for @Injectable when migrating provider', async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
import {MyService, MySecondService} from './service';
|
import {MyService, MySecondService} from './service';
|
||||||
|
|
||||||
@NgModule({providers: [MyService, MySecondService]})
|
@${type}({${propName}: [MyService, MySecondService]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
writeFile('/service.ts', `export class MyService {}
|
writeFile('/service.ts', `export class MyService {}
|
||||||
|
@ -431,7 +479,8 @@ describe('Missing injectable migration', () => {
|
||||||
expect(tree.readContent('/service.ts')).toMatch(/@Injectable\(\)\s+export class MyService/);
|
expect(tree.readContent('/service.ts')).toMatch(/@Injectable\(\)\s+export class MyService/);
|
||||||
expect(tree.readContent('/service.ts'))
|
expect(tree.readContent('/service.ts'))
|
||||||
.toMatch(/@Injectable\(\)\s+export class MySecondService/);
|
.toMatch(/@Injectable\(\)\s+export class MySecondService/);
|
||||||
expect(tree.readContent('/service.ts')).toMatch(/import { Injectable } from "@angular\/core";/);
|
expect(tree.readContent('/service.ts'))
|
||||||
|
.toMatch(/import { Injectable } from "@angular\/core";/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should re-use existing namespace import for importing @Injectable when migrating provider',
|
it('should re-use existing namespace import for importing @Injectable when migrating provider',
|
||||||
|
@ -447,11 +496,11 @@ describe('Missing injectable migration', () => {
|
||||||
`);
|
`);
|
||||||
|
|
||||||
writeFile('/app.module.ts', `
|
writeFile('/app.module.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
import {MyService} from './index';
|
import {MyService} from './index';
|
||||||
|
|
||||||
@NgModule({providers: [MyService]})
|
@${type}({${propName}: [MyService]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
@ -461,40 +510,43 @@ describe('Missing injectable migration', () => {
|
||||||
.toMatch(/@core.Injectable\(\)\s+export class MyService/);
|
.toMatch(/@core.Injectable\(\)\s+export class MyService/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should warn if a referenced provider could not be resolved', async() => {
|
it('should warn if a referenced individual provider could not be resolved', async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
@NgModule({providers: [NotPresent]})
|
@${type}({${propName}: [NotPresent]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
|
||||||
|
const providerSourceTextColumn = 15 + type.length + propName.length;
|
||||||
expect(warnOutput.length).toBe(1);
|
expect(warnOutput.length).toBe(1);
|
||||||
expect(warnOutput[0]).toMatch(/\s+index\.ts@4:30: Provider is not statically analyzable./);
|
expect(warnOutput[0]).toMatch(/\s+index\.ts@4:.+Provider is not statically analyzable./);
|
||||||
|
expect(warnOutput[0]).toContain(`4:${providerSourceTextColumn}:`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should warn if the module providers could not be resolved', async() => {
|
it(`should warn if ${propName} value could not be resolved`, async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
@NgModule({providers: NOT_ANALYZABLE)
|
@${type}({${propName}: NOT_ANALYZABLE)
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
|
||||||
|
const propValueSourceTextColumn = 14 + type.length + propName.length;
|
||||||
expect(warnOutput.length).toBe(1);
|
expect(warnOutput.length).toBe(1);
|
||||||
expect(warnOutput[0])
|
expect(warnOutput[0]).toMatch(/\s+index\.ts@4:.+Providers.*not statically analyzable./);
|
||||||
.toMatch(/\s+index\.ts@4:29: Providers of module.*not statically analyzable./);
|
expect(warnOutput[0]).toContain(`4:${propValueSourceTextColumn}:`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw if an empty @NgModule is analyzed', async() => {
|
it(`should not throw if an empty @${type} is analyzed`, async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
|
|
||||||
@NgModule()
|
@${type}()
|
||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
@ -507,13 +559,14 @@ describe('Missing injectable migration', () => {
|
||||||
expect(warnOutput.length).toBe(0);
|
expect(warnOutput.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create new import for injectable after full end of last import statement', async() => {
|
it('should create new import for injectable after full end of last import statement',
|
||||||
|
async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
import {MyService} from './service';
|
import {MyService} from './service';
|
||||||
|
|
||||||
@NgModule({providers: [MyService]})
|
@${type}({${propName}: [MyService]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
writeFile('/service.ts', `
|
writeFile('/service.ts', `
|
||||||
|
@ -526,18 +579,19 @@ describe('Missing injectable migration', () => {
|
||||||
await runMigration();
|
await runMigration();
|
||||||
|
|
||||||
expect(warnOutput.length).toBe(0);
|
expect(warnOutput.length).toBe(0);
|
||||||
expect(tree.readContent('/service.ts')).toMatch(/@Injectable\(\)\s+export class MyService/);
|
expect(tree.readContent('/service.ts'))
|
||||||
|
.toMatch(/@Injectable\(\)\s+export class MyService/);
|
||||||
expect(tree.readContent('/service.ts'))
|
expect(tree.readContent('/service.ts'))
|
||||||
.toMatch(/'b'; \/\/ some comment\s+import { Injectable } from "@angular\/core";/);
|
.toMatch(/'b'; \/\/ some comment\s+import { Injectable } from "@angular\/core";/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create new import at source file start with trailing new-line', async() => {
|
it('should create new import at source file start with trailing new-line', async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
import {MyService} from './service';
|
import {MyService} from './service';
|
||||||
|
|
||||||
@NgModule({providers: [MyService]})
|
@${type}({${propName}: [MyService]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
writeFile('/service.ts', `/* @license */
|
writeFile('/service.ts', `/* @license */
|
||||||
|
@ -554,11 +608,11 @@ describe('Missing injectable migration', () => {
|
||||||
|
|
||||||
it('should remove @Inject decorator for providers which are migrated', async() => {
|
it('should remove @Inject decorator for providers which are migrated', async() => {
|
||||||
writeFile('/index.ts', `
|
writeFile('/index.ts', `
|
||||||
import {NgModule} from '@angular/core';
|
import {${type}} from '@angular/core';
|
||||||
import {MyService} from './service';
|
import {MyService} from './service';
|
||||||
|
|
||||||
@NgModule({providers: [MyService]})
|
@${type}({${propName}: [MyService]})
|
||||||
export class MyModule {}
|
export class TestClass {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
writeFile('/service.ts', `
|
writeFile('/service.ts', `
|
||||||
|
@ -573,8 +627,10 @@ describe('Missing injectable migration', () => {
|
||||||
expect(warnOutput.length).toBe(0);
|
expect(warnOutput.length).toBe(0);
|
||||||
expect(tree.readContent('/service.ts'))
|
expect(tree.readContent('/service.ts'))
|
||||||
.toMatch(/core';\s+@Injectable\(\)\s+export class MyService/);
|
.toMatch(/core';\s+@Injectable\(\)\s+export class MyService/);
|
||||||
|
// "inject" import will be preserved. We don't want to bother with detecting
|
||||||
|
// if the import is unused or not. We leave this up to the developers.
|
||||||
expect(tree.readContent('/service.ts'))
|
expect(tree.readContent('/service.ts'))
|
||||||
.toMatch(/import { Inject, Injectable } from '@angular\/core';/);
|
.toMatch(/import { Inject, Injectable } from '@angular\/core';/);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue