Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/schematics/angular/application/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ describe('Application Schematic', () => {
const content = JSON.parse(tree.readContent('/tslint.json'));
expect(content.extends).toMatch('tslint:recommended');
expect(content.rules['component-selector'][2]).toMatch('app');
expect(content.rules['trailing-comma']).toBeDefined();
expect(content.rules['no-console']).toBeDefined();
});

it(`should create correct paths when 'newProjectRoot' is blank`, async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
"version": "9.0.2",
"factory": "./update-9/schematic-options",
"description": "Replace deprecated 'styleext' and 'spec' Angular schematic options."
},
"tslint-version-6": {
"version": "10.0.0-beta.0",
"factory": "./update-10/update-tslint",
"description": "Update tslint to version 6."
}
}
}
156 changes: 156 additions & 0 deletions packages/schematics/angular/migrations/update-10/update-tslint.ts
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 packages/schematics/angular/migrations/update-10/update-tslint_spec.ts
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();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"karma-jasmine-html-reporter": "^1.4.2",
"protractor": "~5.4.3",<% } %>
"ts-node": "~8.3.0",
"tslint": "~5.18.0",
"tslint": "~6.1.0",
"typescript": "<%= latestVersions.TypeScript %>"
}
}
Loading