diff --git a/README.md b/README.md index 18abc4c1..c3004e36 100644 --- a/README.md +++ b/README.md @@ -35,14 +35,35 @@ TSLint rules without ESLint equivalents will be wrapped with [eslint-plugin-tsli Each of these flags is optional. -#### `eslint` +- **[`config`](#config)**: Path to print the generated ESLint configuration file to. +- **[`eslint`](#eslint)**: Path to an ESLint configuration file to read settings from. +- **[`package`](#package)**: Path to a package.json file to read dependencies from. +- **[`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. + +#### `config` ```shell -npx tslint-to-eslint-config --eslint ./path/to/seslintrc.json +npx tslint-to-eslint-config --config .eslintrc.json ``` _Default: `.eslintrc.js`_ +Path to print the generated ESLint configuration file to. + +The file extension of this path will be used to determine the format of the created file: + +- `.js` file paths will be written `module.exports = ...` JavaScript +- Other file paths will default to JSON + +#### `eslint` + +```shell +npx tslint-to-eslint-config --eslint ./path/to/eslintrc.js +``` + +_Default: `--config`'s value_ + Path to an ESLint configuration file to read settings from. This isn't yet used for anything, but will eventually inform settings for the generated ESLint configuration file. @@ -76,7 +97,7 @@ npx tslint-to-eslint-config --typescript ./path/to/tsconfig.json _Default: `tsconfig.json`_ -Path to a `tsconfig.json` file to read TypeScript compiler options from. +Path to a TypeScript configuration file to read TypeScript compiler options from. This will help inform the generated ESLint configuration file's [env](https://eslint.org/docs/user-guide/configuring#specifying-parser-options) settings. ## Development diff --git a/src/cli/runCli.ts b/src/cli/runCli.ts index b457e931..b465fbe0 100644 --- a/src/cli/runCli.ts +++ b/src/cli/runCli.ts @@ -19,13 +19,17 @@ export const runCli = async ( ): Promise => { const command = new Command() .usage("[options] --language [language]") - .option("--eslint [eslint]", "eslint configuration file to convert") - .option("--package [package]", "package configuration file to convert") - .option("--tslint [tslint]", "tslint configuration file to convert") - .option("--typescript [typescript]", "typescript configuration file to convert") + .option("--config [config]", "eslint configuration file to output to") + .option("--eslint [eslint]", "eslint configuration file to convert using") + .option("--package [package]", "package configuration file to convert using") + .option("--tslint [tslint]", "tslint configuration file to convert using") + .option("--typescript [typescript]", "typescript configuration file to convert using") .option("-V --version", "output the package version"); - const parsedArgv = command.parse(rawArgv) as Partial; + const parsedArgv = { + config: "./eslintrc.js", + ...(command.parse(rawArgv) as Partial), + }; if ({}.hasOwnProperty.call(parsedArgv, "version")) { dependencies.logger.stdout.write(`${version}${EOL}`); diff --git a/src/conversion/convertConfig.test.ts b/src/conversion/convertConfig.test.ts index 8a8718f1..fb21682d 100644 --- a/src/conversion/convertConfig.test.ts +++ b/src/conversion/convertConfig.test.ts @@ -28,7 +28,9 @@ describe("convertConfig", () => { const dependencies = createStubDependencies({ findOriginalConfigurations: async () => findError, }); - const settings = {}; + const settings = { + config: "./eslintrc.js", + }; // Act const result = await convertConfig(dependencies, settings); @@ -46,7 +48,9 @@ describe("convertConfig", () => { const dependencies = createStubDependencies({ findOriginalConfigurations: async () => findSuccess, }); - const settings = {}; + const settings = { + config: "./eslintrc.js", + }; // Act const result = await convertConfig(dependencies, settings); diff --git a/src/conversion/convertConfig.ts b/src/conversion/convertConfig.ts index fc4046a3..6701fd21 100644 --- a/src/conversion/convertConfig.ts +++ b/src/conversion/convertConfig.ts @@ -25,7 +25,11 @@ export const convertConfig = async ( originalConfigurations.data.tslint.rules, ); - await dependencies.writeConversionResults(ruleConversionResults, originalConfigurations.data); + await dependencies.writeConversionResults( + settings.config, + ruleConversionResults, + originalConfigurations.data, + ); dependencies.reportConversionResults(ruleConversionResults); return { diff --git a/src/creation/formatting/formatOutput.test.ts b/src/creation/formatting/formatOutput.test.ts new file mode 100644 index 00000000..698ac73f --- /dev/null +++ b/src/creation/formatting/formatOutput.test.ts @@ -0,0 +1,54 @@ +import { formatOutput } from "./formatOutput"; +import { EOL } from "os"; + +describe("formatOutput", () => { + it("formats output as JavaScript for a .js file path", () => { + // Arrange + const outputPath = ".eslintrc.js"; + const configuration = { rules: {} }; + + // Act + const output = formatOutput(outputPath, configuration); + + // Assert + expect(output).toBe( + `module.exports = ${JSON.stringify(configuration, undefined, 4)};${EOL}`, + ); + }); + + it("formats output as JSON for a .json file path", () => { + // Arrange + const outputPath = ".eslintrc.json"; + const configuration = { rules: {} }; + + // Act + const output = formatOutput(outputPath, configuration); + + // Assert + expect(output).toBe(`${JSON.stringify(configuration, undefined, 4)}${EOL}`); + }); + + it("formats output as JSON for an unknown dot file path", () => { + // Arrange + const outputPath = ".eslintrc"; + const configuration = { rules: {} }; + + // Act + const output = formatOutput(outputPath, configuration); + + // Assert + expect(output).toBe(`${JSON.stringify(configuration, undefined, 4)}${EOL}`); + }); + + it("formats output as JSON for an unknown raw file path", () => { + // Arrange + const outputPath = "eslintrc"; + const configuration = { rules: {} }; + + // Act + const output = formatOutput(outputPath, configuration); + + // Assert + expect(output).toBe(`${JSON.stringify(configuration, undefined, 4)}${EOL}`); + }); +}); diff --git a/src/creation/formatting/formatOutput.ts b/src/creation/formatting/formatOutput.ts new file mode 100644 index 00000000..72667911 --- /dev/null +++ b/src/creation/formatting/formatOutput.ts @@ -0,0 +1,17 @@ +import { formatJsonOutput } from "./formatters/formatJsonOutput"; +import { formatJsOutput } from "./formatters/formatJsOutput"; + +const formatters = new Map([["js", formatJsOutput]]); + +export const formatOutput = (outputPath: string, configuration: unknown): string => { + const customFormatter = formatters.get(getExtension(outputPath)); + const formatter = customFormatter === undefined ? formatJsonOutput : formatJsOutput; + + return formatter(configuration); +}; + +const getExtension = (outputPath: string) => { + const periodIndex = outputPath.lastIndexOf("."); + + return periodIndex === -1 ? outputPath : outputPath.slice(periodIndex + 1); +}; diff --git a/src/creation/formatting/formatters/formatJsOutput.ts b/src/creation/formatting/formatters/formatJsOutput.ts new file mode 100644 index 00000000..d5e02087 --- /dev/null +++ b/src/creation/formatting/formatters/formatJsOutput.ts @@ -0,0 +1,4 @@ +import { EOL } from "os"; + +export const formatJsOutput = (configuration: unknown) => + `module.exports = ${JSON.stringify(configuration, undefined, 4)};${EOL}`; diff --git a/src/creation/formatting/formatters/formatJsonOutput.ts b/src/creation/formatting/formatters/formatJsonOutput.ts new file mode 100644 index 00000000..6aceb00e --- /dev/null +++ b/src/creation/formatting/formatters/formatJsonOutput.ts @@ -0,0 +1,4 @@ +import { EOL } from "os"; + +export const formatJsonOutput = (configuration: unknown) => + `${JSON.stringify(configuration, undefined, 4)}${EOL}`; diff --git a/src/creation/writeConversionResults.test.ts b/src/creation/writeConversionResults.test.ts index 65258c19..2da4e799 100644 --- a/src/creation/writeConversionResults.test.ts +++ b/src/creation/writeConversionResults.test.ts @@ -1,5 +1,6 @@ import { createEmptyConversionResults } from "../conversion/conversionResults.stubs"; import { writeConversionResults } from "./writeConversionResults"; +import { formatJsonOutput } from "./formatting/formatters/formatJsonOutput"; const originalConfigurations = { tslint: { @@ -17,29 +18,30 @@ describe("writeConversionResults", () => { const fileSystem = { writeFile: jest.fn().mockReturnValue(Promise.resolve()) }; // Act - await writeConversionResults({ fileSystem }, conversionResults, originalConfigurations); + await writeConversionResults( + { fileSystem }, + ".eslintrc.json", + conversionResults, + originalConfigurations, + ); // Assert expect(fileSystem.writeFile).toHaveBeenLastCalledWith( ".eslintrc.json", - JSON.stringify( - { - env: { - browser: true, - es6: true, - node: true, - }, - parser: "@typescript-eslint/parser", - parserOptions: { - project: "tsconfig.json", - sourceType: "module", - }, - plugins: ["@typescript-eslint"], - rules: {}, + formatJsonOutput({ + env: { + browser: true, + es6: true, + node: true, }, - undefined, - 4, - ), + parser: "@typescript-eslint/parser", + parserOptions: { + project: "tsconfig.json", + sourceType: "module", + }, + plugins: ["@typescript-eslint"], + rules: {}, + }), ); }); @@ -58,38 +60,39 @@ describe("writeConversionResults", () => { const fileSystem = { writeFile: jest.fn().mockReturnValue(Promise.resolve()) }; // Act - await writeConversionResults({ fileSystem }, conversionResults, originalConfigurations); + await writeConversionResults( + { fileSystem }, + ".eslintrc.json", + conversionResults, + originalConfigurations, + ); // Assert expect(fileSystem.writeFile).toHaveBeenLastCalledWith( ".eslintrc.json", - JSON.stringify( - { - env: { - browser: true, - es6: true, - node: true, - }, - parser: "@typescript-eslint/parser", - parserOptions: { - project: "tsconfig.json", - sourceType: "module", - }, - plugins: ["@typescript-eslint", "@typescript-eslint/tslint"], - rules: { - "@typescript-eslint/tslint/config": [ - "error", - { - rules: { - "tslint-rule-one": true, - }, + formatJsonOutput({ + env: { + browser: true, + es6: true, + node: true, + }, + parser: "@typescript-eslint/parser", + parserOptions: { + project: "tsconfig.json", + sourceType: "module", + }, + plugins: ["@typescript-eslint", "@typescript-eslint/tslint"], + rules: { + "@typescript-eslint/tslint/config": [ + "error", + { + rules: { + "tslint-rule-one": true, }, - ], - }, + }, + ], }, - undefined, - 4, - ), + }), ); }); }); diff --git a/src/creation/writeConversionResults.ts b/src/creation/writeConversionResults.ts index f09fe865..07f6802d 100644 --- a/src/creation/writeConversionResults.ts +++ b/src/creation/writeConversionResults.ts @@ -3,6 +3,7 @@ import { RuleConversionResults } from "../rules/convertRules"; import { formatConvertedRules } from "./formatConvertedRules"; import { OriginalConfigurations } from "../input/findOriginalConfigurations"; import { createEnv } from "./eslint/createEnv"; +import { formatOutput } from "./formatting/formatOutput"; export type WriteConversionResultsDependencies = { fileSystem: Pick; @@ -10,6 +11,7 @@ export type WriteConversionResultsDependencies = { export const writeConversionResults = async ( dependencies: WriteConversionResultsDependencies, + outputPath: string, ruleConversionResults: RuleConversionResults, originalConfigurations: OriginalConfigurations, ) => { @@ -29,5 +31,5 @@ export const writeConversionResults = async ( rules: formatConvertedRules(ruleConversionResults, originalConfigurations.tslint), }; - await dependencies.fileSystem.writeFile(".eslintrc.json", JSON.stringify(output, undefined, 4)); + await dependencies.fileSystem.writeFile(outputPath, formatOutput(outputPath, output)); }; diff --git a/src/input/findESLintConfiguration.test.ts b/src/input/findESLintConfiguration.test.ts index 0615c990..173a40c0 100644 --- a/src/input/findESLintConfiguration.test.ts +++ b/src/input/findESLintConfiguration.test.ts @@ -1,5 +1,12 @@ import { findESLintConfiguration } from "./findESLintConfiguration"; import { createStubExec, createStubThrowingExec } from "../adapters/exec.stubs"; +import { TSLintToESLintSettings } from "../types"; + +const createStubRawSettings = (overrides: Partial = {}) => ({ + config: "./eslintrc.js", + eslint: undefined, + ...overrides, +}); describe("findESLintConfiguration", () => { it("returns an error when one occurs", async () => { @@ -8,7 +15,7 @@ describe("findESLintConfiguration", () => { const dependencies = { exec: createStubThrowingExec({ stderr: message }) }; // Act - const result = await findESLintConfiguration(dependencies, undefined); + const result = await findESLintConfiguration(dependencies, createStubRawSettings()); // Assert expect(result).toEqual( @@ -23,7 +30,7 @@ describe("findESLintConfiguration", () => { const dependencies = { exec: createStubExec() }; // Act - await findESLintConfiguration(dependencies, undefined); + await findESLintConfiguration(dependencies, createStubRawSettings()); // Assert expect(dependencies.exec).toHaveBeenLastCalledWith("eslint --print-config ./eslintrc.js"); @@ -32,7 +39,9 @@ describe("findESLintConfiguration", () => { it("includes a configuration file in the ESLint command when one is provided", async () => { // Arrange const dependencies = { exec: createStubExec() }; - const config = "./custom/eslintrc.js"; + const config = createStubRawSettings({ + eslint: "./custom/eslintrc.js", + }); // Act await findESLintConfiguration(dependencies, config); @@ -46,7 +55,9 @@ describe("findESLintConfiguration", () => { it("applies ESLint defaults when none are provided", async () => { // Arrange const dependencies = { exec: createStubExec({ stdout: "{}" }) }; - const config = "./custom/eslintrc.js"; + const config = createStubRawSettings({ + eslint: "./custom/eslintrc.js", + }); // Act const result = await findESLintConfiguration(dependencies, config); diff --git a/src/input/findESLintConfiguration.ts b/src/input/findESLintConfiguration.ts index 179c82cc..4171dda4 100644 --- a/src/input/findESLintConfiguration.ts +++ b/src/input/findESLintConfiguration.ts @@ -1,3 +1,4 @@ +import { TSLintToESLintSettings } from "../types"; import { findConfiguration, FindConfigurationDependencies } from "./findConfiguration"; export type ESLintConfiguration = { @@ -16,12 +17,12 @@ const defaultESLintConfiguration = { export const findESLintConfiguration = async ( dependencies: FindConfigurationDependencies, - config: string | undefined, + rawSettings: Pick, ): Promise => { const rawConfiguration = await findConfiguration( dependencies.exec, "eslint --print-config", - config || "./eslintrc.js", + rawSettings.eslint || rawSettings.config, ); return rawConfiguration instanceof Error diff --git a/src/input/findOriginalConfigurations.test.ts b/src/input/findOriginalConfigurations.test.ts index c2a36a81..c1777a88 100644 --- a/src/input/findOriginalConfigurations.test.ts +++ b/src/input/findOriginalConfigurations.test.ts @@ -5,6 +5,7 @@ import { import { ResultStatus } from "../types"; const createRawSettings = () => ({ + config: "./eslintrc.js", eslint: "", tslint: "", typescript: "", diff --git a/src/input/findOriginalConfigurations.ts b/src/input/findOriginalConfigurations.ts index a6559d97..a3b387b7 100644 --- a/src/input/findOriginalConfigurations.ts +++ b/src/input/findOriginalConfigurations.ts @@ -27,7 +27,7 @@ export const findOriginalConfigurations = async ( rawSettings: TSLintToESLintSettings, ): Promise> => { const [eslint, packages, tslint, typescript] = await Promise.all([ - dependencies.findESLintConfiguration(rawSettings.eslint), + dependencies.findESLintConfiguration(rawSettings), dependencies.findPackagesConfiguration(rawSettings.package), dependencies.findTSLintConfiguration(rawSettings.tslint), dependencies.findTypeScriptConfiguration(rawSettings.typescript), diff --git a/src/types.ts b/src/types.ts index cd347816..474eb3f4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,9 @@ export type TSLintToESLintSettings = { + /** + * Output ESLint configuration file path, such as `.eslintrc.js`. + */ + config: string; + /** * Original ESLint configuration file path, such as `.eslintrc.js`. */