Skip to content

Commit

Permalink
Added Node API (#732)
Browse files Browse the repository at this point in the history
* Added Node API

* Expanded node API

* Docs correction

* Added findReportedConfiguration and improved its docs area

* Lol, removed file system shenanigans

* Remove some export comments

* Switched debug write stream to being lazily created

* rm unused vsCodeSettings file
  • Loading branch information
Josh Goldberg authored Nov 8, 2020
1 parent 9d55a89 commit 23dc93e
Show file tree
Hide file tree
Showing 27 changed files with 868 additions and 688 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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). 💖
138 changes: 138 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
@@ -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);
```
11 changes: 7 additions & 4 deletions docs/Architecture/Linters.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/Dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"prettier --write"
]
},
"main": "./src/index.js",
"name": "tslint-to-eslint-config",
"repository": {
"type": "git",
Expand Down
6 changes: 5 additions & 1 deletion src/adapters/processLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
64 changes: 64 additions & 0 deletions src/api/convertTSLintConfigStandalone.ts
Original file line number Diff line number Diff line change
@@ -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<LintConfigConversionSettings> = {},
): Promise<ConfigurationErrorResult | SucceededDataResult<TSLintConversionData>> => {
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,
};
};
49 changes: 49 additions & 0 deletions src/api/createESLintConfigurationStandalone.ts
Original file line number Diff line number Diff line change
@@ -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<OriginalConfigurations<ESLintConfiguration>>;
packages?: PackagesConfiguration;
tslint: Partial<OriginalConfigurations<TSLintConfiguration>>;
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<string, string[]>(),
);
};
Loading

0 comments on commit 23dc93e

Please sign in to comment.