-
Notifications
You must be signed in to change notification settings - Fork 12k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(@schematics/angular): add migration to update tslint to version 6
In tslint version 6, several recommanded rules have been removed in the following PRs: palantir/tslint#4871 palantir/tslint#4312 With this migration we update the tslint depedency to 6+ and add back these rules in the user workspace `tslint.json` also we remove some deprecated rules. Until version 10, this is an opt-in migration and users will need to run this manually with the below command: ``` ng update @angular/cli --migrate-only tslint-version-6 ``` Closes: #17117 Reference: TOOL-1348
- Loading branch information
1 parent
df4382f
commit a144e82
Showing
3 changed files
with
275 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
156 changes: 156 additions & 0 deletions
156
packages/schematics/angular/migrations/update-10/update-tslint.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
/** | ||
* @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 { JsonAstObject, JsonValue } from '@angular-devkit/core'; | ||
import { Rule } from '@angular-devkit/schematics'; | ||
import { addPackageJsonDependency, getPackageJsonDependency } from '../../utility/dependencies'; | ||
import { findPropertyInAstObject, insertPropertyInAstObjectInOrder, removePropertyInAstObject } from '../../utility/json-utils'; | ||
import { readJsonFileAsAstObject } from '../update-9/utils'; | ||
|
||
export const TSLINT_VERSION = '~6.1.0'; | ||
const TSLINT_CONFIG_PATH = '/tslint.json'; | ||
|
||
const RULES_TO_DELETE: string[] = [ | ||
'no-use-before-declare', | ||
'no-unused-variable', | ||
]; | ||
|
||
const RULES_TO_ADD: Record<string, JsonValue> = { | ||
align: { | ||
options: ['parameters', 'statements'], | ||
}, | ||
'arrow-return-shorthand': true, | ||
curly: true, | ||
eofline: true, | ||
'import-spacing': true, | ||
indent: { | ||
options: ['spaces'], | ||
}, | ||
'variable-name': { | ||
options: ['ban-keywords', 'check-format', 'allow-pascal-case'], | ||
}, | ||
semicolon: { options: ['always'] }, | ||
'space-before-function-paren': { | ||
options: { | ||
anonymous: 'never', | ||
asyncArrow: 'always', | ||
constructor: 'never', | ||
method: 'never', | ||
named: 'never', | ||
}, | ||
}, | ||
'typedef-whitespace': { | ||
options: [ | ||
{ | ||
'call-signature': 'nospace', | ||
'index-signature': 'nospace', | ||
parameter: 'nospace', | ||
'property-declaration': 'nospace', | ||
'variable-declaration': 'nospace', | ||
}, | ||
{ | ||
'call-signature': 'onespace', | ||
'index-signature': 'onespace', | ||
parameter: 'onespace', | ||
'property-declaration': 'onespace', | ||
'variable-declaration': 'onespace', | ||
}, | ||
], | ||
}, | ||
whitespace: { | ||
options: [ | ||
'check-branch', | ||
'check-decl', | ||
'check-operator', | ||
'check-separator', | ||
'check-type', | ||
'check-typecast', | ||
], | ||
}, | ||
}; | ||
|
||
export default function (): Rule { | ||
return (tree, context) => { | ||
const logger = context.logger; | ||
|
||
// Update tslint dependency | ||
const current = getPackageJsonDependency(tree, 'tslint'); | ||
|
||
if (!current) { | ||
logger.info('"tslint" in not a dependency of this workspace.'); | ||
|
||
return; | ||
} | ||
|
||
if (current.version !== TSLINT_VERSION) { | ||
addPackageJsonDependency(tree, { | ||
type: current.type, | ||
name: 'tslint', | ||
version: TSLINT_VERSION, | ||
overwrite: true, | ||
}); | ||
} | ||
|
||
// Update tslint config. | ||
const tslintJsonAst = readJsonFileAsAstObject(tree, TSLINT_CONFIG_PATH); | ||
if (!tslintJsonAst) { | ||
const config = ['tslint.js', 'tslint.yaml'].find(c => tree.exists(c)); | ||
if (config) { | ||
logger.warn(`Expected a JSON configuration file but found "${config}".`); | ||
} else { | ||
logger.warn('Cannot find "tslint.json" configuration file.'); | ||
} | ||
|
||
return; | ||
} | ||
|
||
// Remove old/deprecated rules. | ||
for (const rule of RULES_TO_DELETE) { | ||
const tslintJsonAst = readJsonFileAsAstObject(tree, TSLINT_CONFIG_PATH) as JsonAstObject; | ||
const rulesAst = findPropertyInAstObject(tslintJsonAst, 'rules'); | ||
if (rulesAst?.kind !== 'object') { | ||
break; | ||
} | ||
|
||
const recorder = tree.beginUpdate(TSLINT_CONFIG_PATH); | ||
removePropertyInAstObject(recorder, rulesAst, rule); | ||
tree.commitUpdate(recorder); | ||
} | ||
|
||
// Add new rules only iif the configuration extends 'tslint:recommended'. | ||
// This is because some rules conflict with prettier or other tools. | ||
const extendsAst = findPropertyInAstObject(tslintJsonAst, 'extends'); | ||
if ( | ||
!extendsAst || | ||
(extendsAst.kind === 'string' && extendsAst.value !== 'tslint:recommended') || | ||
(extendsAst.kind === 'array' && extendsAst.elements.some(e => e.value !== 'tslint:recommended')) | ||
) { | ||
logger.warn(`tslint configuration does not extend "tslint:recommended".` | ||
+ '\nMigration will terminate as some rules might conflict.'); | ||
|
||
return; | ||
} | ||
|
||
for (const [name, value] of Object.entries(RULES_TO_ADD)) { | ||
const tslintJsonAst = readJsonFileAsAstObject(tree, TSLINT_CONFIG_PATH) as JsonAstObject; | ||
const rulesAst = findPropertyInAstObject(tslintJsonAst, 'rules'); | ||
if (rulesAst?.kind !== 'object') { | ||
break; | ||
} | ||
|
||
if (findPropertyInAstObject(rulesAst, name)) { | ||
// Skip as rule already exists. | ||
continue; | ||
} | ||
|
||
const recorder = tree.beginUpdate(TSLINT_CONFIG_PATH); | ||
insertPropertyInAstObjectInOrder(recorder, rulesAst, name, value, 4); | ||
tree.commitUpdate(recorder); | ||
} | ||
|
||
}; | ||
} |
114 changes: 114 additions & 0 deletions
114
packages/schematics/angular/migrations/update-10/update-tslint_spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/** | ||
* @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 { EmptyTree } from '@angular-devkit/schematics'; | ||
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; | ||
import { TSLINT_VERSION } from './update-tslint'; | ||
|
||
describe('Migration of tslint to version 6', () => { | ||
const schematicRunner = new SchematicTestRunner( | ||
'migrations', | ||
require.resolve('../migration-collection.json'), | ||
); | ||
|
||
let tree: UnitTestTree; | ||
const TSLINT_PATH = '/tslint.json'; | ||
const PACKAGE_JSON_PATH = '/package.json'; | ||
|
||
const TSLINT_CONFIG = { | ||
extends: 'tslint:recommended', | ||
rules: { | ||
'no-use-before-declare': true, | ||
'arrow-return-shorthand': false, | ||
'label-position': true, | ||
}, | ||
}; | ||
|
||
const PACKAGE_JSON = { | ||
devDependencies: { | ||
tslint: '~5.1.0', | ||
}, | ||
}; | ||
|
||
beforeEach(() => { | ||
tree = new UnitTestTree(new EmptyTree()); | ||
tree.create(PACKAGE_JSON_PATH, JSON.stringify(PACKAGE_JSON, null, 2)); | ||
tree.create(TSLINT_PATH, JSON.stringify(TSLINT_CONFIG, null, 2)); | ||
}); | ||
|
||
it('should update tslint dependency', async () => { | ||
const newTree = await schematicRunner.runSchematicAsync('tslint-version-6', {}, tree).toPromise(); | ||
const packageJson = JSON.parse(newTree.readContent(PACKAGE_JSON_PATH)); | ||
expect(packageJson.devDependencies.tslint).toBe(TSLINT_VERSION); | ||
}); | ||
|
||
it('should remove old/deprecated rules', async () => { | ||
const newTree = await schematicRunner.runSchematicAsync('tslint-version-6', {}, tree).toPromise(); | ||
const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); | ||
expect(rules['no-use-before-declare']).toBeUndefined(); | ||
}); | ||
|
||
it('should add new rules', async () => { | ||
const newTree = await schematicRunner.runSchematicAsync('tslint-version-6', {}, tree).toPromise(); | ||
const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); | ||
expect(rules['eofline']).toBe(true); | ||
}); | ||
|
||
it('should not update already present rules', async () => { | ||
const newTree = await schematicRunner.runSchematicAsync('tslint-version-6', {}, tree).toPromise(); | ||
const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); | ||
expect(rules['arrow-return-shorthand']).toBe(false); | ||
}); | ||
|
||
it(`should not add new rules when not extending 'tslint:recommended'`, async () => { | ||
tree.overwrite( | ||
TSLINT_PATH, | ||
JSON.stringify({ | ||
...TSLINT_CONFIG, | ||
extends: 'tslint-config-prettier', | ||
}, null, 2), | ||
); | ||
|
||
const newTree = await schematicRunner.runSchematicAsync('tslint-version-6', {}, tree).toPromise(); | ||
const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); | ||
expect(rules['eofline']).toBeUndefined(); | ||
}); | ||
|
||
it(`should not add new rules when extending multiple configs`, async () => { | ||
tree.overwrite( | ||
TSLINT_PATH, | ||
JSON.stringify({ | ||
...TSLINT_CONFIG, | ||
extends: [ | ||
'tslint:recommended', | ||
'tslint-config-prettier', | ||
], | ||
}, null, 2), | ||
); | ||
|
||
const newTree = await schematicRunner.runSchematicAsync('tslint-version-6', {}, tree).toPromise(); | ||
const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); | ||
expect(rules['eofline']).toBeUndefined(); | ||
}); | ||
|
||
it(`should remove old/deprecated rules when extending multiple configs`, async () => { | ||
tree.overwrite( | ||
TSLINT_PATH, | ||
JSON.stringify({ | ||
...TSLINT_CONFIG, | ||
extends: [ | ||
'tslint:recommended', | ||
'tslint-config-prettier', | ||
], | ||
}, null, 2), | ||
); | ||
|
||
const newTree = await schematicRunner.runSchematicAsync('tslint-version-6', {}, tree).toPromise(); | ||
const { rules } = JSON.parse(newTree.readContent(TSLINT_PATH)); | ||
expect(rules['no-use-before-declare']).toBeUndefined(); | ||
}); | ||
}); |