Skip to content
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
*.tsbuildinfo
*.d.ts
!src/typings/**/*.d.ts
*.log
*.map
coverage/
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Each of these flags is optional:
- **[`editor`](#editor)**: Path to an editor configuration file to convert linter settings within.
- **[`eslint`](#eslint)**: Path to an ESLint configuration file to read settings from.
- **[`package`](#package)**: Path to a package.json file to read dependencies from.
- **[`prettier`](#prettier)**: Add `eslint-config-prettier` to the plugins list.
- **[`tslint`](#tslint)**: Path to a TSLint configuration file to read settings from.
- **[`typescript`](#typescript)**: Path to a TypeScript configuration file to read TypeScript compiler options from.

Expand Down Expand Up @@ -97,6 +98,22 @@ _Default: `package.json`_
Path to a `package.json` file to read dependencies from.
This will help inform the generated ESLint configuration file's [env](https://eslint.org/docs/user-guide/configuring#specifying-parser-options) settings.

#### `prettier`

```shell
npx tslint-to-eslint-config --prettier
```

_Default: `false`_

Add [`eslint-config-prettier`](https://github.com/prettier/eslint-config-prettier) to the list of ESLint plugins.
We [highly recommend](./docs/FAQs.md#should-i-use-prettier) you use [Prettier](http://prettier.io) for code formatting.

When `--prettier` isn't enabled:

- If the output configuration already doesn't enable any formatting rules, it'll extend from `eslint-config-prettier`.
- Otherwise, a CLI message will suggest running with `--prettier`.

#### `tslint`

```shell
Expand Down
1 change: 1 addition & 0 deletions docs/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,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 and TSLint presets
- 3a. If no output rules conflict with `eslint-config-prettier`, it's added in
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
4 changes: 1 addition & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dependencies": {
"chalk": "4.0.0",
"commander": "5.1.0",
"eslint-config-prettier": "^6.11.0",
"strip-json-comments": "3.1.0",
"tslint": "6.1.1",
"typescript": "3.8.3"
Expand All @@ -28,7 +29,6 @@
"@typescript-eslint/parser": "2.29.0",
"babel-jest": "25.4.0",
"eslint": "6.8.0",
"eslint-config-prettier": "6.11.0",
"husky": "4.2.5",
"jest": "25.4.0",
"lint-staged": "10.1.7",
Expand Down
2 changes: 2 additions & 0 deletions src/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
convertEditorConfig,
ConvertEditorConfigDependencies,
} from "../conversion/convertEditorConfig";
import { addPrettierExtensions } from "../creation/simplification/prettier/addPrettierExtensions";
import { removeExtendsDuplicatedRules } from "../creation/simplification/removeExtendsDuplicatedRules";
import {
retrieveExtendsValues,
Expand Down Expand Up @@ -103,6 +104,7 @@ const retrieveExtendsValuesDependencies: RetrieveExtendsValuesDependencies = {
};

const simplifyPackageRulesDependencies: SimplifyPackageRulesDependencies = {
addPrettierExtensions,
removeExtendsDuplicatedRules,
retrieveExtendsValues: bind(retrieveExtendsValues, retrieveExtendsValuesDependencies),
};
Expand Down
3 changes: 2 additions & 1 deletion src/cli/runCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ export const runCli = async (
const command = new Command()
.usage("[options] <file ...> --language [language]")
.option("--config [config]", "eslint configuration file to output to")
.option("--editor [editor]", "editor configuration file to convert")
.option("--eslint [eslint]", "eslint configuration file to convert using")
.option("--package [package]", "package configuration file to convert using")
.option("--prettier [prettier]", "add eslint-config-prettier to the plugins list")
.option("--tslint [tslint]", "tslint configuration file to convert using")
.option("--typescript [typescript]", "typescript configuration file to convert using")
.option("--editor [editor]", "editor configuration file to convert")
.option("-V --version", "output the package version");

const parsedArgv = {
Expand Down
6 changes: 3 additions & 3 deletions src/conversion/conversionResults.stubs.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { EditorSettingConversionResults } from "../editorSettings/convertEditorSettings";
import { RuleConversionResults } from "../rules/convertRules";
import { SimplifiedResultsConfiguration } from "../creation/simplification/simplifyPackageRules";

export const createEmptyConversionResults = (
overrides: Partial<RuleConversionResults> = {},
): RuleConversionResults => ({
overrides: Partial<SimplifiedResultsConfiguration> = {},
): SimplifiedResultsConfiguration => ({
converted: new Map(),
failed: [],
missing: [],
Expand Down
3 changes: 3 additions & 0 deletions src/conversion/convertConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ const createStubDependencies = (
simplifyPackageRules: async (_configurations, data) => ({
...data,
converted: new Map(),
extends: [],
failed: [],
missing: [],
plugins: new Set(),
}),
writeConversionResults: jest.fn().mockReturnValue(Promise.resolve()),
...overrides,
Expand Down
14 changes: 6 additions & 8 deletions src/conversion/convertConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,12 @@ export const convertConfig = async (
);

// 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,
)),
};
const simplifiedConfiguration = await dependencies.simplifyPackageRules(
originalConfigurations.data.eslint,
originalConfigurations.data.tslint,
ruleConversionResults,
settings.prettier,
);

// 4. The simplified configuration is written to the output config file
const fileWriteError = await dependencies.writeConversionResults(
Expand Down
59 changes: 59 additions & 0 deletions src/creation/simplification/prettier/addPrettierExtensions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { createEmptyConversionResults } from "../../../conversion/conversionResults.stubs";
import { addPrettierExtensions } from "./addPrettierExtensions";

const createStubRuleConversions = (ruleName: string, ruleSeverity: "error" | "off") =>
new Map([[ruleName, { ruleName, ruleSeverity }]]);

describe("addPrettierExtensions", () => {
it("returns false when a matching converted rule is enabled", async () => {
// Arrange
const ruleConversionResults = createEmptyConversionResults({
converted: createStubRuleConversions("max-len", "error"),
});

// Act
const result = await addPrettierExtensions(ruleConversionResults);

// Assert
expect(result).toEqual(false);
});

it("returns true when a matching converted rule is disabled", async () => {
// Arrange
const ruleConversionResults = createEmptyConversionResults({
converted: createStubRuleConversions("max-len", "off"),
});

// Act
const result = await addPrettierExtensions(ruleConversionResults);

// Assert
expect(result).toEqual(true);
});

it("returns true when there are no matching converted rules", async () => {
// Arrange
const ruleConversionResults = createEmptyConversionResults({
converted: createStubRuleConversions("unknown", "error"),
});

// Act
const result = await addPrettierExtensions(ruleConversionResults);

// Assert
expect(result).toEqual(true);
});

it("returns true when prettier is requested", async () => {
// Arrange
const ruleConversionResults = createEmptyConversionResults({
converted: createStubRuleConversions("max-len", "off"),
});

// Act
const result = await addPrettierExtensions(ruleConversionResults, true);

// Assert
expect(result).toEqual(true);
});
});
21 changes: 21 additions & 0 deletions src/creation/simplification/prettier/addPrettierExtensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import prettierRuleSettings from "eslint-config-prettier";

import { RuleConversionResults } from "../../../rules/convertRules";

export const addPrettierExtensions = async (
ruleConversionResults: Pick<RuleConversionResults, "converted">,
prettierRequested?: boolean,
) => {
if (prettierRequested) {
return true;
}

for (const rule in prettierRuleSettings.rules) {
const convertedRule = ruleConversionResults.converted.get(rule);
if (convertedRule !== undefined && convertedRule.ruleSeverity !== "off") {
return false;
}
}

return true;
};
47 changes: 38 additions & 9 deletions src/creation/simplification/simplifyPackageRules.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { ConfigurationError } from "../../errors/configurationError";
import { ESLintRuleOptions } from "../../rules/types";
import { createEmptyConversionResults } from "../../conversion/conversionResults.stubs";
import { simplifyPackageRules } from "./simplifyPackageRules";
import { simplifyPackageRules, SimplifyPackageRulesDependencies } from "./simplifyPackageRules";

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

const createStubESLintConfiguration = (fullExtends: string[]) => ({
Expand All @@ -25,7 +27,7 @@ const createStubTSLintConfiguration = () => ({
});

describe("simplifyPackageRules", () => {
it("returns the conversion results directly when there is no loaded ESLint configuration and no TSLint extensions", async () => {
it("returns equivalent conversion results when there is no loaded ESLint configuration and no TSLint extensions", async () => {
// Arrange
const dependencies = createStubDependencies();
const eslint = undefined;
Expand All @@ -41,10 +43,36 @@ describe("simplifyPackageRules", () => {
);

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

it("returns the conversion results directly when there is an empty ESLint configuration and no TSLint extensions", async () => {
it("adds Prettier extensions when addPrettierExtensions returns true", async () => {
// Arrange
const dependencies = createStubDependencies({
addPrettierExtensions: async () => true,
});
const eslint = undefined;
const tslint = createStubTSLintConfiguration();
const ruleConversionResults = createEmptyConversionResults();

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

// Assert
expect(simplifiedResults).toEqual({
...ruleConversionResults,
converted: undefined,
extends: ["eslint-config-prettier", "eslint-config-prettier/@typescript-eslint"],
});
});

it("returns equivalent conversion results when there is an empty ESLint configuration and no TSLint extensions", async () => {
// Arrange
const dependencies = createStubDependencies();
const eslint = createStubESLintConfiguration([]);
Expand All @@ -60,7 +88,7 @@ describe("simplifyPackageRules", () => {
);

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

it("includes deduplicated rules and extension failures when the ESLint configuration extends", async () => {
Expand All @@ -76,13 +104,13 @@ describe("simplifyPackageRules", () => {
},
],
]);
const dependencies = {
const dependencies = createStubDependencies({
removeExtendsDuplicatedRules: () => deduplicatedRules,
retrieveExtendsValues: async () => ({
configurationErrors,
importedExtensions: [],
}),
};
});
const eslintExtends = ["extension-name"];
const eslint = createStubESLintConfiguration(eslintExtends);
const tslint = createStubTSLintConfiguration();
Expand All @@ -98,8 +126,9 @@ describe("simplifyPackageRules", () => {

// Assert
expect(simplifiedResults).toEqual({
...ruleConversionResults,
converted: deduplicatedRules,
extends: eslintExtends,
extends: [...eslintExtends],
failed: configurationErrors,
});
});
Expand Down
19 changes: 13 additions & 6 deletions src/creation/simplification/simplifyPackageRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@ import { TSLintConfiguration } from "../../input/findTSLintConfiguration";
import { RuleConversionResults } from "../../rules/convertRules";
import { uniqueFromSources } from "../../utils";
import { collectTSLintRulesets } from "./collectTSLintRulesets";
import { addPrettierExtensions } from "./prettier/addPrettierExtensions";
import { removeExtendsDuplicatedRules } from "./removeExtendsDuplicatedRules";
import { retrieveExtendsValues } from "./retrieveExtendsValues";

export type SimplifyPackageRulesDependencies = {
addPrettierExtensions: typeof addPrettierExtensions;
removeExtendsDuplicatedRules: typeof removeExtendsDuplicatedRules;
retrieveExtendsValues: SansDependencies<typeof retrieveExtendsValues>;
};

export type SimplifiedRuleConversionResults = Pick<
RuleConversionResults,
"converted" | "failed"
> & {
export type SimplifiedResultsConfiguration = RuleConversionResults & {
extends?: string[];
};

Expand All @@ -28,11 +27,18 @@ export const simplifyPackageRules = async (
dependencies: SimplifyPackageRulesDependencies,
eslint: Pick<OriginalConfigurations<ESLintConfiguration>, "full"> | undefined,
tslint: OriginalConfigurations<Pick<TSLintConfiguration, "extends">>,
ruleConversionResults: SimplifiedRuleConversionResults,
): Promise<SimplifiedRuleConversionResults> => {
ruleConversionResults: RuleConversionResults,
prettierRequested?: boolean,
): Promise<SimplifiedResultsConfiguration> => {
const extendedESLintRulesets = eslint?.full.extends ?? [];
const extendedTSLintRulesets = collectTSLintRulesets(tslint);
const allExtensions = uniqueFromSources(extendedESLintRulesets, extendedTSLintRulesets);

// 3a. If no output rules conflict with `eslint-config-prettier`, it's added in
if (await dependencies.addPrettierExtensions(ruleConversionResults, prettierRequested)) {
allExtensions.push("eslint-config-prettier", "eslint-config-prettier/@typescript-eslint");
}

if (allExtensions.length === 0) {
return ruleConversionResults;
}
Expand All @@ -47,6 +53,7 @@ export const simplifyPackageRules = async (
);

return {
...ruleConversionResults,
converted,
extends: allExtensions,
failed: [...ruleConversionResults.failed, ...configurationErrors],
Expand Down
Loading