feat(ivy): add injectable-pipe schematic (#29847)

Adds a schematic that will annotate all `Pipe` classes with `Injectable` so that they can be injected.

This PR resolves FW-1228.

PR Close #29847
This commit is contained in:
Kristiyan Kostadinov
2019-04-12 20:19:32 +02:00
committed by Ben Lesh
parent aaf8145c48
commit d92fb25161
15 changed files with 547 additions and 6 deletions

View File

@ -8,6 +8,8 @@ ts_library(
"//packages/core/schematics:migrations.json",
],
deps = [
"//packages/core/schematics/migrations/injectable-pipe",
"//packages/core/schematics/migrations/injectable-pipe/google3",
"//packages/core/schematics/migrations/move-document",
"//packages/core/schematics/migrations/static-queries",
"//packages/core/schematics/migrations/static-queries/google3",

View File

@ -0,0 +1,91 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {readFileSync, writeFileSync} from 'fs';
import {dirname, join} from 'path';
import * as shx from 'shelljs';
import {Configuration, Linter} from 'tslint';
describe('Google3 injectable pipe TSLint rule', () => {
const rulesDirectory =
dirname(require.resolve('../../migrations/injectable-pipe/google3/injectablePipeRule'));
let tmpDir: string;
beforeEach(() => {
tmpDir = join(process.env['TEST_TMPDIR'] !, 'google3-test');
shx.mkdir('-p', tmpDir);
writeFile('tsconfig.json', JSON.stringify({compilerOptions: {module: 'es2015'}}));
});
afterEach(() => shx.rm('-r', tmpDir));
function runTSLint(fix = true) {
const program = Linter.createProgram(join(tmpDir, 'tsconfig.json'));
const linter = new Linter({fix, rulesDirectory: [rulesDirectory]}, program);
const config = Configuration.parseConfigFile(
{rules: {'injectable-pipe': true}, linterOptions: {typeCheck: true}});
program.getRootFileNames().forEach(fileName => {
linter.lint(fileName, program.getSourceFile(fileName) !.getFullText(), config);
});
return linter;
}
function writeFile(fileName: string, content: string) {
writeFileSync(join(tmpDir, fileName), content);
}
function getFile(fileName: string) { return readFileSync(join(tmpDir, fileName), 'utf8'); }
it('should report pipes that are not marked as Injectable', () => {
writeFile('index.ts', `
import { Pipe } from '@angular/core';
@Pipe({ name: 'myPipe' })
export class MyPipe {
}
`);
const linter = runTSLint(false);
const failures = linter.getResult().failures;
expect(failures.length).toBe(1);
expect(failures[0].getFailure()).toMatch(/@Pipe should be decorated with @Injectable/);
});
it('should add @Injectable to pipes that do not have it', () => {
writeFile('/index.ts', `
import { Pipe } from '@angular/core';
@Pipe({ name: 'myPipe' })
export class MyPipe {
}
`);
runTSLint();
expect(getFile('/index.ts'))
.toMatch(/@Injectable\(\)\s+@Pipe\(\{ name: 'myPipe' \}\)\s+export class MyPipe/);
});
it('should add an import for Injectable to the @angular/core import declaration', () => {
writeFile('/index.ts', `
import { Pipe } from '@angular/core';
@Pipe()
export class MyPipe {
}
`);
runTSLint();
expect(getFile('/index.ts')).toContain('import { Pipe, Injectable } from \'@angular/core\'');
});
});

View File

@ -0,0 +1,125 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {getSystemPath, normalize, virtualFs} from '@angular-devkit/core';
import {TempScopedNodeJsSyncHost} from '@angular-devkit/core/node/testing';
import {HostTree} from '@angular-devkit/schematics';
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
import * as shx from 'shelljs';
describe('injectable pipe migration', () => {
let runner: SchematicTestRunner;
let host: TempScopedNodeJsSyncHost;
let tree: UnitTestTree;
let tmpDirPath: string;
let previousWorkingDir: string;
beforeEach(() => {
runner = new SchematicTestRunner('test', require.resolve('../migrations.json'));
host = new TempScopedNodeJsSyncHost();
tree = new UnitTestTree(new HostTree(host));
writeFile('/tsconfig.json', JSON.stringify({
compilerOptions: {
lib: ['es2015'],
}
}));
writeFile('/angular.json', JSON.stringify({
projects: {t: {architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}
}));
previousWorkingDir = shx.pwd();
tmpDirPath = getSystemPath(host.root);
// Switch into the temporary directory path. This allows us to run
// the schematic against our custom unit test tree.
shx.cd(tmpDirPath);
});
afterEach(() => {
shx.cd(previousWorkingDir);
shx.rm('-r', tmpDirPath);
});
it('should add @Injectable to pipes that do not have it', () => {
writeFile('/index.ts', `
import { Pipe } from '@angular/core';
@Pipe({ name: 'myPipe' })
export class MyPipe {
}
`);
runMigration();
expect(tree.readContent('/index.ts'))
.toMatch(/@Injectable\(\)\s+@Pipe\(\{ name: 'myPipe' \}\)\s+export class MyPipe/);
});
it('should add an import for Injectable to the @angular/core import declaration', () => {
writeFile('/index.ts', `
import { Pipe } from '@angular/core';
@Pipe()
export class MyPipe {
}
`);
runMigration();
expect(tree.readContent('/index.ts'))
.toContain('import { Pipe, Injectable } from \'@angular/core\'');
});
it('should not add an import for Injectable if it is imported already', () => {
writeFile('/index.ts', `
import { Pipe, Injectable, NgModule } from '@angular/core';
@Pipe()
export class MyPipe {
}
`);
runMigration();
expect(tree.readContent('/index.ts'))
.toContain('import { Pipe, Injectable, NgModule } from \'@angular/core\'');
});
it('should do nothing if the pipe is marked as injectable already', () => {
const source = `
import { Injectable, Pipe } from '@angular/core';
@Injectable()
@Pipe()
export class MyPipe {
}
`;
writeFile('/index.ts', source);
runMigration();
expect(tree.readContent('/index.ts')).toBe(source);
});
it('should not add @Injectable if @Pipe was not imported from @angular/core', () => {
const source = `
import { Pipe } from '@not-angular/core';
@Pipe()
export class MyPipe {
}
`;
writeFile('/index.ts', source);
runMigration();
expect(tree.readContent('/index.ts')).toBe(source);
});
function writeFile(filePath: string, contents: string) {
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
}
function runMigration() { runner.runSchematic('migration-v8-injectable-pipe', {}, tree); }
});