From 216a504a16f02f66a90663a9deb7517e9a95d7e2 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 3 Oct 2020 23:45:18 -0400 Subject: [PATCH 1/8] Added Node API --- README.md | 12 + docs/API.md | 65 +++++ docs/Architecture/Linters.md | 11 +- docs/Dependencies.md | 2 +- jest.config.js | 1 + package.json | 1 + src/api/convertTSLintConfigStandalone.ts | 73 ++++++ src/api/dependencies.ts | 221 ++++++++++++++++ src/cli/main.ts | 219 +--------------- .../lintConfigs/convertLintConfig.test.ts | 22 +- .../lintConfigs/convertLintConfig.ts | 37 ++- .../createESLintConfiguration.test.ts | 26 ++ .../lintConfigs/createESLintConfiguration.ts | 30 +++ .../joinConfigConversionResults.test.ts | 199 +++++++++++++++ ...ults.ts => joinConfigConversionResults.ts} | 16 +- .../writeConfigConversionResults.test.ts | 240 ------------------ src/index.ts | 2 + src/input/findESLintConfiguration.ts | 4 +- src/input/findOriginalConfigurations.ts | 21 +- src/types.ts | 41 ++- 20 files changed, 713 insertions(+), 530 deletions(-) create mode 100644 docs/API.md create mode 100644 src/api/convertTSLintConfigStandalone.ts create mode 100644 src/api/dependencies.ts create mode 100644 src/converters/lintConfigs/createESLintConfiguration.test.ts create mode 100644 src/converters/lintConfigs/createESLintConfiguration.ts create mode 100644 src/converters/lintConfigs/joinConfigConversionResults.test.ts rename src/converters/lintConfigs/{writeConfigConversionResults.ts => joinConfigConversionResults.ts} (70%) delete mode 100644 src/converters/lintConfigs/writeConfigConversionResults.test.ts create mode 100644 src/index.ts 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..a5f947e8f --- /dev/null +++ b/docs/API.md @@ -0,0 +1,65 @@ +# 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. 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 ab87e6f1f..d9d88eb1e 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "prettier --write" ] }, + "main": "./src/index.js", "name": "tslint-to-eslint-config", "repository": { "type": "git", diff --git a/src/api/convertTSLintConfigStandalone.ts b/src/api/convertTSLintConfigStandalone.ts new file mode 100644 index 000000000..3f3bfe97a --- /dev/null +++ b/src/api/convertTSLintConfigStandalone.ts @@ -0,0 +1,73 @@ +import { createESLintConfiguration } from "../converters/lintConfigs/createESLintConfiguration"; +import { formatOutput } from "../converters/lintConfigs/formatting/formatOutput"; +import { + joinConfigConversionResults, + JoinedConversionResult, +} from "../converters/lintConfigs/joinConfigConversionResults"; +import { findOriginalConfigurations } from "../input/findOriginalConfigurations"; +import { + ConfigurationErrorResult, + LintConfigConversionSettings, + ResultStatus, + SucceededDataResult, +} from "../types"; +import { + createESLintConfigurationDependencies, + findOriginalConfigurationsDependencies, +} from "./dependencies"; + +/** + * 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 findOriginalConfigurations( + findOriginalConfigurationsDependencies, + settings, + ); + if (originalConfigurations.status !== ResultStatus.Succeeded) { + return originalConfigurations; + } + + const summarizedConfiguration = await createESLintConfiguration( + createESLintConfigurationDependencies, + originalConfigurations.data, + settings.prettier, + new Map(), + ); + + const output = joinConfigConversionResults( + summarizedConfiguration, + originalConfigurations.data, + ); + + return { + data: { + formatted: formatOutput(settings.config, output), + raw: output, + }, + status: ResultStatus.Succeeded, + }; +}; diff --git a/src/api/dependencies.ts b/src/api/dependencies.ts new file mode 100644 index 000000000..b7c12d48f --- /dev/null +++ b/src/api/dependencies.ts @@ -0,0 +1,221 @@ +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 { + ConvertEditorSettingsDependencies, + convertEditorSettings, +} from "../converters/editorConfigs/convertEditorSettings"; +import { editorSettingsConverters } from "../converters/editorConfigs/editorSettingsConverters"; +import { reportEditorSettingConversionResults } from "../converters/editorConfigs/reporting/reportEditorSettingConversionResults"; +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 { writeEditorConfigConversionResults } from "../converters/lintConfigs/writeEditorConfigConversionResults"; +import { removeExtendsDuplicatedRules } from "../converters/lintConfigs/pruning/removeExtendsDuplicatedRules"; +import { + ExtractGlobPathsDependencies, + extractGlobPaths, +} from "../converters/comments/extractGlobPaths"; +import { + findEditorConfiguration, + FindEditorConfigurationDependencies, +} from "../input/findEditorConfiguration"; +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"; +export const convertFileCommentsDependencies: ConvertFileCommentsDependencies = { + converters: ruleConverters, + fileSystem: fsFileSystem, +}; + +export const reportCommentResultsDependencies: ReportCommentResultsDependencies = { + logger: processLogger, +}; + +export const convertRulesDependencies: ConvertRulesDependencies = { + ruleConverters, + ruleMergers, +}; + +export const convertEditorSettingsDependencies: ConvertEditorSettingsDependencies = { + converters: editorSettingsConverters, +}; + +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 findEditorConfigurationDependencies: FindEditorConfigurationDependencies = { + fileSystem: fsFileSystem, + 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 = { + findEditorConfiguration: bind(findEditorConfiguration, findEditorConfigurationDependencies), + convertEditorSettings: bind(convertEditorSettings, convertEditorSettingsDependencies), + reportEditorSettingConversionResults: bind( + reportEditorSettingConversionResults, + reportEditorSettingConversionResultsDependencies, + ), + writeEditorConfigConversionResults: bind( + writeEditorConfigConversionResults, + writeConversionResultsDependencies, + ), +}; + +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(convertEditorConfig, convertEditorConfigDependencies), + bind(convertComments, convertCommentsDependencies), + ], + findOriginalConfigurations: bind( + findOriginalConfigurations, + findOriginalConfigurationsDependencies, + ), + logger: processLogger, +}; diff --git a/src/cli/main.ts b/src/cli/main.ts index 6064b82a9..58ab5e3e9 100644 --- a/src/cli/main.ts +++ b/src/cli/main.ts @@ -1,223 +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 { - ConvertEditorConfigDependencies, - convertEditorConfig, -} from "../converters/editorConfigs/convertEditorConfig"; -import { - ConvertEditorSettingsDependencies, - convertEditorSettings, -} from "../converters/editorConfigs/convertEditorSettings"; -import { editorSettingsConverters } from "../converters/editorConfigs/editorSettingsConverters"; -import { reportEditorSettingConversionResults } from "../converters/editorConfigs/reporting/reportEditorSettingConversionResults"; -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 { - 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 { writeEditorConfigConversionResults } from "../converters/lintConfigs/writeEditorConfigConversionResults"; -import { checkPrettierExtension } from "../converters/lintConfigs/summarization/prettier/checkPrettierExtension"; -import { removeExtendsDuplicatedRules } from "../converters/lintConfigs/pruning/removeExtendsDuplicatedRules"; -import { - ExtractGlobPathsDependencies, - extractGlobPaths, -} from "../converters/comments/extractGlobPaths"; -import { - findEditorConfiguration, - FindEditorConfigurationDependencies, -} from "../input/findEditorConfiguration"; -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 convertEditorSettingsDependencies: ConvertEditorSettingsDependencies = { - converters: editorSettingsConverters, -}; - -const nativeImporterDependencies: ImporterDependencies = { - fileSystem: fsFileSystem, - getCwd: () => process.cwd(), - nativeImporter: nativeImporter, -}; - -const boundImporter = bind(importer, nativeImporterDependencies); - -const findConfigurationDependencies = { - exec: childProcessExec, - importer: boundImporter, -}; - -const findEditorConfigurationDependencies: FindEditorConfigurationDependencies = { - fileSystem: fsFileSystem, - 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 reportEditorSettingConversionResultsDependencies = { - logger: processLogger, -}; - -const convertEditorConfigDependencies: ConvertEditorConfigDependencies = { - findEditorConfiguration: bind(findEditorConfiguration, findEditorConfigurationDependencies), - convertEditorSettings: bind(convertEditorSettings, convertEditorSettingsDependencies), - reportEditorSettingConversionResults: bind( - reportEditorSettingConversionResults, - reportEditorSettingConversionResultsDependencies, - ), - writeEditorConfigConversionResults: bind( - writeEditorConfigConversionResults, - writeConversionResultsDependencies, - ), -}; - -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(convertEditorConfig, convertEditorConfigDependencies), - 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..a70747e85 100644 --- a/src/converters/lintConfigs/writeConfigConversionResults.ts +++ b/src/converters/lintConfigs/joinConfigConversionResults.ts @@ -1,18 +1,10 @@ -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, +export const joinConfigConversionResults = ( summarizedResults: SummarizedConfigResultsConfiguration, originalConfigurations: AllOriginalConfigurations, ) => { @@ -23,7 +15,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 +28,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..3b2d33981 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export { convertTSLintConfigStandalone as convertTSLintConfig } from "./api/convertTSLintConfigStandalone"; +export * from "./types"; diff --git a/src/input/findESLintConfiguration.ts b/src/input/findESLintConfiguration.ts index 6024c3482..127603449 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"; @@ -36,7 +36,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/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/types.ts b/src/types.ts index a25919786..91c7ca804 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,43 +1,56 @@ -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`. + * Original ESLint configuration file path, such as `.eslintrc.js`. */ - comments?: true | string | string[]; + eslint?: string; /** - * Original Editor configuration file path, such as `.vscode/settings.json`. + * Original packages configuration file path, such as `package.json`. */ - editor?: string; + package?: string; /** - * Original ESLint configuration file path, such as `.eslintrc.js`. + * Original TSLint configuration file path, such as `tslint.json`. */ - eslint?: string; + tslint?: string; /** - * Original packages configuration file path, such as `package.json`. + * Original TypeScript configuration file path, such as `tsconfig.json`. */ - package?: string; + typescript?: string; +}; +/** + * Settings to find and convert configurations to an ESLint configuration. + */ +export type LintConfigConversionSettings = ConfigurationLocations & { /** - * Add `eslint-config-prettier` to the plugins list. + * Whether to add `eslint-config-prettier` to the plugins list. */ prettier?: boolean; +}; +/** + * Base settings to run conversions with. + */ +export type TSLintToESLintSettings = LintConfigConversionSettings & { /** - * Original TSLint configuration file path, such as `tslint.json`. + * File globs to convert `tslint:disable` comments within to `eslint-disable`. */ - tslint?: string; + comments?: true | string | string[]; /** - * Original TypeScript configuration file path, such as `tsconfig.json`. + * Original Editor configuration file path, such as `.vscode/settings.json`. */ - typescript?: string; + editor?: string; }; export type TSLintToESLintResult = ResultWithStatus; From 327cd65eed18fdece7395f0f4bcc84e280720509 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 14 Oct 2020 16:48:50 -0400 Subject: [PATCH 2/8] Expanded node API --- docs/API.md | 63 +++++++++++++++++++ src/adapters/fsFileSystem.ts | 3 + src/api/convertTSLintConfigStandalone.ts | 17 ++--- .../createESLintConfigurationStandalone.ts | 49 +++++++++++++++ .../findOriginalConfigurationsStandalone.ts | 19 ++++++ .../joinConfigConversionResults.ts | 3 + src/index.ts | 4 ++ 7 files changed, 145 insertions(+), 13 deletions(-) create mode 100644 src/api/createESLintConfigurationStandalone.ts create mode 100644 src/api/findOriginalConfigurationsStandalone.ts diff --git a/docs/API.md b/docs/API.md index a5f947e8f..97d06fdff 100644 --- a/docs/API.md +++ b/docs/API.md @@ -63,3 +63,66 @@ if (result.status !== ResultStatus.Succeeded) { ``` > 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. + +The individual steps within `convertTSLintConfig` are each available as exported functions as well. + +1. **[`findOriginalConfigurationsStandalone`](#findOriginalConfigurationsStandalone)** takes in an object of original configuration locations and retrieves their raw and computed contents. +2. **[`createESLintConfiguration`](#createESLintConfiguration)** creates an raw output ESLint configuration summary from those input configuration values. +3. `joinConfigConversionResults` turns a raw ESLint configuration summary into ESLint's configuration shape. +4. `formatOutput` prints that formatted output into a string per the output file extension. + +> Read the docs also dts yo +> Please file an issue on GitHub if you'd like to see changes. + +### `findOriginalConfigurationsStandalone` + +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", +}); +``` + +### `createESLintConfiguration` + +Generating an ESLint configuration from the contents of a local `tslint.json`: + +```ts +import { createESLintConfiguration } from "tslint-to-eslint-config"; + +const summarizedConfiguration = await createESLintConfiguration({ + tslint: { + 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/src/adapters/fsFileSystem.ts b/src/adapters/fsFileSystem.ts index 9740bcde9..49dab02da 100644 --- a/src/adapters/fsFileSystem.ts +++ b/src/adapters/fsFileSystem.ts @@ -8,6 +8,7 @@ const writeFile = promisify(fs.writeFile); export const fsFileSystem: FileSystem = { fileExists: async (filePath: string) => { + throw new Error("hi fileExists: " + filePath); try { return fs.existsSync(filePath); } catch (error) { @@ -15,6 +16,7 @@ export const fsFileSystem: FileSystem = { } }, readFile: async (filePath: string) => { + throw new Error("hi readFile: " + filePath); try { return (await readFile(filePath)).toString(); } catch (error) { @@ -22,6 +24,7 @@ export const fsFileSystem: FileSystem = { } }, writeFile: async (filePath: string, contents: string) => { + throw new Error("hi writeFile: " + filePath); try { return writeFile(filePath, contents); } catch (error) { diff --git a/src/api/convertTSLintConfigStandalone.ts b/src/api/convertTSLintConfigStandalone.ts index 3f3bfe97a..d829e79cc 100644 --- a/src/api/convertTSLintConfigStandalone.ts +++ b/src/api/convertTSLintConfigStandalone.ts @@ -1,20 +1,16 @@ -import { createESLintConfiguration } from "../converters/lintConfigs/createESLintConfiguration"; import { formatOutput } from "../converters/lintConfigs/formatting/formatOutput"; import { joinConfigConversionResults, JoinedConversionResult, } from "../converters/lintConfigs/joinConfigConversionResults"; -import { findOriginalConfigurations } from "../input/findOriginalConfigurations"; import { ConfigurationErrorResult, LintConfigConversionSettings, ResultStatus, SucceededDataResult, } from "../types"; -import { - createESLintConfigurationDependencies, - findOriginalConfigurationsDependencies, -} from "./dependencies"; +import { createESLintConfigurationStandalone } from "./createESLintConfigurationStandalone"; +import { findOriginalConfigurationsStandalone } from "./findOriginalConfigurationsStandalone"; /** * Resultant configuration data from converting a TSLint configuration. @@ -43,19 +39,14 @@ export const convertTSLintConfigStandalone = async ( ...rawSettings, config: ".eslintrc.js", }; - const originalConfigurations = await findOriginalConfigurations( - findOriginalConfigurationsDependencies, - settings, - ); + const originalConfigurations = await findOriginalConfigurationsStandalone(settings); if (originalConfigurations.status !== ResultStatus.Succeeded) { return originalConfigurations; } - const summarizedConfiguration = await createESLintConfiguration( - createESLintConfigurationDependencies, + const summarizedConfiguration = await createESLintConfigurationStandalone( originalConfigurations.data, settings.prettier, - new Map(), ); const output = joinConfigConversionResults( 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/findOriginalConfigurationsStandalone.ts b/src/api/findOriginalConfigurationsStandalone.ts new file mode 100644 index 000000000..d1ebb9bb6 --- /dev/null +++ b/src/api/findOriginalConfigurationsStandalone.ts @@ -0,0 +1,19 @@ +// export { formatOutput } from "../converters/lintConfigs/formatting/formatOutput"; +// export { joinConfigConversionResults } from "../converters/lintConfigs/joinConfigConversionResults"; +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/converters/lintConfigs/joinConfigConversionResults.ts b/src/converters/lintConfigs/joinConfigConversionResults.ts index a70747e85..e0fae65ec 100644 --- a/src/converters/lintConfigs/joinConfigConversionResults.ts +++ b/src/converters/lintConfigs/joinConfigConversionResults.ts @@ -4,6 +4,9 @@ import { createEnv } from "./eslint/createEnv"; import { formatConvertedRules } from "./formatConvertedRules"; import { SummarizedConfigResultsConfiguration } from "./summarization/types"; +/** + * Turns a raw ESLint configuration summary into ESLint's configuration shape. + */ export const joinConfigConversionResults = ( summarizedResults: SummarizedConfigResultsConfiguration, originalConfigurations: AllOriginalConfigurations, diff --git a/src/index.ts b/src/index.ts index 3b2d33981..884beb8e1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,6 @@ export { convertTSLintConfigStandalone as convertTSLintConfig } from "./api/convertTSLintConfigStandalone"; +export { createESLintConfigurationStandalone as createESLintConfiguration } from "./api/createESLintConfigurationStandalone"; +export { findOriginalConfigurationsStandalone as findOriginalConfigurations } from "./api/findOriginalConfigurationsStandalone"; +export { formatOutput } from "./converters/lintConfigs/formatting/formatOutput"; +export { joinConfigConversionResults } from "./converters/lintConfigs/joinConfigConversionResults"; export * from "./types"; From f494ba158c837cd2c13cadc9039ee8913617bc26 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 14 Oct 2020 16:52:53 -0400 Subject: [PATCH 3/8] Docs correction --- docs/API.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/API.md b/docs/API.md index 97d06fdff..fbe2b3d92 100644 --- a/docs/API.md +++ b/docs/API.md @@ -68,6 +68,7 @@ if (result.status !== ResultStatus.Succeeded) { > ⚠ 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. The individual steps within `convertTSLintConfig` are each available as exported functions as well. @@ -76,9 +77,6 @@ The individual steps within `convertTSLintConfig` are each available as exported 3. `joinConfigConversionResults` turns a raw ESLint configuration summary into ESLint's configuration shape. 4. `formatOutput` prints that formatted output into a string per the output file extension. -> Read the docs also dts yo -> Please file an issue on GitHub if you'd like to see changes. - ### `findOriginalConfigurationsStandalone` Reading in from the default file locations, including `.eslintrc.js`: From e012bc3bffb499a0ea1a51c16f97734c824e7b05 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 15 Oct 2020 17:07:25 -0400 Subject: [PATCH 4/8] Added findReportedConfiguration and improved its docs area --- docs/API.md | 26 ++++++++++++++----- .../findReportedConfigurationStandalone.ts | 12 +++++++++ src/index.ts | 1 + 3 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 src/api/findReportedConfigurationStandalone.ts diff --git a/docs/API.md b/docs/API.md index fbe2b3d92..5cf6cddea 100644 --- a/docs/API.md +++ b/docs/API.md @@ -70,14 +70,15 @@ if (result.status !== ResultStatus.Succeeded) { > Use at your own risk. > Please file an issue on GitHub if you'd like to see changes. -The individual steps within `convertTSLintConfig` are each available as exported functions as well. +Portions of the individual steps within `convertTSLintConfig` are each available as exported functions as well. -1. **[`findOriginalConfigurationsStandalone`](#findOriginalConfigurationsStandalone)** takes in an object of original configuration locations and retrieves their raw and computed contents. -2. **[`createESLintConfiguration`](#createESLintConfiguration)** creates an raw output ESLint configuration summary from those input configuration values. -3. `joinConfigConversionResults` turns a raw ESLint configuration summary into ESLint's configuration shape. -4. `formatOutput` prints that formatted output into a string per the output file extension. +* **[`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. -### `findOriginalConfigurationsStandalone` +### `findOriginalConfigurations` Reading in from the default file locations, including `.eslintrc.js`: @@ -98,15 +99,26 @@ const originalConfigurations = await findOriginalConfigurations({ }); ``` +#### `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 } from "tslint-to-eslint-config"; +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"), }, }); 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/index.ts b/src/index.ts index 884beb8e1..12bfb1a61 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +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"; From 1bdbe40db1549a4e2f94ac9c7138f30b7dcead8d Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 15 Oct 2020 17:35:24 -0400 Subject: [PATCH 5/8] Lol, removed file system shenanigans --- src/adapters/fsFileSystem.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/adapters/fsFileSystem.ts b/src/adapters/fsFileSystem.ts index 49dab02da..9740bcde9 100644 --- a/src/adapters/fsFileSystem.ts +++ b/src/adapters/fsFileSystem.ts @@ -8,7 +8,6 @@ const writeFile = promisify(fs.writeFile); export const fsFileSystem: FileSystem = { fileExists: async (filePath: string) => { - throw new Error("hi fileExists: " + filePath); try { return fs.existsSync(filePath); } catch (error) { @@ -16,7 +15,6 @@ export const fsFileSystem: FileSystem = { } }, readFile: async (filePath: string) => { - throw new Error("hi readFile: " + filePath); try { return (await readFile(filePath)).toString(); } catch (error) { @@ -24,7 +22,6 @@ export const fsFileSystem: FileSystem = { } }, writeFile: async (filePath: string, contents: string) => { - throw new Error("hi writeFile: " + filePath); try { return writeFile(filePath, contents); } catch (error) { From c8447434eba559fc597341b3fa22d593632ea449 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 15 Oct 2020 17:36:38 -0400 Subject: [PATCH 6/8] Remove some export comments --- src/api/findOriginalConfigurationsStandalone.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/api/findOriginalConfigurationsStandalone.ts b/src/api/findOriginalConfigurationsStandalone.ts index d1ebb9bb6..940c80799 100644 --- a/src/api/findOriginalConfigurationsStandalone.ts +++ b/src/api/findOriginalConfigurationsStandalone.ts @@ -1,5 +1,3 @@ -// export { formatOutput } from "../converters/lintConfigs/formatting/formatOutput"; -// export { joinConfigConversionResults } from "../converters/lintConfigs/joinConfigConversionResults"; import { findOriginalConfigurations } from "../input/findOriginalConfigurations"; import { ConfigurationLocations } from "../types"; import { findOriginalConfigurationsDependencies } from "./dependencies"; From 9be1186a6ac14a5ab3aeb4486b3b819e32b7c781 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 6 Nov 2020 20:35:13 -0500 Subject: [PATCH 7/8] Switched debug write stream to being lazily created --- src/adapters/processLogger.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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, }; From cb19a6517d9f54d2237aeb1f27e99de385e2b0b3 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 8 Nov 2020 11:33:47 -0500 Subject: [PATCH 8/8] rm unused vsCodeSettings file --- src/input/vsCodeSettings.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/input/vsCodeSettings.ts 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";