Skip to content

Commit c6c3e88

Browse files
author
Josh Goldberg
authored
Account for TSLint and @typescript-eslint rulesets (#276)
* WIP * Fixed tsc failures and existing or new unit tests * Brought coverage back up to 100%
1 parent 428b2f9 commit c6c3e88

18 files changed

+250
-48
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
[![Join the chat at https://gitter.im/tslint-to-eslint-config/community](https://img.shields.io/badge/chat-gitter-informational.svg)](https://gitter.im/tslint-to-eslint-config/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
99
[![Code Style: Prettier](https://img.shields.io/badge/speed-blazingly_fast-blueviolet.svg)](https://prettier.io)
1010

11-
Converts your TSLint configuration to the closest possible ESLint equivalent.
11+
Converts your TSLint configuration to the closest reasonable ESLint equivalent.
1212

1313
👉 Did you know [TSLint is being deprecated this year](https://github.com/palantir/tslint/issues/4534)?
1414
Hooray!

docs/Architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Within `src/conversion/convertConfig.ts`, the following steps occur:
1313

1414
1. Existing configurations are read from disk
1515
2. TSLint rules are converted into their ESLint configurations
16-
3. ESLint configurations are simplified based on extended ESLint presets
16+
3. ESLint configurations are simplified based on extended ESLint and TSLint presets
1717
4. The simplified configuration is written to the output config file
1818
5. A summary of the results is printed to the user's console
1919

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"bugs": {
66
"url": "https://github.com/typescript-eslint/tslint-to-eslint-config/issues"
77
},
8-
"description": "Converts your TSLint configuration to the closest possible ESLint equivalent.",
8+
"description": "Converts your TSLint configuration to the closest reasonable ESLint equivalent.",
99
"dependencies": {
1010
"chalk": "3.0.0",
1111
"commander": "4.0.1",

src/conversion/convertConfig.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ const createStubDependencies = (
1414
status: ResultStatus.Succeeded,
1515
}),
1616
reportConversionResults: jest.fn(),
17-
simplifyPackageRules: async (_configurations, data) => data,
17+
simplifyPackageRules: async (_configurations, data) => ({
18+
...data,
19+
converted: new Map(),
20+
failed: [],
21+
}),
1822
writeConversionResults: jest.fn().mockReturnValue(Promise.resolve()),
1923
...overrides,
2024
});

src/conversion/convertConfig.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ export const convertConfig = async (
3333
originalConfigurations.data.tslint.full.rules,
3434
);
3535

36-
// 3. ESLint configurations are simplified based on extended ESLint presets
36+
// 3. ESLint configurations are simplified based on extended ESLint and TSLint presets
3737
const simplifiedConfiguration = {
3838
...ruleConversionResults,
3939
...(await dependencies.simplifyPackageRules(
4040
originalConfigurations.data.eslint,
41+
originalConfigurations.data.tslint,
4142
ruleConversionResults,
4243
)),
4344
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { collectTSLintRulesets } from "./collectTSLintRulesets";
2+
3+
describe("collectTSLintRulesets", () => {
4+
it("includes mapped ESLint extensions for a full TSLint extension when it exists", () => {
5+
const tslint = {
6+
full: {
7+
extends: ["tslint:recommended"],
8+
},
9+
raw: {},
10+
};
11+
12+
const extensions = collectTSLintRulesets(tslint);
13+
14+
expect(extensions).toEqual([
15+
"plugin:@typescript-eslint/recommended",
16+
"plugin:@typescript-eslint/recommended-requiring-type-checking",
17+
]);
18+
});
19+
20+
it("includes mapped ESLint extensions for a raw TSLint extension when it exists", () => {
21+
const tslint = {
22+
full: {},
23+
raw: {
24+
extends: ["tslint:recommended"],
25+
},
26+
};
27+
28+
const extensions = collectTSLintRulesets(tslint);
29+
30+
expect(extensions).toEqual([
31+
"plugin:@typescript-eslint/recommended",
32+
"plugin:@typescript-eslint/recommended-requiring-type-checking",
33+
]);
34+
});
35+
36+
it("ignores a TSLint extension when it has no mapped ESLint extensions", () => {
37+
const tslint = {
38+
full: {
39+
extends: ["does not exist"],
40+
},
41+
raw: {
42+
extends: ["also does not exist"],
43+
},
44+
};
45+
46+
const extensions = collectTSLintRulesets(tslint);
47+
48+
expect(extensions).toEqual([]);
49+
});
50+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { OriginalConfigurations } from "../../input/findOriginalConfigurations";
2+
import { TSLintConfiguration } from "../../input/findTSLintConfiguration";
3+
import { uniqueFromSources } from "../../utils";
4+
5+
const nativeExtensions = new Map([
6+
["tslint:all", ["plugin:@typescript-eslint/all"]],
7+
[
8+
"tslint:recommended",
9+
[
10+
"plugin:@typescript-eslint/recommended",
11+
"plugin:@typescript-eslint/recommended-requiring-type-checking",
12+
],
13+
],
14+
]);
15+
16+
export const collectTSLintRulesets = (
17+
tslint: OriginalConfigurations<Pick<TSLintConfiguration, "extends">>,
18+
) => {
19+
const allExtensions = uniqueFromSources(tslint.full.extends, tslint.raw.extends);
20+
21+
const extensions = new Set<string>();
22+
23+
for (const extension of allExtensions) {
24+
const mappedExtensions = nativeExtensions.get(extension);
25+
26+
if (mappedExtensions !== undefined) {
27+
for (const mappedExtension of mappedExtensions) {
28+
extensions.add(mappedExtension);
29+
}
30+
}
31+
}
32+
33+
return Array.from(extensions);
34+
};

src/creation/simplification/resolveExtensionNames.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ describe("resolveExtensionNames", () => {
5555
// Assert
5656
expect(extensionNames).toEqual(["eslint-plugin-value"]);
5757
});
58+
5859
it("doesn't prepend eslint-plugin- when a plugin starts with eslint:", () => {
5960
// Arrange
6061
const rawExtensionName = "eslint:recommended";
@@ -65,4 +66,15 @@ describe("resolveExtensionNames", () => {
6566
// Assert
6667
expect(extensionNames).toEqual(["eslint:recommended"]);
6768
});
69+
70+
it("doesn't prepend eslint-plugin- when a plugin starts with plugin:", () => {
71+
// Arrange
72+
const rawExtensionName = "eslint:recommended";
73+
74+
// Act
75+
const extensionNames = resolveExtensionNames(rawExtensionName);
76+
77+
// Assert
78+
expect(extensionNames).toEqual(["eslint:recommended"]);
79+
});
6880
});

src/creation/simplification/resolveExtensionNames.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ export const resolveExtensionNames = (rawExtensionNames: string | string[]) => {
66
return rawExtensionNames.map(rawExtensionName =>
77
rawExtensionName.startsWith(".") ||
88
rawExtensionName.startsWith("eslint-plugin-") ||
9-
rawExtensionName.startsWith("eslint:")
9+
rawExtensionName.startsWith("eslint:") ||
10+
rawExtensionName.startsWith("plugin:")
1011
? rawExtensionName
1112
: `eslint-plugin-${rawExtensionName}`,
1213
);

src/creation/simplification/retrieveExtendsValues.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { retrieveExtendsValues } from "./retrieveExtendsValues";
22

33
describe("retrieveExtendsValues", () => {
4-
it("retrieves eslint-all when an extension is named eslint:all", async () => {
4+
it("retrieves an equivalent ESLint configuration when a retrieved extensions is an ESLint builtin", async () => {
55
// Arrange
66
const eslintAll = { rules: {} };
77
const importer = async (extensionName: string) =>
@@ -16,22 +16,22 @@ describe("retrieveExtendsValues", () => {
1616
expect(importedExtensions).toEqual([eslintAll]);
1717
});
1818

19-
it("retrieves eslint-recommended when an extension is named eslint:recommended", async () => {
19+
it("retrieves an equivalent typescript-eslint configuration when a retrieved extensions is a typescript-eslint builtin", async () => {
2020
// Arrange
21-
const eslintRecommended = { rules: {} };
21+
const eslintAll = { rules: {} };
2222
const importer = async (extensionName: string) =>
23-
extensionName === "eslint/conf/eslint-recommended"
24-
? eslintRecommended
23+
extensionName === "node_modules/@typescript-eslint/eslint-plugin/dist/configs/all.json"
24+
? eslintAll
2525
: new Error(`Unknown extension name: '${extensionName}`);
2626

2727
// Act
2828
const { importedExtensions } = await retrieveExtendsValues(
2929
{ importer },
30-
"eslint:recommended",
30+
"plugin:@typescript-eslint/all",
3131
);
3232

3333
// Assert
34-
expect(importedExtensions).toEqual([eslintRecommended]);
34+
expect(importedExtensions).toEqual([eslintAll]);
3535
});
3636

3737
it("reports a failure when an extension fails to import", async () => {

src/creation/simplification/retrieveExtendsValues.ts

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,29 @@ export type RetrievedExtensionValues = {
1313
importedExtensions: Partial<ESLintConfiguration>[];
1414
};
1515

16+
const builtInExtensions = new Map([
17+
["eslint:all", "eslint/conf/eslint-all"],
18+
["eslint:recommended", "eslint/conf/eslint-recommended"],
19+
]);
20+
21+
const typescriptPluginExtensions = new Map([
22+
[
23+
"plugin:@typescript-eslint/all",
24+
"node_modules/@typescript-eslint/eslint-plugin/dist/configs/all.json",
25+
],
26+
[
27+
"plugin:@typescript-eslint/recommended",
28+
"node_modules/@typescript-eslint/eslint-plugin/dist/configs/recommended.json",
29+
],
30+
[
31+
"plugin:@typescript-eslint/recommended-requiring-type-checking",
32+
"node_modules/@typescript-eslint/eslint-plugin/dist/configs/recommended-requiring-type-checking.json",
33+
],
34+
]);
35+
36+
/**
37+
* Imports any extended ESLint rulesets as ESLint configurations.
38+
*/
1639
export const retrieveExtendsValues = async (
1740
dependencies: RetrieveExtendsValuesDependencies,
1841
rawExtensionNames: string | string[],
@@ -21,26 +44,24 @@ export const retrieveExtendsValues = async (
2144
const configurationErrors: ConfigurationError[] = [];
2245
const extensionNames = resolveExtensionNames(rawExtensionNames);
2346

24-
const builtInExtensionGetters = new Map<string, () => Promise<ESLintConfiguration>>([
25-
[
26-
"eslint:all",
27-
async () =>
28-
(await dependencies.importer("eslint/conf/eslint-all")) as ESLintConfiguration,
29-
],
30-
[
31-
"eslint:recommended",
32-
async () =>
33-
(await dependencies.importer(
34-
"eslint/conf/eslint-recommended",
35-
)) as ESLintConfiguration,
36-
],
37-
]);
38-
3947
await Promise.all(
4048
extensionNames.map(async extensionName => {
41-
const getBuiltInExtension = builtInExtensionGetters.get(extensionName);
42-
if (getBuiltInExtension !== undefined) {
43-
importedExtensions.push(await getBuiltInExtension());
49+
const builtInExtension = builtInExtensions.get(extensionName);
50+
if (builtInExtension !== undefined) {
51+
importedExtensions.push(
52+
(await dependencies.importer(builtInExtension)) as ESLintConfiguration,
53+
);
54+
return;
55+
}
56+
57+
const typescriptPluginExtension = typescriptPluginExtensions.get(extensionName);
58+
if (typescriptPluginExtension !== undefined) {
59+
const importedTypeScriptPlugin = (await dependencies.importer(
60+
typescriptPluginExtension,
61+
)) as ESLintConfiguration;
62+
importedExtensions.push({
63+
rules: importedTypeScriptPlugin.rules,
64+
});
4465
return;
4566
}
4667

src/creation/simplification/simplifyPackageRules.test.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,53 +5,65 @@ import { simplifyPackageRules } from "./simplifyPackageRules";
55

66
const createStubDependencies = () => ({
77
removeExtendsDuplicatedRules: jest.fn(),
8-
retrieveExtendsValues: jest.fn(),
8+
retrieveExtendsValues: async () => ({
9+
configurationErrors: [],
10+
importedExtensions: [],
11+
}),
912
});
1013

11-
const createStubESLintConfiguration = (fullExtends: string | string[]) => ({
14+
const createStubESLintConfiguration = (fullExtends: string[]) => ({
1215
full: {
1316
env: {},
1417
extends: fullExtends,
1518
rules: {},
1619
},
1720
});
1821

22+
const createStubTSLintConfiguration = () => ({
23+
full: {},
24+
raw: {},
25+
});
26+
1927
describe("simplifyPackageRules", () => {
20-
it("returns the conversion results directly when there is no loaded eslint configuration", async () => {
28+
it("returns the conversion results directly when there is no loaded ESLint configuration and no TSLint extensions", async () => {
2129
// Arrange
2230
const dependencies = createStubDependencies();
2331
const eslint = undefined;
32+
const tslint = createStubTSLintConfiguration();
2433
const ruleConversionResults = createEmptyConversionResults();
2534

2635
// Act
2736
const simplifiedResults = await simplifyPackageRules(
2837
dependencies,
2938
eslint,
39+
tslint,
3040
ruleConversionResults,
3141
);
3242

3343
// Assert
3444
expect(simplifiedResults).toBe(ruleConversionResults);
3545
});
3646

37-
it("returns the conversion results directly when the eslint configuration has an empty extends", async () => {
47+
it("returns the conversion results directly when there is an empty ESLint configuration and no TSLint extensions", async () => {
3848
// Arrange
3949
const dependencies = createStubDependencies();
4050
const eslint = createStubESLintConfiguration([]);
51+
const tslint = createStubTSLintConfiguration();
4152
const ruleConversionResults = createEmptyConversionResults();
4253

4354
// Act
4455
const simplifiedResults = await simplifyPackageRules(
4556
dependencies,
4657
eslint,
58+
tslint,
4759
ruleConversionResults,
4860
);
4961

5062
// Assert
5163
expect(simplifiedResults).toBe(ruleConversionResults);
5264
});
5365

54-
it("includes deduplicated rules and extension failures when the eslint configuration extends", async () => {
66+
it("includes deduplicated rules and extension failures when the ESLint configuration extends", async () => {
5567
// Arrange
5668
const configurationErrors = [new ConfigurationError(new Error("oh no"), "darn")];
5769
const deduplicatedRules = new Map<string, ESLintRuleOptions>([
@@ -71,19 +83,23 @@ describe("simplifyPackageRules", () => {
7183
importedExtensions: [],
7284
}),
7385
};
74-
const eslint = createStubESLintConfiguration(["extension-name"]);
86+
const eslintExtends = ["extension-name"];
87+
const eslint = createStubESLintConfiguration(eslintExtends);
88+
const tslint = createStubTSLintConfiguration();
7589
const ruleConversionResults = createEmptyConversionResults();
7690

7791
// Act
7892
const simplifiedResults = await simplifyPackageRules(
7993
dependencies,
8094
eslint,
95+
tslint,
8196
ruleConversionResults,
8297
);
8398

8499
// Assert
85100
expect(simplifiedResults).toEqual({
86101
converted: deduplicatedRules,
102+
extends: eslintExtends,
87103
failed: configurationErrors,
88104
});
89105
});

0 commit comments

Comments
 (0)