Skip to content

Account for TSLint and @typescript-eslint rulesets #276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 23, 2019
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[![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)
[![Code Style: Prettier](https://img.shields.io/badge/speed-blazingly_fast-blueviolet.svg)](https://prettier.io)

Converts your TSLint configuration to the closest possible ESLint equivalent.
Converts your TSLint configuration to the closest reasonable ESLint equivalent.

👉 Did you know [TSLint is being deprecated this year](https://github.com/palantir/tslint/issues/4534)?
Hooray!
Expand Down
2 changes: 1 addition & 1 deletion docs/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Within `src/conversion/convertConfig.ts`, the following steps occur:

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

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"bugs": {
"url": "https://github.com/typescript-eslint/tslint-to-eslint-config/issues"
},
"description": "Converts your TSLint configuration to the closest possible ESLint equivalent.",
"description": "Converts your TSLint configuration to the closest reasonable ESLint equivalent.",
"dependencies": {
"chalk": "3.0.0",
"commander": "4.0.1",
Expand Down
6 changes: 5 additions & 1 deletion src/conversion/convertConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ const createStubDependencies = (
status: ResultStatus.Succeeded,
}),
reportConversionResults: jest.fn(),
simplifyPackageRules: async (_configurations, data) => data,
simplifyPackageRules: async (_configurations, data) => ({
...data,
converted: new Map(),
failed: [],
}),
writeConversionResults: jest.fn().mockReturnValue(Promise.resolve()),
...overrides,
});
Expand Down
3 changes: 2 additions & 1 deletion src/conversion/convertConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ export const convertConfig = async (
originalConfigurations.data.tslint.full.rules,
);

// 3. ESLint configurations are simplified based on extended ESLint presets
// 3. ESLint configurations are simplified based on extended ESLint and TSLint presets
const simplifiedConfiguration = {
...ruleConversionResults,
...(await dependencies.simplifyPackageRules(
originalConfigurations.data.eslint,
originalConfigurations.data.tslint,
ruleConversionResults,
)),
};
Expand Down
50 changes: 50 additions & 0 deletions src/creation/simplification/collectTSLintRulesets.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { collectTSLintRulesets } from "./collectTSLintRulesets";

describe("collectTSLintRulesets", () => {
it("includes mapped ESLint extensions for a full TSLint extension when it exists", () => {
const tslint = {
full: {
extends: ["tslint:recommended"],
},
raw: {},
};

const extensions = collectTSLintRulesets(tslint);

expect(extensions).toEqual([
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
]);
});

it("includes mapped ESLint extensions for a raw TSLint extension when it exists", () => {
const tslint = {
full: {},
raw: {
extends: ["tslint:recommended"],
},
};

const extensions = collectTSLintRulesets(tslint);

expect(extensions).toEqual([
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
]);
});

it("ignores a TSLint extension when it has no mapped ESLint extensions", () => {
const tslint = {
full: {
extends: ["does not exist"],
},
raw: {
extends: ["also does not exist"],
},
};

const extensions = collectTSLintRulesets(tslint);

expect(extensions).toEqual([]);
});
});
34 changes: 34 additions & 0 deletions src/creation/simplification/collectTSLintRulesets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { OriginalConfigurations } from "../../input/findOriginalConfigurations";
import { TSLintConfiguration } from "../../input/findTSLintConfiguration";
import { uniqueFromSources } from "../../utils";

const nativeExtensions = new Map([
["tslint:all", ["plugin:@typescript-eslint/all"]],
[
"tslint:recommended",
[
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
],
],
]);

export const collectTSLintRulesets = (
tslint: OriginalConfigurations<Pick<TSLintConfiguration, "extends">>,
) => {
const allExtensions = uniqueFromSources(tslint.full.extends, tslint.raw.extends);

const extensions = new Set<string>();

for (const extension of allExtensions) {
const mappedExtensions = nativeExtensions.get(extension);

if (mappedExtensions !== undefined) {
for (const mappedExtension of mappedExtensions) {
extensions.add(mappedExtension);
}
}
}

return Array.from(extensions);
};
12 changes: 12 additions & 0 deletions src/creation/simplification/resolveExtensionNames.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe("resolveExtensionNames", () => {
// Assert
expect(extensionNames).toEqual(["eslint-plugin-value"]);
});

it("doesn't prepend eslint-plugin- when a plugin starts with eslint:", () => {
// Arrange
const rawExtensionName = "eslint:recommended";
Expand All @@ -65,4 +66,15 @@ describe("resolveExtensionNames", () => {
// Assert
expect(extensionNames).toEqual(["eslint:recommended"]);
});

it("doesn't prepend eslint-plugin- when a plugin starts with plugin:", () => {
// Arrange
const rawExtensionName = "eslint:recommended";

// Act
const extensionNames = resolveExtensionNames(rawExtensionName);

// Assert
expect(extensionNames).toEqual(["eslint:recommended"]);
});
});
3 changes: 2 additions & 1 deletion src/creation/simplification/resolveExtensionNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export const resolveExtensionNames = (rawExtensionNames: string | string[]) => {
return rawExtensionNames.map(rawExtensionName =>
rawExtensionName.startsWith(".") ||
rawExtensionName.startsWith("eslint-plugin-") ||
rawExtensionName.startsWith("eslint:")
rawExtensionName.startsWith("eslint:") ||
rawExtensionName.startsWith("plugin:")
? rawExtensionName
: `eslint-plugin-${rawExtensionName}`,
);
Expand Down
14 changes: 7 additions & 7 deletions src/creation/simplification/retrieveExtendsValues.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { retrieveExtendsValues } from "./retrieveExtendsValues";

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

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

// Act
const { importedExtensions } = await retrieveExtendsValues(
{ importer },
"eslint:recommended",
"plugin:@typescript-eslint/all",
);

// Assert
expect(importedExtensions).toEqual([eslintRecommended]);
expect(importedExtensions).toEqual([eslintAll]);
});

it("reports a failure when an extension fails to import", async () => {
Expand Down
57 changes: 39 additions & 18 deletions src/creation/simplification/retrieveExtendsValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,29 @@ export type RetrievedExtensionValues = {
importedExtensions: Partial<ESLintConfiguration>[];
};

const builtInExtensions = new Map([
["eslint:all", "eslint/conf/eslint-all"],
["eslint:recommended", "eslint/conf/eslint-recommended"],
]);

const typescriptPluginExtensions = new Map([
[
"plugin:@typescript-eslint/all",
"node_modules/@typescript-eslint/eslint-plugin/dist/configs/all.json",
],
[
"plugin:@typescript-eslint/recommended",
"node_modules/@typescript-eslint/eslint-plugin/dist/configs/recommended.json",
],
[
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"node_modules/@typescript-eslint/eslint-plugin/dist/configs/recommended-requiring-type-checking.json",
],
]);

/**
* Imports any extended ESLint rulesets as ESLint configurations.
*/
export const retrieveExtendsValues = async (
dependencies: RetrieveExtendsValuesDependencies,
rawExtensionNames: string | string[],
Expand All @@ -21,26 +44,24 @@ export const retrieveExtendsValues = async (
const configurationErrors: ConfigurationError[] = [];
const extensionNames = resolveExtensionNames(rawExtensionNames);

const builtInExtensionGetters = new Map<string, () => Promise<ESLintConfiguration>>([
[
"eslint:all",
async () =>
(await dependencies.importer("eslint/conf/eslint-all")) as ESLintConfiguration,
],
[
"eslint:recommended",
async () =>
(await dependencies.importer(
"eslint/conf/eslint-recommended",
)) as ESLintConfiguration,
],
]);

await Promise.all(
extensionNames.map(async extensionName => {
const getBuiltInExtension = builtInExtensionGetters.get(extensionName);
if (getBuiltInExtension !== undefined) {
importedExtensions.push(await getBuiltInExtension());
const builtInExtension = builtInExtensions.get(extensionName);
if (builtInExtension !== undefined) {
importedExtensions.push(
(await dependencies.importer(builtInExtension)) as ESLintConfiguration,
);
return;
}

const typescriptPluginExtension = typescriptPluginExtensions.get(extensionName);
if (typescriptPluginExtension !== undefined) {
const importedTypeScriptPlugin = (await dependencies.importer(
typescriptPluginExtension,
)) as ESLintConfiguration;
importedExtensions.push({
rules: importedTypeScriptPlugin.rules,
});
return;
}

Expand Down
28 changes: 22 additions & 6 deletions src/creation/simplification/simplifyPackageRules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,65 @@ import { simplifyPackageRules } from "./simplifyPackageRules";

const createStubDependencies = () => ({
removeExtendsDuplicatedRules: jest.fn(),
retrieveExtendsValues: jest.fn(),
retrieveExtendsValues: async () => ({
configurationErrors: [],
importedExtensions: [],
}),
});

const createStubESLintConfiguration = (fullExtends: string | string[]) => ({
const createStubESLintConfiguration = (fullExtends: string[]) => ({
full: {
env: {},
extends: fullExtends,
rules: {},
},
});

const createStubTSLintConfiguration = () => ({
full: {},
raw: {},
});

describe("simplifyPackageRules", () => {
it("returns the conversion results directly when there is no loaded eslint configuration", async () => {
it("returns the conversion results directly when there is no loaded ESLint configuration and no TSLint extensions", async () => {
// Arrange
const dependencies = createStubDependencies();
const eslint = undefined;
const tslint = createStubTSLintConfiguration();
const ruleConversionResults = createEmptyConversionResults();

// Act
const simplifiedResults = await simplifyPackageRules(
dependencies,
eslint,
tslint,
ruleConversionResults,
);

// Assert
expect(simplifiedResults).toBe(ruleConversionResults);
});

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

// Act
const simplifiedResults = await simplifyPackageRules(
dependencies,
eslint,
tslint,
ruleConversionResults,
);

// Assert
expect(simplifiedResults).toBe(ruleConversionResults);
});

it("includes deduplicated rules and extension failures when the eslint configuration extends", async () => {
it("includes deduplicated rules and extension failures when the ESLint configuration extends", async () => {
// Arrange
const configurationErrors = [new ConfigurationError(new Error("oh no"), "darn")];
const deduplicatedRules = new Map<string, ESLintRuleOptions>([
Expand All @@ -71,19 +83,23 @@ describe("simplifyPackageRules", () => {
importedExtensions: [],
}),
};
const eslint = createStubESLintConfiguration(["extension-name"]);
const eslintExtends = ["extension-name"];
const eslint = createStubESLintConfiguration(eslintExtends);
const tslint = createStubTSLintConfiguration();
const ruleConversionResults = createEmptyConversionResults();

// Act
const simplifiedResults = await simplifyPackageRules(
dependencies,
eslint,
tslint,
ruleConversionResults,
);

// Assert
expect(simplifiedResults).toEqual({
converted: deduplicatedRules,
extends: eslintExtends,
failed: configurationErrors,
});
});
Expand Down
Loading