diff --git a/README.md b/README.md index 8d27092dc..c8737c10a 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,18 @@ _Default: `tsconfig.json`_ 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. + +## Node API + +You can use `tslint-to-eslint-config` programmatically via its exported functions. +See [docs/API](./docs/API.md) for details. + +```ts +import { convertLintConfig } from "tslint-to-eslint-config"; + +const result = await convertLintConfig(); +``` + ## Development See the [Code of Conduct](./.github/CODE_OF_CONDUCT.md) and [general development docs](./docs/Development.md). 💖 diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 000000000..5cf6cddea --- /dev/null +++ b/docs/API.md @@ -0,0 +1,138 @@ +# API + +You can use `tslint-to-eslint-config` programmatically in your Node apps. +It provides a **[`convertTSLintConfig`](#convertTSLintConfig)** function to find relevant configurations on disk and output the generated ESLint configuration. + +## `convertTSLintConfig` + +```ts +import { convertTSLintConfig } from "tslint-to-eslint-config"; + +const result = await convertTSLintConfig(); +``` + +Finds relevant configurations on disk and outputs the generated ESLint configuration. + +Optionally takes in the same settings you can provide via the CLI: + +* `config`: Output ESLint configuration file path _(default: `.eslintrc.js`)_. +* `eslint`: Original ESLint configuration file path _(default: `.eslintrc.js`)_. +* `package`: Original packages configuration file path _(default: `package.json`)_. +* `prettier`: Whether to add `eslint-config-prettier` to the plugins list. +* `tslint`: Original TSLint configuration file path _(default: `tslint.json`)_. +* `typescript`: Original TypeScript configuration file path _(default: `tsconfig.json`)_. + +```ts +import { convertTSLintConfig } from "tslint-to-eslint-config"; + +const result = await convertTSLintConfig({ + config: "./path/to/output/eslintrc.js", + eslint: "./path/to/input/eslintrc.js", + package: "./path/to/package.json", + prettier: true, // Prettier: highly recommended! + tslint: "./path/to/tslint.json", + typescript: "./path/to/tsconfig.json", +}); +``` + +If the TSLint configuration or any manually specified configurations fail to read from disk, the result will contain: + +* `complaints`: String complaints describing the errors. +* `status`: `ResultStatus.ConfigurationError` (`2`). + +If no error is detected, the result will contain: + +* `data`: Resultant ESLint configuration as: + * `formatted`: Stringified result per the output config path's file type. + * `raw`: Plain old JavaScript object. +* `status`: `ResultStatus.Succeeded` (`0`). + +```ts +import { convertTSLintConfig, ResultStatus } from "tslint-to-eslint-config"; + +const result = await convertTSLintConfig({ /* ... */ }); + +if (result.status !== ResultStatus.Succeeded) { + console.info("Oh no!"); + console.error(result.complaints.join("\n")); +} else { + console.info("Hooray!"); + console.log(result.data.formatted); + console.log(result.data.raw); +} +``` + +> See the provided `.d.ts` TypeScript typings for full descriptions of inputs and outputs. + +## Standalone API + +> ⚠ This area of code is still considered experimental. +> Use at your own risk. +> Please file an issue on GitHub if you'd like to see changes. + +Portions of the individual steps within `convertTSLintConfig` are each available as exported functions as well. + +* **[`findOriginalConfigurations`](#findOriginalConfigurations)** takes in an object of original configuration locations and retrieves their raw and computed contents. + * **[`findReportedConfiguration`](#findReportedConfiguration)** runs a config print command and parses its output as JSON. +* **[`createESLintConfiguration`](#createESLintConfiguration)** creates an raw output ESLint configuration summary from those input configuration values. + * `joinConfigConversionResults` turns a raw ESLint configuration summary into ESLint's configuration shape. + * `formatOutput` prints that formatted output into a string per the output file extension. + +### `findOriginalConfigurations` + +Reading in from the default file locations, including `.eslintrc.js`: + +```ts +import { findOriginalConfigurations } from "tslint-to-eslint-config"; + +const originalConfigurations = await findOriginalConfigurations(); +``` + +Overriding some configuration file locations to read from: + +```ts +import { findOriginalConfigurations } from "tslint-to-eslint-config"; + +const originalConfigurations = await findOriginalConfigurations({ + config: "./path/to/.eslintrc.json", + tslint: "./another/path/to/tslint.custom.json", +}); +``` + +#### `findReportedConfiguration` + +Retrieving the reported contents of a TSLint configuration: + +```ts +import { findReportedConfiguration } from "tslint-to-eslint-config"; + +const full = await findReportedConfiguration("npx tslint --print-config", "./tslint.json"); +``` + +### `createESLintConfiguration` + +Generating an ESLint configuration from the contents of a local `tslint.json`: + +```ts +import { createESLintConfiguration, findReportedConfiguration } from "tslint-to-eslint-config"; + +const summarizedConfiguration = await createESLintConfiguration({ + tslint: { + full: await findReportedConfiguration("npx tslint --print-config", "./tslint.json"), + raw: require("./tslint.json"), + }, +}); +``` + +Using the full configuration values from disk: + +```ts +import { createESLintConfiguration, findOriginalConfigurations } from "tslint-to-eslint-config"; + +const originalConfigurations = await findOriginalConfigurations(); +const summarizedConfiguration = await createESLintConfiguration(originalConfigurations); + +const raw = joinConfigConversionResults(summarizedConfiguration, originalConfigurations.data); + +const formatted = formatOutput("eslintrc.js", raw); +``` diff --git a/docs/Architecture/Linters.md b/docs/Architecture/Linters.md index 1826bd1e7..701c736af 100644 --- a/docs/Architecture/Linters.md +++ b/docs/Architecture/Linters.md @@ -3,10 +3,13 @@ TSLint-to-ESLint linter configuration conversion is the first root-level converter run. Within `src/converters/lintConfigs/convertLintConfig.ts`, the following steps occur: -1. Raw TSLint rules are mapped to their ESLint equivalents. -2. Those ESLint equivalents are deduplicated and relevant preset(s) detected. -3. Those deduplicated rules and metadata are written to the output configuration file. -4. A summary of conversion results is printed, along with any now-missing packages. +1. Deduplicated ESLint rules and metadata are generated from raw TSLint rules. + 1a. Raw TSLint rules are mapped to their ESLint equivalents. + 1b. Those ESLint equivalents are deduplicated and relevant preset(s) detected. +2. Those deduplicated rules and metadata are written to the output configuration file. +3. A summary of conversion results is printed, along with any now-missing packages. + +> Stepss 1 and 2 are the logic exported by the [Node API](../API.md) as [`convertTSLintConfig`](../API.md#convertTSLintConfig). ## Rule Conversion diff --git a/docs/Dependencies.md b/docs/Dependencies.md index ec9431d3e..be5e23bcb 100644 --- a/docs/Dependencies.md +++ b/docs/Dependencies.md @@ -8,7 +8,7 @@ Its dependencies object is manually created in `src/cli/main.ts` and bound to th ## When to Use Dependencies Most functions don't need a `dependencies` object. -Only add one if something should be stubbed out during tests. +Only add one if something should be stubbed out during tests _or_ should be available to multiple callers. ## How to Use Dependencies diff --git a/jest.config.js b/jest.config.js index 0d96a436e..3ede2a162 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,6 +4,7 @@ module.exports = { "!./src/**/*.d.ts", "!./src/**/*.stubs.ts", "!./src/adapters/*.ts", + "!./src/api/*.ts", "!./src/cli/main.ts", "!./src/converters/editorConfigs/editorSettingsConverters.ts", "!./src/converters/lintConfigs/rules/ruleConverters.ts", diff --git a/package.json b/package.json index 1e5281c84..4d22ceb5d 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "prettier --write" ] }, + "main": "./src/index.js", "name": "tslint-to-eslint-config", "repository": { "type": "git", diff --git a/src/adapters/processLogger.ts b/src/adapters/processLogger.ts index 40e254e68..52820c693 100644 --- a/src/adapters/processLogger.ts +++ b/src/adapters/processLogger.ts @@ -2,9 +2,13 @@ import * as fs from "fs"; const debugFileName = "./tslint-to-eslint-config.log"; +let writeStream: fs.WriteStream | undefined; + export const processLogger = { debugFileName, - info: fs.createWriteStream(debugFileName), + get info() { + return (writeStream ??= fs.createWriteStream(debugFileName)); + }, stderr: process.stderr, stdout: process.stdout, }; diff --git a/src/api/convertTSLintConfigStandalone.ts b/src/api/convertTSLintConfigStandalone.ts new file mode 100644 index 000000000..d829e79cc --- /dev/null +++ b/src/api/convertTSLintConfigStandalone.ts @@ -0,0 +1,64 @@ +import { formatOutput } from "../converters/lintConfigs/formatting/formatOutput"; +import { + joinConfigConversionResults, + JoinedConversionResult, +} from "../converters/lintConfigs/joinConfigConversionResults"; +import { + ConfigurationErrorResult, + LintConfigConversionSettings, + ResultStatus, + SucceededDataResult, +} from "../types"; +import { createESLintConfigurationStandalone } from "./createESLintConfigurationStandalone"; +import { findOriginalConfigurationsStandalone } from "./findOriginalConfigurationsStandalone"; + +/** + * Resultant configuration data from converting a TSLint configuration. + */ +export type TSLintConversionData = { + /** + * Formatted configuration string per the output file's extension. + */ + formatted: string; + + /** + * Object description of the resultant configuration data. + */ + raw: JoinedConversionResult; +}; + +/** + * Finds relevant configurations on disk and outputs the generated ESLint configuration. + * + * @param settings - Settings to find and convert configurations to an ESLint configuration. + */ +export const convertTSLintConfigStandalone = async ( + rawSettings: Partial = {}, +): Promise> => { + const settings = { + ...rawSettings, + config: ".eslintrc.js", + }; + const originalConfigurations = await findOriginalConfigurationsStandalone(settings); + if (originalConfigurations.status !== ResultStatus.Succeeded) { + return originalConfigurations; + } + + const summarizedConfiguration = await createESLintConfigurationStandalone( + originalConfigurations.data, + settings.prettier, + ); + + const output = joinConfigConversionResults( + summarizedConfiguration, + originalConfigurations.data, + ); + + return { + data: { + formatted: formatOutput(settings.config, output), + raw: output, + }, + status: ResultStatus.Succeeded, + }; +}; diff --git a/src/api/createESLintConfigurationStandalone.ts b/src/api/createESLintConfigurationStandalone.ts new file mode 100644 index 000000000..cefd7bd2b --- /dev/null +++ b/src/api/createESLintConfigurationStandalone.ts @@ -0,0 +1,49 @@ +import { createESLintConfiguration } from "../converters/lintConfigs/createESLintConfiguration"; +import { ESLintConfiguration } from "../input/findESLintConfiguration"; +import { + AllOriginalConfigurations, + OriginalConfigurations, +} from "../input/findOriginalConfigurations"; +import { PackagesConfiguration } from "../input/findPackagesConfiguration"; +import { TSLintConfiguration } from "../input/findTSLintConfiguration"; +import { TypeScriptConfiguration } from "../input/findTypeScriptConfiguration"; +import { createESLintConfigurationDependencies } from "./dependencies"; + +export type AllOriginalConfigurationsOptionally = { + eslint?: Partial>; + packages?: PackagesConfiguration; + tslint: Partial>; + typescript?: TypeScriptConfiguration; +}; + +/** + * Creates a raw output ESLint configuration summary from input configuration values. + * + * @param originalConfigurations + * Any input configuration objects, including 'raw' (exact configuration file contents) + * and 'full' (tool-reported computed values) for both ESLint and TSLint. + * @param prettier + * Whether to always consider the output configuration as extending from the Prettier + * ruleset, instead of inferring it from computed rule values (recommended). + */ +export const createESLintConfigurationStandalone = async ( + originalConfigurations: AllOriginalConfigurations, + prettier?: boolean, +) => { + const allOriginalConfigurations = { ...originalConfigurations }; + + if (allOriginalConfigurations.eslint) { + allOriginalConfigurations.eslint.full ??= allOriginalConfigurations.eslint.raw; + } + + if (allOriginalConfigurations.tslint) { + allOriginalConfigurations.tslint.full ??= allOriginalConfigurations.tslint.raw; + } + + return createESLintConfiguration( + createESLintConfigurationDependencies, + originalConfigurations, + prettier, + new Map(), + ); +}; diff --git a/src/api/dependencies.ts b/src/api/dependencies.ts new file mode 100644 index 000000000..2940039fc --- /dev/null +++ b/src/api/dependencies.ts @@ -0,0 +1,218 @@ +import { childProcessExec } from "../adapters/childProcessExec"; +import { fsFileSystem } from "../adapters/fsFileSystem"; +import { globAsync } from "../adapters/globAsync"; +import { nativeImporter } from "../adapters/nativeImporter"; +import { processLogger } from "../adapters/processLogger"; +import { bind } from "../binding"; +import { + collectCommentFileNames, + CollectCommentFileNamesDependencies, +} from "../comments/collectCommentFileNames"; +import { + ReportCommentResultsDependencies, + reportCommentResults, +} from "../converters/comments/reporting/reportCommentResults"; +import { + ConvertEditorConfigDependencies, + convertEditorConfig, +} from "../converters/editorConfigs/convertEditorConfig"; +import { + ConvertLintConfigDependencies, + convertLintConfig, +} from "../converters/lintConfigs/convertLintConfig"; +import { + ReportConversionResultsDependencies, + reportConfigConversionResults, +} from "../converters/lintConfigs/reporting/reportConfigConversionResults"; +import { + ConvertCommentsDependencies, + convertComments, +} from "../converters/comments/convertComments"; +import { + ConvertFileCommentsDependencies, + convertFileComments, +} from "../converters/comments/convertFileComments"; +import { + ConvertRulesDependencies, + convertRules, +} from "../converters/lintConfigs/rules/convertRules"; +import { ruleConverters } from "../converters/lintConfigs/rules/ruleConverters"; +import { + RetrieveExtendsValuesDependencies, + retrieveExtendsValues, +} from "../converters/lintConfigs/summarization/retrieveExtendsValues"; +import { + SummarizePackageRulesDependencies, + summarizePackageRules, +} from "../converters/lintConfigs/summarization/summarizePackageRules"; +import { + ChoosePackageManagerDependencies, + choosePackageManager, +} from "../converters/lintConfigs/reporting/packages/choosePackageManager"; +import { + LogMissingPackagesDependencies, + logMissingPackages, +} from "../converters/lintConfigs/reporting/packages/logMissingPackages"; +import { RunCliDependencies } from "../cli/runCli"; +import { ruleMergers } from "../converters/lintConfigs/rules/ruleMergers"; +import { removeExtendsDuplicatedRules } from "../converters/lintConfigs/pruning/removeExtendsDuplicatedRules"; +import { + ExtractGlobPathsDependencies, + extractGlobPaths, +} from "../converters/comments/extractGlobPaths"; +import { findESLintConfiguration } from "../input/findESLintConfiguration"; +import { + findOriginalConfigurations, + FindOriginalConfigurationsDependencies, +} from "../input/findOriginalConfigurations"; +import { findPackagesConfiguration } from "../input/findPackagesConfiguration"; +import { findTSLintConfiguration } from "../input/findTSLintConfiguration"; +import { findTypeScriptConfiguration } from "../input/findTypeScriptConfiguration"; +import { importer, ImporterDependencies } from "../input/importer"; +import { mergeLintConfigurations } from "../input/mergeLintConfigurations"; +import { + createESLintConfiguration, + CreateESLintConfigurationDependencies, +} from "../converters/lintConfigs/createESLintConfiguration"; +import { checkPrettierExtension } from "../converters/lintConfigs/summarization/prettier/checkPrettierExtension"; +import { + convertEditorConfigs, + ConvertEditorConfigsDependencies, +} from "../converters/editorConfigs/convertEditorConfigs"; +import { reportEditorConfigConversionResults } from "../converters/editorConfigs/reporting/reportEditorConfigConversionResults"; +import { EditorConfigDescriptor } from "../converters/editorConfigs/types"; +import { convertAtomConfig } from "../converters/editorConfigs/converters/convertAtomConfig"; +import { convertVSCodeConfig } from "../converters/editorConfigs/converters/convertVSCodeConfig"; +export const convertFileCommentsDependencies: ConvertFileCommentsDependencies = { + converters: ruleConverters, + fileSystem: fsFileSystem, +}; + +export const reportCommentResultsDependencies: ReportCommentResultsDependencies = { + logger: processLogger, +}; + +export const convertRulesDependencies: ConvertRulesDependencies = { + ruleConverters, + ruleMergers, +}; + +export const nativeImporterDependencies: ImporterDependencies = { + fileSystem: fsFileSystem, + getCwd: () => process.cwd(), + nativeImporter: nativeImporter, +}; + +export const boundImporter = bind(importer, nativeImporterDependencies); + +export const findConfigurationDependencies = { + exec: childProcessExec, + importer: boundImporter, +}; + +export const findOriginalConfigurationsDependencies: FindOriginalConfigurationsDependencies = { + findESLintConfiguration: bind(findESLintConfiguration, findConfigurationDependencies), + findPackagesConfiguration: bind(findPackagesConfiguration, findConfigurationDependencies), + findTypeScriptConfiguration: bind(findTypeScriptConfiguration, findConfigurationDependencies), + findTSLintConfiguration: bind(findTSLintConfiguration, findConfigurationDependencies), + mergeLintConfigurations, +}; + +export const collectCommentFileNamesDependencies: CollectCommentFileNamesDependencies = { + findTypeScriptConfiguration: bind(findTypeScriptConfiguration, findConfigurationDependencies), +}; + +export const extractGlobPathsDependencies: ExtractGlobPathsDependencies = { + globAsync, +}; + +export const convertCommentsDependencies: ConvertCommentsDependencies = { + collectCommentFileNames: bind(collectCommentFileNames, collectCommentFileNamesDependencies), + convertFileComments: bind(convertFileComments, convertFileCommentsDependencies), + extractGlobPaths: bind(extractGlobPaths, extractGlobPathsDependencies), + reportCommentResults: bind(reportCommentResults, reportCommentResultsDependencies), +}; + +export const retrieveExtendsValuesDependencies: RetrieveExtendsValuesDependencies = { + importer: boundImporter, +}; + +export const summarizePackageRulesDependencies: SummarizePackageRulesDependencies = { + checkPrettierExtension, + removeExtendsDuplicatedRules, + retrieveExtendsValues: bind(retrieveExtendsValues, retrieveExtendsValuesDependencies), +}; + +export const choosePackageManagerDependencies: ChoosePackageManagerDependencies = { + fileSystem: fsFileSystem, +}; + +export const createESLintConfigurationDependencies: CreateESLintConfigurationDependencies = { + convertRules: bind(convertRules, convertRulesDependencies), + summarizePackageRules: bind(summarizePackageRules, summarizePackageRulesDependencies), +}; + +export const logMissingPackagesDependencies: LogMissingPackagesDependencies = { + choosePackageManager: bind(choosePackageManager, choosePackageManagerDependencies), + logger: processLogger, +}; + +export const reportConversionResultsDependencies: ReportConversionResultsDependencies = { + logger: processLogger, +}; + +export const reportEditorSettingConversionResultsDependencies = { + logger: processLogger, +}; + +export const writeConversionResultsDependencies = { + fileSystem: fsFileSystem, +}; + +export const convertEditorConfigDependencies: ConvertEditorConfigDependencies = { + fileSystem: fsFileSystem, +}; + +export const editorConfigDescriptors: EditorConfigDescriptor[] = [ + [".atom/config.cson", convertAtomConfig], + [".vscode/settings.json", convertVSCodeConfig], +]; + +export const reportEditorConfigConversionResultsDependencies = { + logger: processLogger, +}; + +export const convertEditorConfigsDependencies: ConvertEditorConfigsDependencies = { + convertEditorConfig: bind(convertEditorConfig, convertEditorConfigDependencies), + editorConfigDescriptors, + reportEditorConfigConversionResults: bind( + reportEditorConfigConversionResults, + reportEditorConfigConversionResultsDependencies, + ), +}; + +export const convertLintConfigDependencies: ConvertLintConfigDependencies = { + createESLintConfiguration: bind( + createESLintConfiguration, + createESLintConfigurationDependencies, + ), + fileSystem: fsFileSystem, + logMissingPackages: bind(logMissingPackages, logMissingPackagesDependencies), + reportConfigConversionResults: bind( + reportConfigConversionResults, + reportConversionResultsDependencies, + ), +}; + +export const runCliDependencies: RunCliDependencies = { + converters: [ + bind(convertLintConfig, convertLintConfigDependencies), + bind(convertEditorConfigs, convertEditorConfigsDependencies), + bind(convertComments, convertCommentsDependencies), + ], + findOriginalConfigurations: bind( + findOriginalConfigurations, + findOriginalConfigurationsDependencies, + ), + logger: processLogger, +}; diff --git a/src/api/findOriginalConfigurationsStandalone.ts b/src/api/findOriginalConfigurationsStandalone.ts new file mode 100644 index 000000000..940c80799 --- /dev/null +++ b/src/api/findOriginalConfigurationsStandalone.ts @@ -0,0 +1,17 @@ +import { findOriginalConfigurations } from "../input/findOriginalConfigurations"; +import { ConfigurationLocations } from "../types"; +import { findOriginalConfigurationsDependencies } from "./dependencies"; + +/** + * Retrieves the raw and computed contents of original configuration files. + * + * @param locations - Any overrides to file locations to read from. + */ +export const findOriginalConfigurationsStandalone = async ( + locations?: Partial, +) => { + return findOriginalConfigurations(findOriginalConfigurationsDependencies, { + ...locations, + config: locations?.config ?? ".eslintrc.js", + }); +}; diff --git a/src/api/findReportedConfigurationStandalone.ts b/src/api/findReportedConfigurationStandalone.ts new file mode 100644 index 000000000..0daedcab5 --- /dev/null +++ b/src/api/findReportedConfigurationStandalone.ts @@ -0,0 +1,12 @@ +import { childProcessExec } from "../adapters/childProcessExec"; +import { findReportedConfiguration } from "../input/findReportedConfiguration"; + +/** + * Runs a config print command and parses its output as JSON. + * + * @param command - Printer command to exec, such as "npx tslint --print-config". + * @param config - Configuration file location to read from. + */ +export const findReportedConfigurationStandalone = async (command: string, config: string) => { + return await findReportedConfiguration(childProcessExec, command, config); +}; diff --git a/src/cli/main.ts b/src/cli/main.ts index 5dd5ffe5b..58ab5e3e9 100644 --- a/src/cli/main.ts +++ b/src/cli/main.ts @@ -1,214 +1,8 @@ import { EOL } from "os"; -import { childProcessExec } from "../adapters/childProcessExec"; -import { fsFileSystem } from "../adapters/fsFileSystem"; -import { globAsync } from "../adapters/globAsync"; -import { nativeImporter } from "../adapters/nativeImporter"; import { processLogger } from "../adapters/processLogger"; -import { bind } from "../binding"; -import { - collectCommentFileNames, - CollectCommentFileNamesDependencies, -} from "../comments/collectCommentFileNames"; -import { - ReportCommentResultsDependencies, - reportCommentResults, -} from "../converters/comments/reporting/reportCommentResults"; -import { convertEditorConfig } from "../converters/editorConfigs/convertEditorConfig"; -import { - ConvertLintConfigDependencies, - convertLintConfig, -} from "../converters/lintConfigs/convertLintConfig"; -import { - ReportConversionResultsDependencies, - reportConfigConversionResults, -} from "../converters/lintConfigs/reporting/reportConfigConversionResults"; -import { - WriteConversionResultsDependencies, - writeConfigConversionResults, -} from "../converters/lintConfigs/writeConfigConversionResults"; -import { - ConvertCommentsDependencies, - convertComments, -} from "../converters/comments/convertComments"; -import { - ConvertFileCommentsDependencies, - convertFileComments, -} from "../converters/comments/convertFileComments"; -import { - convertEditorConfigs, - ConvertEditorConfigsDependencies, -} from "../converters/editorConfigs/convertEditorConfigs"; -import { convertAtomConfig } from "../converters/editorConfigs/converters/convertAtomConfig"; -import { convertVSCodeConfig } from "../converters/editorConfigs/converters/convertVSCodeConfig"; -import { reportEditorConfigConversionResults } from "../converters/editorConfigs/reporting/reportEditorConfigConversionResults"; -import { EditorConfigDescriptor } from "../converters/editorConfigs/types"; -import { - ConvertRulesDependencies, - convertRules, -} from "../converters/lintConfigs/rules/convertRules"; -import { ruleConverters } from "../converters/lintConfigs/rules/ruleConverters"; -import { - RetrieveExtendsValuesDependencies, - retrieveExtendsValues, -} from "../converters/lintConfigs/summarization/retrieveExtendsValues"; -import { - SummarizePackageRulesDependencies, - summarizePackageRules, -} from "../converters/lintConfigs/summarization/summarizePackageRules"; -import { - ChoosePackageManagerDependencies, - choosePackageManager, -} from "../converters/lintConfigs/reporting/packages/choosePackageManager"; -import { - LogMissingPackagesDependencies, - logMissingPackages, -} from "../converters/lintConfigs/reporting/packages/logMissingPackages"; -import { runCli, RunCliDependencies } from "./runCli"; -import { ruleMergers } from "../converters/lintConfigs/rules/ruleMergers"; -import { checkPrettierExtension } from "../converters/lintConfigs/summarization/prettier/checkPrettierExtension"; -import { removeExtendsDuplicatedRules } from "../converters/lintConfigs/pruning/removeExtendsDuplicatedRules"; -import { - ExtractGlobPathsDependencies, - extractGlobPaths, -} from "../converters/comments/extractGlobPaths"; -import { findESLintConfiguration } from "../input/findESLintConfiguration"; -import { - findOriginalConfigurations, - FindOriginalConfigurationsDependencies, -} from "../input/findOriginalConfigurations"; -import { findPackagesConfiguration } from "../input/findPackagesConfiguration"; -import { findTSLintConfiguration } from "../input/findTSLintConfiguration"; -import { findTypeScriptConfiguration } from "../input/findTypeScriptConfiguration"; -import { importer, ImporterDependencies } from "../input/importer"; -import { mergeLintConfigurations } from "../input/mergeLintConfigurations"; - -const convertFileCommentsDependencies: ConvertFileCommentsDependencies = { - converters: ruleConverters, - fileSystem: fsFileSystem, -}; - -const reportCommentResultsDependencies: ReportCommentResultsDependencies = { - logger: processLogger, -}; - -const convertRulesDependencies: ConvertRulesDependencies = { - ruleConverters, - ruleMergers, -}; - -const nativeImporterDependencies: ImporterDependencies = { - fileSystem: fsFileSystem, - getCwd: () => process.cwd(), - nativeImporter: nativeImporter, -}; - -const boundImporter = bind(importer, nativeImporterDependencies); - -const findConfigurationDependencies = { - exec: childProcessExec, - importer: boundImporter, -}; - -const findOriginalConfigurationsDependencies: FindOriginalConfigurationsDependencies = { - findESLintConfiguration: bind(findESLintConfiguration, findConfigurationDependencies), - findPackagesConfiguration: bind(findPackagesConfiguration, findConfigurationDependencies), - findTypeScriptConfiguration: bind(findTypeScriptConfiguration, findConfigurationDependencies), - findTSLintConfiguration: bind(findTSLintConfiguration, findConfigurationDependencies), - mergeLintConfigurations, -}; - -const collectCommentFileNamesDependencies: CollectCommentFileNamesDependencies = { - findTypeScriptConfiguration: bind(findTypeScriptConfiguration, findConfigurationDependencies), -}; - -const extractGlobPathsDependencies: ExtractGlobPathsDependencies = { - globAsync, -}; - -const convertCommentsDependencies: ConvertCommentsDependencies = { - collectCommentFileNames: bind(collectCommentFileNames, collectCommentFileNamesDependencies), - convertFileComments: bind(convertFileComments, convertFileCommentsDependencies), - extractGlobPaths: bind(extractGlobPaths, extractGlobPathsDependencies), - reportCommentResults: bind(reportCommentResults, reportCommentResultsDependencies), -}; - -const choosePackageManagerDependencies: ChoosePackageManagerDependencies = { - fileSystem: fsFileSystem, -}; - -const logMissingPackagesDependencies: LogMissingPackagesDependencies = { - choosePackageManager: bind(choosePackageManager, choosePackageManagerDependencies), - logger: processLogger, -}; - -const reportConversionResultsDependencies: ReportConversionResultsDependencies = { - logger: processLogger, -}; - -const retrieveExtendsValuesDependencies: RetrieveExtendsValuesDependencies = { - importer: boundImporter, -}; - -const summarizePackageRulesDependencies: SummarizePackageRulesDependencies = { - checkPrettierExtension, - removeExtendsDuplicatedRules, - retrieveExtendsValues: bind(retrieveExtendsValues, retrieveExtendsValuesDependencies), -}; - -const writeConversionResultsDependencies: WriteConversionResultsDependencies = { - fileSystem: fsFileSystem, -}; - -const editorConfigDescriptors: EditorConfigDescriptor[] = [ - [".atom/config.cson", convertAtomConfig], - [".vscode/settings.json", convertVSCodeConfig], -]; - -const convertEditorConfigDependencies = { - editorConfigDescriptors, - fileSystem: fsFileSystem, -}; - -const reportEditorConfigConversionResultsDependencies = { - logger: processLogger, -}; - -const convertEditorConfigsDependencies: ConvertEditorConfigsDependencies = { - convertEditorConfig: bind(convertEditorConfig, convertEditorConfigDependencies), - editorConfigDescriptors, - reportEditorConfigConversionResults: bind( - reportEditorConfigConversionResults, - reportEditorConfigConversionResultsDependencies, - ), -}; - -const convertLintConfigDependencies: ConvertLintConfigDependencies = { - convertRules: bind(convertRules, convertRulesDependencies), - logMissingPackages: bind(logMissingPackages, logMissingPackagesDependencies), - reportConfigConversionResults: bind( - reportConfigConversionResults, - reportConversionResultsDependencies, - ), - summarizePackageRules: bind(summarizePackageRules, summarizePackageRulesDependencies), - writeConfigConversionResults: bind( - writeConfigConversionResults, - writeConversionResultsDependencies, - ), -}; - -const runCliDependencies: RunCliDependencies = { - converters: [ - bind(convertLintConfig, convertLintConfigDependencies), - bind(convertEditorConfigs, convertEditorConfigsDependencies), - bind(convertComments, convertCommentsDependencies), - ], - findOriginalConfigurations: bind( - findOriginalConfigurations, - findOriginalConfigurationsDependencies, - ), - logger: processLogger, -}; +import { runCliDependencies } from "../api/dependencies"; +import { runCli } from "../cli/runCli"; export const main = async (argv: string[]) => { try { diff --git a/src/converters/lintConfigs/convertLintConfig.test.ts b/src/converters/lintConfigs/convertLintConfig.test.ts index 554a01f49..964059f15 100644 --- a/src/converters/lintConfigs/convertLintConfig.test.ts +++ b/src/converters/lintConfigs/convertLintConfig.test.ts @@ -13,14 +13,10 @@ const createStubDependencies = ( const ruleConversionResults = createEmptyConfigConversionResults(); return { - convertRules: jest.fn(), - reportConfigConversionResults: jest.fn(), - summarizePackageRules: async (_configurations, data) => ({ - ...ruleConversionResults, - ...data, - }), - logMissingPackages: jest.fn().mockReturnValue(Promise.resolve()), - writeConfigConversionResults: jest.fn().mockReturnValue(Promise.resolve()), + createESLintConfiguration: jest.fn().mockResolvedValue(ruleConversionResults), + fileSystem: { writeFile: jest.fn() }, + logMissingPackages: jest.fn().mockResolvedValue(undefined), + reportConfigConversionResults: jest.fn().mockResolvedValue(undefined), ...overrides, }; }; @@ -30,7 +26,9 @@ describe("convertLintConfig", () => { // Arrange const fileWriteError = new Error(); const dependencies = createStubDependencies({ - writeConfigConversionResults: jest.fn().mockResolvedValueOnce(fileWriteError), + fileSystem: { + writeFile: jest.fn().mockResolvedValue(fileWriteError), + }, }); // Act @@ -53,7 +51,11 @@ describe("convertLintConfig", () => { const convertCommentsResult = { status: ResultStatus.Succeeded, }; - const dependencies = createStubDependencies(); + const dependencies = createStubDependencies({ + fileSystem: { + writeFile: jest.fn().mockResolvedValue(undefined), + }, + }); // Act const result = await convertLintConfig( diff --git a/src/converters/lintConfigs/convertLintConfig.ts b/src/converters/lintConfigs/convertLintConfig.ts index c6e7de782..e3a32d193 100644 --- a/src/converters/lintConfigs/convertLintConfig.ts +++ b/src/converters/lintConfigs/convertLintConfig.ts @@ -1,18 +1,18 @@ +import { FileSystem } from "../../adapters/fileSystem"; import { SansDependencies } from "../../binding"; import { AllOriginalConfigurations } from "../../input/findOriginalConfigurations"; import { TSLintToESLintSettings, ResultWithStatus, ResultStatus } from "../../types"; +import { createESLintConfiguration } from "./createESLintConfiguration"; +import { formatOutput } from "./formatting/formatOutput"; +import { joinConfigConversionResults } from "./joinConfigConversionResults"; import { logMissingPackages } from "./reporting/packages/logMissingPackages"; import { reportConfigConversionResults } from "./reporting/reportConfigConversionResults"; -import { convertRules } from "./rules/convertRules"; -import { summarizePackageRules } from "./summarization/summarizePackageRules"; -import { writeConfigConversionResults } from "./writeConfigConversionResults"; export type ConvertLintConfigDependencies = { - convertRules: SansDependencies; + createESLintConfiguration: SansDependencies; + fileSystem: Pick; logMissingPackages: SansDependencies; reportConfigConversionResults: SansDependencies; - summarizePackageRules: SansDependencies; - writeConfigConversionResults: SansDependencies; }; /** @@ -25,25 +25,20 @@ export const convertLintConfig = async ( originalConfigurations: AllOriginalConfigurations, ruleEquivalents: Map, ): Promise => { - // 1. Raw TSLint rules are mapped to their ESLint equivalents. - const ruleConversionResults = dependencies.convertRules( - originalConfigurations.tslint.full.rules, + // 1. Deduplicated ESLint rules and metadata are generated from raw TSLint rules. + const summarizedConfiguration = await dependencies.createESLintConfiguration( + originalConfigurations, + settings.prettier, ruleEquivalents, ); - // 2. Those ESLint equivalents are deduplicated and relevant preset(s) detected. - const summarizedConfiguration = await dependencies.summarizePackageRules( - originalConfigurations.eslint, - originalConfigurations.tslint, - ruleConversionResults, - settings.prettier, - ); + // 2. Those deduplicated rules and metadata are written to the output configuration file. + const output = joinConfigConversionResults(summarizedConfiguration, originalConfigurations); - // 3. Those deduplicated rules and metadata are written to the output configuration file. - const fileWriteError = await dependencies.writeConfigConversionResults( + // 3. That ESLint configuration output is written to the output configuration file. + const fileWriteError = await dependencies.fileSystem.writeFile( settings.config, - summarizedConfiguration, - originalConfigurations, + formatOutput(settings.config, output), ); if (fileWriteError !== undefined) { return { @@ -52,7 +47,7 @@ export const convertLintConfig = async ( }; } - // 4. A summary of conversion results is printed, along with any now-missing packages. + // 5. A summary of conversion results is printed, along with any now-missing packages. await dependencies.reportConfigConversionResults(settings.config, summarizedConfiguration); await dependencies.logMissingPackages(summarizedConfiguration, originalConfigurations.packages); diff --git a/src/converters/lintConfigs/createESLintConfiguration.test.ts b/src/converters/lintConfigs/createESLintConfiguration.test.ts new file mode 100644 index 000000000..334458c27 --- /dev/null +++ b/src/converters/lintConfigs/createESLintConfiguration.test.ts @@ -0,0 +1,26 @@ +import { createStubOriginalConfigurationsData } from "../../settings.stubs"; +import { createEmptyConfigConversionResults } from "./configConversionResults.stubs"; +import { createESLintConfiguration } from "./createESLintConfiguration"; + +describe("createESLintConfiguration", () => { + it("returns the result of summarizing package rules", async () => { + // Arrange + const summarizedResults = createEmptyConfigConversionResults(); + const dependencies = { + convertRules: jest.fn().mockReturnValue(summarizedResults), + summarizePackageRules: jest.fn().mockReturnValue(summarizedResults), + }; + const originalConfigurations = createStubOriginalConfigurationsData(); + + // Act + const result = await createESLintConfiguration( + dependencies, + originalConfigurations, + true, + new Map(), + ); + + // Assert + expect(result).toEqual(summarizedResults); + }); +}); diff --git a/src/converters/lintConfigs/createESLintConfiguration.ts b/src/converters/lintConfigs/createESLintConfiguration.ts new file mode 100644 index 000000000..bd1e0d7b4 --- /dev/null +++ b/src/converters/lintConfigs/createESLintConfiguration.ts @@ -0,0 +1,30 @@ +import { SansDependencies } from "../../binding"; +import { AllOriginalConfigurations } from "../../input/findOriginalConfigurations"; +import { convertRules } from "./rules/convertRules"; +import { summarizePackageRules } from "./summarization/summarizePackageRules"; + +export type CreateESLintConfigurationDependencies = { + convertRules: SansDependencies; + summarizePackageRules: SansDependencies; +}; + +export const createESLintConfiguration = async ( + dependencies: CreateESLintConfigurationDependencies, + originalConfigurations: AllOriginalConfigurations, + prettier: boolean | undefined, + ruleEquivalents: Map, +) => { + // 1a. Raw TSLint rules are mapped to their ESLint equivalents. + const ruleConversionResults = dependencies.convertRules( + originalConfigurations.tslint.full.rules, + ruleEquivalents, + ); + + // 1b. Those ESLint equivalents are deduplicated and relevant preset(s) detected. + return await dependencies.summarizePackageRules( + originalConfigurations.eslint, + originalConfigurations.tslint, + ruleConversionResults, + prettier, + ); +}; diff --git a/src/converters/lintConfigs/joinConfigConversionResults.test.ts b/src/converters/lintConfigs/joinConfigConversionResults.test.ts new file mode 100644 index 000000000..617dffaad --- /dev/null +++ b/src/converters/lintConfigs/joinConfigConversionResults.test.ts @@ -0,0 +1,199 @@ +import { AllOriginalConfigurations } from "../../input/findOriginalConfigurations"; +import { createEmptyConfigConversionResults } from "./configConversionResults.stubs"; +import { joinConfigConversionResults } from "./joinConfigConversionResults"; +import { SummarizedConfigResultsConfiguration } from "./summarization/types"; + +const createStubOriginalConfigurations = ( + overrides: Partial = {}, +) => ({ + tslint: { + full: { + rulesDirectory: [], + rules: {}, + }, + raw: {}, + }, + ...overrides, +}); + +describe("writeConversionResults", () => { + it("excludes the tslint plugin when there are no missing rules", () => { + // Arrange + const conversionResults = createEmptyConfigConversionResults(); + + // Act + const output = joinConfigConversionResults( + conversionResults, + createStubOriginalConfigurations(), + ); + + // Assert + expect(output).toEqual({ + env: { + browser: true, + es6: true, + node: true, + }, + parser: "@typescript-eslint/parser", + parserOptions: { + project: "tsconfig.json", + sourceType: "module", + }, + plugins: ["@typescript-eslint"], + }); + }); + + it("includes typescript-eslint plugin settings when there are missing rules", () => { + // Arrange + const conversionResults = createEmptyConfigConversionResults({ + missing: [ + { + ruleArguments: [], + ruleName: "tslint-rule-one", + ruleSeverity: "error", + }, + ], + plugins: new Set(["eslint-plugin-example"]), + }); + + // Act + const output = joinConfigConversionResults( + conversionResults, + createStubOriginalConfigurations(), + ); + + // Assert + expect(output).toEqual({ + env: { + browser: true, + es6: true, + node: true, + }, + parser: "@typescript-eslint/parser", + parserOptions: { + project: "tsconfig.json", + sourceType: "module", + }, + plugins: ["eslint-plugin-example", "@typescript-eslint", "@typescript-eslint/tslint"], + rules: { + "@typescript-eslint/tslint/config": [ + "error", + { + rules: { + "tslint-rule-one": true, + }, + }, + ], + }, + }); + }); + + it("includes the original eslint configuration when it exists", () => { + // Arrange + const conversionResults = createEmptyConfigConversionResults(); + const eslint = { + full: { + env: {}, + extends: [], + globals: { + Promise: true, + }, + rules: {}, + }, + raw: {}, + }; + const originalConfigurations = createStubOriginalConfigurations({ + eslint, + }); + + // Act + const output = joinConfigConversionResults(conversionResults, originalConfigurations); + + // Assert + expect(output).toEqual({ + env: { + browser: true, + es6: true, + node: true, + }, + parser: "@typescript-eslint/parser", + parserOptions: { + project: "tsconfig.json", + sourceType: "module", + }, + plugins: ["@typescript-eslint"], + }); + }); + + it("includes extensions when they exist", () => { + // Arrange + const extension = ["stub-extension"]; + const conversionResults = { + ...createEmptyConfigConversionResults(), + extends: extension, + }; + + // Act + const output = joinConfigConversionResults( + conversionResults, + createStubOriginalConfigurations(), + ); + + // Assert + expect(output).toEqual({ + env: { + browser: true, + es6: true, + node: true, + }, + extends: extension, + parser: "@typescript-eslint/parser", + parserOptions: { + project: "tsconfig.json", + sourceType: "module", + }, + plugins: ["@typescript-eslint"], + }); + }); + + it("includes raw globals when they exist", () => { + // Arrange + const conversionResults = createEmptyConfigConversionResults(); + const eslint = { + full: { + env: {}, + extends: [], + rules: {}, + }, + raw: { + globals: { + Promise: true, + }, + }, + }; + const originalConfigurations = createStubOriginalConfigurations({ + eslint, + }); + + // Act + const output = joinConfigConversionResults(conversionResults, originalConfigurations); + + // Assert + expect(output).toEqual({ + env: { + browser: true, + es6: true, + node: true, + }, + globals: { + Promise: true, + }, + parser: "@typescript-eslint/parser", + parserOptions: { + project: "tsconfig.json", + sourceType: "module", + }, + plugins: ["@typescript-eslint"], + }); + }); +}); diff --git a/src/converters/lintConfigs/writeConfigConversionResults.ts b/src/converters/lintConfigs/joinConfigConversionResults.ts similarity index 70% rename from src/converters/lintConfigs/writeConfigConversionResults.ts rename to src/converters/lintConfigs/joinConfigConversionResults.ts index b6254ea45..e0fae65ec 100644 --- a/src/converters/lintConfigs/writeConfigConversionResults.ts +++ b/src/converters/lintConfigs/joinConfigConversionResults.ts @@ -1,18 +1,13 @@ -import { FileSystem } from "../../adapters/fileSystem"; import { AllOriginalConfigurations } from "../../input/findOriginalConfigurations"; import { removeEmptyMembers } from "../../utils"; import { createEnv } from "./eslint/createEnv"; import { formatConvertedRules } from "./formatConvertedRules"; -import { formatOutput } from "./formatting/formatOutput"; import { SummarizedConfigResultsConfiguration } from "./summarization/types"; -export type WriteConversionResultsDependencies = { - fileSystem: Pick; -}; - -export const writeConfigConversionResults = async ( - dependencies: WriteConversionResultsDependencies, - outputPath: string, +/** + * Turns a raw ESLint configuration summary into ESLint's configuration shape. + */ +export const joinConfigConversionResults = ( summarizedResults: SummarizedConfigResultsConfiguration, originalConfigurations: AllOriginalConfigurations, ) => { @@ -23,7 +18,7 @@ export const writeConfigConversionResults = async ( plugins.add("@typescript-eslint/tslint"); } - const output = removeEmptyMembers({ + return removeEmptyMembers({ ...eslint?.full, env: createEnv(originalConfigurations), ...(eslint && { globals: eslint.raw.globals }), @@ -36,6 +31,6 @@ export const writeConfigConversionResults = async ( plugins: Array.from(plugins), rules: formatConvertedRules(summarizedResults, tslint.full), }); - - return await dependencies.fileSystem.writeFile(outputPath, formatOutput(outputPath, output)); }; + +export type JoinedConversionResult = ReturnType; diff --git a/src/converters/lintConfigs/writeConfigConversionResults.test.ts b/src/converters/lintConfigs/writeConfigConversionResults.test.ts deleted file mode 100644 index 733a5464e..000000000 --- a/src/converters/lintConfigs/writeConfigConversionResults.test.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { AllOriginalConfigurations } from "../../input/findOriginalConfigurations"; -import { createEmptyConfigConversionResults } from "./configConversionResults.stubs"; -import { formatJsonOutput } from "./formatting/formatters/formatJsonOutput"; -import { SummarizedConfigResultsConfiguration } from "./summarization/types"; -import { writeConfigConversionResults } from "./writeConfigConversionResults"; - -const createStubOriginalConfigurations = ( - overrides: Partial = {}, -) => ({ - tslint: { - full: { - rulesDirectory: [], - rules: {}, - }, - raw: {}, - }, - ...overrides, -}); - -describe("writeConversionResults", () => { - it("excludes the tslint plugin when there are no missing rules", async () => { - // Arrange - const conversionResults = createEmptyConfigConversionResults(); - const fileSystem = { writeFile: jest.fn().mockReturnValue(Promise.resolve()) }; - - // Act - await writeConfigConversionResults( - { fileSystem }, - ".eslintrc.json", - conversionResults, - createStubOriginalConfigurations(), - ); - - // Assert - expect(fileSystem.writeFile).toHaveBeenLastCalledWith( - ".eslintrc.json", - formatJsonOutput({ - env: { - browser: true, - es6: true, - node: true, - }, - parser: "@typescript-eslint/parser", - parserOptions: { - project: "tsconfig.json", - sourceType: "module", - }, - plugins: ["@typescript-eslint"], - }), - ); - }); - - it("includes typescript-eslint plugin settings when there are missing rules", async () => { - // Arrange - const conversionResults = createEmptyConfigConversionResults({ - missing: [ - { - ruleArguments: [], - ruleName: "tslint-rule-one", - ruleSeverity: "error", - }, - ], - plugins: new Set(["eslint-plugin-example"]), - }); - const fileSystem = { writeFile: jest.fn().mockReturnValue(Promise.resolve()) }; - - // Act - await writeConfigConversionResults( - { fileSystem }, - ".eslintrc.json", - conversionResults, - createStubOriginalConfigurations(), - ); - - // Assert - expect(fileSystem.writeFile).toHaveBeenLastCalledWith( - ".eslintrc.json", - formatJsonOutput({ - env: { - browser: true, - es6: true, - node: true, - }, - parser: "@typescript-eslint/parser", - parserOptions: { - project: "tsconfig.json", - sourceType: "module", - }, - plugins: [ - "eslint-plugin-example", - "@typescript-eslint", - "@typescript-eslint/tslint", - ], - rules: { - "@typescript-eslint/tslint/config": [ - "error", - { - rules: { - "tslint-rule-one": true, - }, - }, - ], - }, - }), - ); - }); - - it("includes the original eslint configuration when it exists", async () => { - // Arrange - const conversionResults = createEmptyConfigConversionResults(); - const eslint = { - full: { - env: {}, - extends: [], - globals: { - Promise: true, - }, - rules: {}, - }, - raw: {}, - }; - const originalConfigurations = createStubOriginalConfigurations({ - eslint, - }); - const fileSystem = { writeFile: jest.fn().mockReturnValue(Promise.resolve()) }; - - // Act - await writeConfigConversionResults( - { fileSystem }, - ".eslintrc.json", - conversionResults, - originalConfigurations, - ); - - // Assert - expect(fileSystem.writeFile).toHaveBeenLastCalledWith( - ".eslintrc.json", - formatJsonOutput({ - env: { - browser: true, - es6: true, - node: true, - }, - parser: "@typescript-eslint/parser", - parserOptions: { - project: "tsconfig.json", - sourceType: "module", - }, - plugins: ["@typescript-eslint"], - }), - ); - }); - - it("includes extensions when they exist", async () => { - // Arrange - const extension = ["stub-extension"]; - const conversionResults = { - ...createEmptyConfigConversionResults(), - extends: extension, - }; - const fileSystem = { writeFile: jest.fn().mockReturnValue(Promise.resolve()) }; - - // Act - await writeConfigConversionResults( - { fileSystem }, - ".eslintrc.json", - conversionResults, - createStubOriginalConfigurations(), - ); - - // Assert - expect(fileSystem.writeFile).toHaveBeenLastCalledWith( - ".eslintrc.json", - formatJsonOutput({ - env: { - browser: true, - es6: true, - node: true, - }, - extends: extension, - parser: "@typescript-eslint/parser", - parserOptions: { - project: "tsconfig.json", - sourceType: "module", - }, - plugins: ["@typescript-eslint"], - }), - ); - }); - - it("includes raw globals when they exist", async () => { - // Arrange - const conversionResults = createEmptyConfigConversionResults(); - const eslint = { - full: { - env: {}, - extends: [], - rules: {}, - }, - raw: { - globals: { - Promise: true, - }, - }, - }; - const originalConfigurations = createStubOriginalConfigurations({ - eslint, - }); - const fileSystem = { writeFile: jest.fn().mockReturnValue(Promise.resolve()) }; - - // Act - await writeConfigConversionResults( - { fileSystem }, - ".eslintrc.json", - conversionResults, - originalConfigurations, - ); - - // Assert - expect(fileSystem.writeFile).toHaveBeenLastCalledWith( - ".eslintrc.json", - formatJsonOutput({ - env: { - browser: true, - es6: true, - node: true, - }, - globals: { - Promise: true, - }, - parser: "@typescript-eslint/parser", - parserOptions: { - project: "tsconfig.json", - sourceType: "module", - }, - plugins: ["@typescript-eslint"], - }), - ); - }); -}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..12bfb1a61 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,7 @@ +export { convertTSLintConfigStandalone as convertTSLintConfig } from "./api/convertTSLintConfigStandalone"; +export { createESLintConfigurationStandalone as createESLintConfiguration } from "./api/createESLintConfigurationStandalone"; +export { findOriginalConfigurationsStandalone as findOriginalConfigurations } from "./api/findOriginalConfigurationsStandalone"; +export { findReportedConfigurationStandalone as findReportedConfiguration } from "./api/findReportedConfigurationStandalone"; +export { formatOutput } from "./converters/lintConfigs/formatting/formatOutput"; +export { joinConfigConversionResults } from "./converters/lintConfigs/joinConfigConversionResults"; +export * from "./types"; diff --git a/src/input/findESLintConfiguration.ts b/src/input/findESLintConfiguration.ts index 5d962b901..5f8139ba1 100644 --- a/src/input/findESLintConfiguration.ts +++ b/src/input/findESLintConfiguration.ts @@ -1,7 +1,7 @@ import { Exec } from "../adapters/exec"; import { SansDependencies } from "../binding"; import { RawESLintRuleSeverity } from "../converters/lintConfigs/rules/types"; -import { TSLintToESLintSettings } from "../types"; +import { ConfigurationLocations } from "../types"; import { uniqueFromSources } from "../utils"; import { findRawConfiguration } from "./findRawConfiguration"; import { findReportedConfiguration } from "./findReportedConfiguration"; @@ -34,7 +34,7 @@ export type FindESLintConfigurationDependencies = { export const findESLintConfiguration = async ( dependencies: FindESLintConfigurationDependencies, - config: Pick, + config: Pick, ): Promise | Error> => { const filePath = config.eslint ?? config.config; const [rawConfiguration, reportedConfiguration] = await Promise.all([ diff --git a/src/input/findEditorConfiguration.test.ts b/src/input/findEditorConfiguration.test.ts deleted file mode 100644 index 734395ca1..000000000 --- a/src/input/findEditorConfiguration.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { createStubFileSystem } from "../adapters/fileSystem.stub"; -import { - findEditorConfiguration, - FindEditorConfigurationDependencies, -} from "./findEditorConfiguration"; -import { DEFAULT_VSCODE_SETTINGS_PATH } from "./vsCodeSettings"; - -const stubConfigPath = "temp/"; - -export const createStubImporter = (filePath = "") => - jest.fn().mockReturnValue(Promise.resolve(filePath)); - -const createStubDependencies = (overrides: Partial = {}) => ({ - importer: createStubImporter(stubConfigPath), - fileSystem: createStubFileSystem(), - ...overrides, -}); - -describe("findEditorConfiguration", () => { - it("returns undefined when the file is not specified and does not exist", async () => { - // Arrange - const dependencies = createStubDependencies({ - fileSystem: { - fileExists: async () => false, - }, - }); - - // Act - const result = await findEditorConfiguration(dependencies, undefined); - - // Assert - expect(result).toEqual(undefined); - }); - - it("returns an error when the file is specified and does not exist", async () => { - // Arrange - const dependencies = createStubDependencies({ - fileSystem: { - fileExists: async () => false, - }, - }); - - // Act - const result = await findEditorConfiguration(dependencies, stubConfigPath); - - // Assert - expect(result).toEqual({ - configPath: stubConfigPath, - result: expect.objectContaining({ - message: `Could not find editor configuration under '${stubConfigPath}'.`, - }), - }); - }); - - it("returns an error when importer returns one", async () => { - // Arrange - const message = "error"; - const dependencies = createStubDependencies({ - importer: async () => { - throw new Error(message); - }, - }); - - // Act - const result = await findEditorConfiguration(dependencies, stubConfigPath); - - // Assert - expect(result).toEqual({ - configPath: stubConfigPath, - result: expect.objectContaining({ - message, - }), - }); - }); - - it("reads from the given configuration path when one is provided", async () => { - // Arrange - const configPath = "/thePath"; - const dependencies = createStubDependencies(); - - // Act - await findEditorConfiguration(dependencies, configPath); - - // Assert - expect(dependencies.importer).toHaveBeenLastCalledWith(configPath); - }); - - it("parses object from the default VS Code configuration path when the file is not specified and read successfully", async () => { - // Arrange - const originalConfig = { - "typescript.tsdk": "node_modules/typescript/lib", - }; - - const dependencies = createStubDependencies({ - importer: async () => originalConfig, - }); - - // Act - const result = await findEditorConfiguration(dependencies, undefined); - - // Assert - expect(result).toEqual({ - configPath: DEFAULT_VSCODE_SETTINGS_PATH, - result: originalConfig, - }); - }); - - it("parses object from configuration path when the file is specified and read successfully", async () => { - // Arrange - const originalConfig = { - "typescript.tsdk": "node_modules/typescript/lib", - }; - - const dependencies = createStubDependencies({ - importer: async () => originalConfig, - }); - - // Act - const result = await findEditorConfiguration(dependencies, stubConfigPath); - - // Assert - expect(result).toEqual({ - configPath: stubConfigPath, - result: originalConfig, - }); - }); -}); diff --git a/src/input/findEditorConfiguration.ts b/src/input/findEditorConfiguration.ts deleted file mode 100644 index b02c0df96..000000000 --- a/src/input/findEditorConfiguration.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { FileSystem } from "../adapters/fileSystem"; -import { SansDependencies } from "../binding"; -import { EditorConfiguration } from "./editorConfiguration"; -import { findRawConfiguration } from "./findRawConfiguration"; -import { DeepPartial } from "./findReportedConfiguration"; -import { importer } from "./importer"; -import { DEFAULT_VSCODE_SETTINGS_PATH } from "./vsCodeSettings"; - -export type FindEditorConfigurationDependencies = { - fileSystem: Pick; - importer: SansDependencies; -}; - -export const findEditorConfiguration = async ( - dependencies: FindEditorConfigurationDependencies, - specifiedConfigPath: string | undefined, -) => { - const attemptingConfigPath = specifiedConfigPath ?? DEFAULT_VSCODE_SETTINGS_PATH; - - if (!(await dependencies.fileSystem.fileExists(attemptingConfigPath))) { - return specifiedConfigPath === undefined - ? undefined - : { - configPath: attemptingConfigPath, - result: new Error( - `Could not find editor configuration under '${attemptingConfigPath}'.`, - ), - }; - } - - const result = await findRawConfiguration>( - dependencies.importer, - attemptingConfigPath, - ); - - return { - configPath: attemptingConfigPath, - result, - }; -}; diff --git a/src/input/findOriginalConfigurations.ts b/src/input/findOriginalConfigurations.ts index 654d7c5f5..2d2ae11ce 100644 --- a/src/input/findOriginalConfigurations.ts +++ b/src/input/findOriginalConfigurations.ts @@ -1,9 +1,9 @@ import { SansDependencies } from "../binding"; import { ConfigurationErrorResult, + ConfigurationLocations, ResultStatus, - ResultWithDataStatus, - TSLintToESLintSettings, + SucceededDataResult, } from "../types"; import { isDefined } from "../utils"; import { findESLintConfiguration, ESLintConfiguration } from "./findESLintConfiguration"; @@ -46,16 +46,19 @@ export type AllOriginalConfigurations = { typescript?: TypeScriptConfiguration; }; +/** + * Searches for all relevant input configurations on disk. + */ export const findOriginalConfigurations = async ( dependencies: FindOriginalConfigurationsDependencies, - rawSettings: TSLintToESLintSettings, -): Promise> => { + locations: ConfigurationLocations, +): Promise> => { // Simultaneously search for all required configuration types const [eslint, packages, tslint, typescript] = await Promise.all([ - dependencies.findESLintConfiguration(rawSettings), - dependencies.findPackagesConfiguration(rawSettings.package), - dependencies.findTSLintConfiguration(rawSettings.tslint), - dependencies.findTypeScriptConfiguration(rawSettings.typescript), + dependencies.findESLintConfiguration(locations), + dependencies.findPackagesConfiguration(locations.package), + dependencies.findTSLintConfiguration(locations.tslint), + dependencies.findTypeScriptConfiguration(locations.typescript), ]); // Out of those configurations, only TSLint's is always required to run @@ -86,7 +89,7 @@ export const findOriginalConfigurations = async ( } // * The user explicitly asked for them - if (typeof rawSettings[key] === "string") { + if (typeof locations[key] === "string") { return error.message; } diff --git a/src/input/vsCodeSettings.ts b/src/input/vsCodeSettings.ts deleted file mode 100644 index 6eef5583d..000000000 --- a/src/input/vsCodeSettings.ts +++ /dev/null @@ -1 +0,0 @@ -export const DEFAULT_VSCODE_SETTINGS_PATH = ".vscode/settings.json"; diff --git a/src/types.ts b/src/types.ts index 8763f2659..61b2f5eb8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,15 +1,13 @@ -export type TSLintToESLintSettings = { +/** + * Configuration file paths to read from. + */ +export type ConfigurationLocations = { /** * Output ESLint configuration file path, such as `.eslintrc.js`. */ config: string; - /** - * File globs to convert `tslint:disable` comments within to `eslint-disable`. - */ - comments?: true | string | string[]; - - /** + /* * Original Editor configuration file path(s), such as `.vscode/settings.json`. */ editor?: string | string[]; @@ -24,11 +22,6 @@ export type TSLintToESLintSettings = { */ package?: string; - /** - * Add `eslint-config-prettier` to the plugins list. - */ - prettier?: boolean; - /** * Original TSLint configuration file path, such as `tslint.json`. */ @@ -40,6 +33,31 @@ export type TSLintToESLintSettings = { typescript?: string; }; +/** + * Settings to find and convert configurations to an ESLint configuration. + */ +export type LintConfigConversionSettings = ConfigurationLocations & { + /** + * Whether to add `eslint-config-prettier` to the plugins list. + */ + prettier?: boolean; +}; + +/** + * Base settings to run conversions with. + */ +export type TSLintToESLintSettings = LintConfigConversionSettings & { + /** + * File globs to convert `tslint:disable` comments within to `eslint-disable`. + */ + comments?: true | string | string[]; + + /** + * Original Editor configuration file path, such as `.vscode/settings.json`. + */ + editor?: string; +}; + export type TSLintToESLintResult = ResultWithStatus; export enum ResultStatus {