Skip to content

Commit 367d3a7

Browse files
committed
feat(@schematics/angular): tslint migration for 8
Migration of the `tslint.json` and `package.json` files required by the refactoring of codelyzer. For more information check this PR mgechev/codelyzer#754.
1 parent 4a093e5 commit 367d3a7

File tree

4 files changed

+202
-0
lines changed

4 files changed

+202
-0
lines changed

packages/schematics/angular/migrations/migration-collection.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
"version": "7.0.3",
3030
"factory": "./update-7/index#updateDevkitBuildNgPackagr",
3131
"description": "Update an Angular CLI project to version 7."
32+
},
33+
"migration-07": {
34+
"version": "8.0.0",
35+
"factory": "./update-8",
36+
"description": "Update an Angular CLI project to version 8."
3237
}
3338
}
3439
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { JsonParseMode, Path, normalize, parseJson, parseJsonAst } from '@angular-devkit/core';
9+
import {
10+
Rule,
11+
SchematicsException,
12+
Tree,
13+
chain,
14+
} from '@angular-devkit/schematics';
15+
import {
16+
NodeDependency,
17+
NodeDependencyType,
18+
addPackageJsonDependency,
19+
} from '../../utility/dependencies';
20+
import { findPropertyInAstObject } from '../../utility/json-utils';
21+
22+
const ruleMapping: {[key: string]: string} = {
23+
'contextual-life-cycle': 'contextual-lifecycle',
24+
'no-conflicting-life-cycle-hooks': 'no-conflicting-lifecycle',
25+
'no-life-cycle-call': 'no-lifecycle-call',
26+
'use-life-cycle-interface': 'use-lifecycle-interface',
27+
'decorator-not-allowed': 'contextual-decorator',
28+
'enforce-component-selector': 'use-component-selector',
29+
'no-output-named-after-standard-event': 'no-output-native',
30+
'use-host-property-decorator': 'no-host-metadata-property',
31+
'use-input-property-decorator': 'no-inputs-metadata-property',
32+
'use-output-property-decorator': 'no-outputs-metadata-property',
33+
'no-queries-parameter': 'no-queries-metadata-property',
34+
'pipe-impure': 'no-pipe-impure',
35+
'use-view-encapsulation': 'use-component-view-encapsulation',
36+
i18n: 'template-i18n',
37+
'banana-in-box': 'template-banana-in-box',
38+
'no-template-call-expression': 'template-no-call-expression',
39+
'templates-no-negated-async': 'template-no-negated-async',
40+
'trackBy-function': 'template-use-track-by-function',
41+
'no-attribute-parameter-decorator': 'no-attribute-decorator',
42+
'max-inline-declarations': 'component-max-inline-declarations',
43+
};
44+
45+
function updateTsLintConfig(): Rule {
46+
return (host: Tree) => {
47+
const tsLintPath = '/tslint.json';
48+
const buffer = host.read(tsLintPath);
49+
if (!buffer) {
50+
return host;
51+
}
52+
const tsCfgAst = parseJsonAst(buffer.toString(), JsonParseMode.Loose);
53+
54+
if (tsCfgAst.kind != 'object') {
55+
return host;
56+
}
57+
58+
const rulesNode = findPropertyInAstObject(tsCfgAst, 'rules');
59+
if (!rulesNode || rulesNode.kind != 'object') {
60+
return host;
61+
}
62+
63+
const recorder = host.beginUpdate(tsLintPath);
64+
65+
rulesNode.properties.forEach(prop => {
66+
const mapping = ruleMapping[prop.key.value];
67+
if (mapping) {
68+
recorder.remove(prop.key.start.offset + 1, prop.key.value.length);
69+
recorder.insertLeft(prop.key.start.offset + 1, mapping);
70+
}
71+
});
72+
73+
host.commitUpdate(recorder);
74+
75+
return host;
76+
};
77+
}
78+
79+
function updatePackageJson() {
80+
return (host: Tree) => {
81+
const dependency: NodeDependency = {
82+
type: NodeDependencyType.Dev,
83+
name: 'codelyzer',
84+
version: '^5.0.0',
85+
overwrite: true,
86+
};
87+
88+
addPackageJsonDependency(host, dependency);
89+
90+
return host;
91+
};
92+
}
93+
94+
function getConfigPath(tree: Tree): Path {
95+
const possiblePath = normalize('angular.json');
96+
if (tree.exists(possiblePath)) {
97+
return possiblePath;
98+
}
99+
100+
throw new SchematicsException('Could not find configuration file');
101+
}
102+
103+
export default function(): Rule {
104+
return () => {
105+
return chain([
106+
updateTsLintConfig(),
107+
updatePackageJson(),
108+
]);
109+
};
110+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
// tslint:disable:no-big-function
9+
import { EmptyTree } from '@angular-devkit/schematics';
10+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
11+
12+
const renames = [
13+
'use-lifecycle-interface',
14+
'no-host-metadata-property',
15+
'no-outputs-metadata-property',
16+
'no-inputs-metadata-property',
17+
];
18+
19+
describe('Migration to version 8', () => {
20+
const schematicRunner = new SchematicTestRunner(
21+
'migrations',
22+
require.resolve('../migration-collection.json'),
23+
);
24+
25+
let tree: UnitTestTree;
26+
const tslintPath = '/tslint.json';
27+
const packageJsonPath = '/package.json';
28+
const baseConfig = {};
29+
const defaultOptions = {};
30+
const configPath = `/angular-cli.json`;
31+
const tslintConfig = {
32+
rules: {
33+
'directive-selector': [
34+
true,
35+
'attribute',
36+
'app',
37+
'camelCase',
38+
],
39+
'component-selector': [
40+
true,
41+
'element',
42+
'app',
43+
'kebab-case',
44+
],
45+
'no-output-on-prefix': true,
46+
'use-input-property-decorator': true,
47+
'use-output-property-decorator': true,
48+
'use-host-property-decorator': true,
49+
'no-input-rename': true,
50+
'no-output-rename': true,
51+
'use-life-cycle-interface': true,
52+
'use-pipe-transform-interface': true,
53+
'component-class-suffix': true,
54+
'directive-class-suffix': true,
55+
},
56+
};
57+
const packageJson = {
58+
devDependencies: {
59+
codelyzer: '^4.5.0',
60+
},
61+
};
62+
63+
describe('Migration of codelyzer to version 5', () => {
64+
beforeEach(() => {
65+
tree = new UnitTestTree(new EmptyTree());
66+
tree.create(configPath, JSON.stringify(baseConfig, null, 2));
67+
tree.create(packageJsonPath, JSON.stringify(packageJson, null, 2));
68+
tree.create(tslintPath, JSON.stringify(tslintConfig, null, 2));
69+
});
70+
71+
it('should rename all previous rules', () => {
72+
tree = schematicRunner.runSchematic('migration-07', defaultOptions, tree);
73+
const tslint = JSON.parse(tree.readContent(tslintPath));
74+
for (const rule of renames) {
75+
expect(rule in tslint.rules).toBeTruthy(`Rule ${rule} not renamed`);
76+
}
77+
});
78+
79+
it('should update codelyzer\'s version', () => {
80+
tree = schematicRunner.runSchematic('migration-07', defaultOptions, tree);
81+
const packageJson = JSON.parse(tree.readContent(packageJsonPath));
82+
expect(packageJson.devDependencies.codelyzer).toBe('^5.0.0');
83+
});
84+
});
85+
});

scripts/test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ export default function (args: ParsedArgs, logger: logging.Logger) {
213213
logger.info(`Found ${tests.length} spec files, out of ${allTests.length}.`);
214214
}
215215

216+
tests = tests.filter(e => e.endsWith('update-8/index_spec.ts'));
217+
216218
if (args.shard !== undefined) {
217219
// Remove tests that are not part of this shard.
218220
const shardId = args['shard'];

0 commit comments

Comments
 (0)