Skip to content

Commit 23dc93e

Browse files
author
Josh Goldberg
authored
Added Node API (#732)
* 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
1 parent 9d55a89 commit 23dc93e

27 files changed

+868
-688
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,18 @@ _Default: `tsconfig.json`_
162162
Path to a TypeScript configuration file to read TypeScript compiler options from.
163163
This will help inform the generated ESLint configuration file's [env](https://eslint.org/docs/user-guide/configuring#specifying-parser-options) settings.
164164

165+
166+
## Node API
167+
168+
You can use `tslint-to-eslint-config` programmatically via its exported functions.
169+
See [docs/API](./docs/API.md) for details.
170+
171+
```ts
172+
import { convertLintConfig } from "tslint-to-eslint-config";
173+
174+
const result = await convertLintConfig();
175+
```
176+
165177
## Development
166178

167179
See the [Code of Conduct](./.github/CODE_OF_CONDUCT.md) and [general development docs](./docs/Development.md). 💖

docs/API.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# API
2+
3+
You can use `tslint-to-eslint-config` programmatically in your Node apps.
4+
It provides a **[`convertTSLintConfig`](#convertTSLintConfig)** function to find relevant configurations on disk and output the generated ESLint configuration.
5+
6+
## `convertTSLintConfig`
7+
8+
```ts
9+
import { convertTSLintConfig } from "tslint-to-eslint-config";
10+
11+
const result = await convertTSLintConfig();
12+
```
13+
14+
Finds relevant configurations on disk and outputs the generated ESLint configuration.
15+
16+
Optionally takes in the same settings you can provide via the CLI:
17+
18+
* `config`: Output ESLint configuration file path _(default: `.eslintrc.js`)_.
19+
* `eslint`: Original ESLint configuration file path _(default: `.eslintrc.js`)_.
20+
* `package`: Original packages configuration file path _(default: `package.json`)_.
21+
* `prettier`: Whether to add `eslint-config-prettier` to the plugins list.
22+
* `tslint`: Original TSLint configuration file path _(default: `tslint.json`)_.
23+
* `typescript`: Original TypeScript configuration file path _(default: `tsconfig.json`)_.
24+
25+
```ts
26+
import { convertTSLintConfig } from "tslint-to-eslint-config";
27+
28+
const result = await convertTSLintConfig({
29+
config: "./path/to/output/eslintrc.js",
30+
eslint: "./path/to/input/eslintrc.js",
31+
package: "./path/to/package.json",
32+
prettier: true, // Prettier: highly recommended!
33+
tslint: "./path/to/tslint.json",
34+
typescript: "./path/to/tsconfig.json",
35+
});
36+
```
37+
38+
If the TSLint configuration or any manually specified configurations fail to read from disk, the result will contain:
39+
40+
* `complaints`: String complaints describing the errors.
41+
* `status`: `ResultStatus.ConfigurationError` (`2`).
42+
43+
If no error is detected, the result will contain:
44+
45+
* `data`: Resultant ESLint configuration as:
46+
* `formatted`: Stringified result per the output config path's file type.
47+
* `raw`: Plain old JavaScript object.
48+
* `status`: `ResultStatus.Succeeded` (`0`).
49+
50+
```ts
51+
import { convertTSLintConfig, ResultStatus } from "tslint-to-eslint-config";
52+
53+
const result = await convertTSLintConfig({ /* ... */ });
54+
55+
if (result.status !== ResultStatus.Succeeded) {
56+
console.info("Oh no!");
57+
console.error(result.complaints.join("\n"));
58+
} else {
59+
console.info("Hooray!");
60+
console.log(result.data.formatted);
61+
console.log(result.data.raw);
62+
}
63+
```
64+
65+
> See the provided `.d.ts` TypeScript typings for full descriptions of inputs and outputs.
66+
67+
## Standalone API
68+
69+
> ⚠ This area of code is still considered experimental.
70+
> Use at your own risk.
71+
> Please file an issue on GitHub if you'd like to see changes.
72+
73+
Portions of the individual steps within `convertTSLintConfig` are each available as exported functions as well.
74+
75+
* **[`findOriginalConfigurations`](#findOriginalConfigurations)** takes in an object of original configuration locations and retrieves their raw and computed contents.
76+
* **[`findReportedConfiguration`](#findReportedConfiguration)** runs a config print command and parses its output as JSON.
77+
* **[`createESLintConfiguration`](#createESLintConfiguration)** creates an raw output ESLint configuration summary from those input configuration values.
78+
* `joinConfigConversionResults` turns a raw ESLint configuration summary into ESLint's configuration shape.
79+
* `formatOutput` prints that formatted output into a string per the output file extension.
80+
81+
### `findOriginalConfigurations`
82+
83+
Reading in from the default file locations, including `.eslintrc.js`:
84+
85+
```ts
86+
import { findOriginalConfigurations } from "tslint-to-eslint-config";
87+
88+
const originalConfigurations = await findOriginalConfigurations();
89+
```
90+
91+
Overriding some configuration file locations to read from:
92+
93+
```ts
94+
import { findOriginalConfigurations } from "tslint-to-eslint-config";
95+
96+
const originalConfigurations = await findOriginalConfigurations({
97+
config: "./path/to/.eslintrc.json",
98+
tslint: "./another/path/to/tslint.custom.json",
99+
});
100+
```
101+
102+
#### `findReportedConfiguration`
103+
104+
Retrieving the reported contents of a TSLint configuration:
105+
106+
```ts
107+
import { findReportedConfiguration } from "tslint-to-eslint-config";
108+
109+
const full = await findReportedConfiguration("npx tslint --print-config", "./tslint.json");
110+
```
111+
112+
### `createESLintConfiguration`
113+
114+
Generating an ESLint configuration from the contents of a local `tslint.json`:
115+
116+
```ts
117+
import { createESLintConfiguration, findReportedConfiguration } from "tslint-to-eslint-config";
118+
119+
const summarizedConfiguration = await createESLintConfiguration({
120+
tslint: {
121+
full: await findReportedConfiguration("npx tslint --print-config", "./tslint.json"),
122+
raw: require("./tslint.json"),
123+
},
124+
});
125+
```
126+
127+
Using the full configuration values from disk:
128+
129+
```ts
130+
import { createESLintConfiguration, findOriginalConfigurations } from "tslint-to-eslint-config";
131+
132+
const originalConfigurations = await findOriginalConfigurations();
133+
const summarizedConfiguration = await createESLintConfiguration(originalConfigurations);
134+
135+
const raw = joinConfigConversionResults(summarizedConfiguration, originalConfigurations.data);
136+
137+
const formatted = formatOutput("eslintrc.js", raw);
138+
```

docs/Architecture/Linters.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
TSLint-to-ESLint linter configuration conversion is the first root-level converter run.
44
Within `src/converters/lintConfigs/convertLintConfig.ts`, the following steps occur:
55

6-
1. Raw TSLint rules are mapped to their ESLint equivalents.
7-
2. Those ESLint equivalents are deduplicated and relevant preset(s) detected.
8-
3. Those deduplicated rules and metadata are written to the output configuration file.
9-
4. A summary of conversion results is printed, along with any now-missing packages.
6+
1. Deduplicated ESLint rules and metadata are generated from raw TSLint rules.
7+
1a. Raw TSLint rules are mapped to their ESLint equivalents.
8+
1b. Those ESLint equivalents are deduplicated and relevant preset(s) detected.
9+
2. Those deduplicated rules and metadata are written to the output configuration file.
10+
3. A summary of conversion results is printed, along with any now-missing packages.
11+
12+
> Stepss 1 and 2 are the logic exported by the [Node API](../API.md) as [`convertTSLintConfig`](../API.md#convertTSLintConfig).
1013
1114
## Rule Conversion
1215

docs/Dependencies.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Its dependencies object is manually created in `src/cli/main.ts` and bound to th
88
## When to Use Dependencies
99

1010
Most functions don't need a `dependencies` object.
11-
Only add one if something should be stubbed out during tests.
11+
Only add one if something should be stubbed out during tests _or_ should be available to multiple callers.
1212

1313
## How to Use Dependencies
1414

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module.exports = {
44
"!./src/**/*.d.ts",
55
"!./src/**/*.stubs.ts",
66
"!./src/adapters/*.ts",
7+
"!./src/api/*.ts",
78
"!./src/cli/main.ts",
89
"!./src/converters/editorConfigs/editorSettingsConverters.ts",
910
"!./src/converters/lintConfigs/rules/ruleConverters.ts",

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"prettier --write"
6363
]
6464
},
65+
"main": "./src/index.js",
6566
"name": "tslint-to-eslint-config",
6667
"repository": {
6768
"type": "git",

src/adapters/processLogger.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ import * as fs from "fs";
22

33
const debugFileName = "./tslint-to-eslint-config.log";
44

5+
let writeStream: fs.WriteStream | undefined;
6+
57
export const processLogger = {
68
debugFileName,
7-
info: fs.createWriteStream(debugFileName),
9+
get info() {
10+
return (writeStream ??= fs.createWriteStream(debugFileName));
11+
},
812
stderr: process.stderr,
913
stdout: process.stdout,
1014
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { formatOutput } from "../converters/lintConfigs/formatting/formatOutput";
2+
import {
3+
joinConfigConversionResults,
4+
JoinedConversionResult,
5+
} from "../converters/lintConfigs/joinConfigConversionResults";
6+
import {
7+
ConfigurationErrorResult,
8+
LintConfigConversionSettings,
9+
ResultStatus,
10+
SucceededDataResult,
11+
} from "../types";
12+
import { createESLintConfigurationStandalone } from "./createESLintConfigurationStandalone";
13+
import { findOriginalConfigurationsStandalone } from "./findOriginalConfigurationsStandalone";
14+
15+
/**
16+
* Resultant configuration data from converting a TSLint configuration.
17+
*/
18+
export type TSLintConversionData = {
19+
/**
20+
* Formatted configuration string per the output file's extension.
21+
*/
22+
formatted: string;
23+
24+
/**
25+
* Object description of the resultant configuration data.
26+
*/
27+
raw: JoinedConversionResult;
28+
};
29+
30+
/**
31+
* Finds relevant configurations on disk and outputs the generated ESLint configuration.
32+
*
33+
* @param settings - Settings to find and convert configurations to an ESLint configuration.
34+
*/
35+
export const convertTSLintConfigStandalone = async (
36+
rawSettings: Partial<LintConfigConversionSettings> = {},
37+
): Promise<ConfigurationErrorResult | SucceededDataResult<TSLintConversionData>> => {
38+
const settings = {
39+
...rawSettings,
40+
config: ".eslintrc.js",
41+
};
42+
const originalConfigurations = await findOriginalConfigurationsStandalone(settings);
43+
if (originalConfigurations.status !== ResultStatus.Succeeded) {
44+
return originalConfigurations;
45+
}
46+
47+
const summarizedConfiguration = await createESLintConfigurationStandalone(
48+
originalConfigurations.data,
49+
settings.prettier,
50+
);
51+
52+
const output = joinConfigConversionResults(
53+
summarizedConfiguration,
54+
originalConfigurations.data,
55+
);
56+
57+
return {
58+
data: {
59+
formatted: formatOutput(settings.config, output),
60+
raw: output,
61+
},
62+
status: ResultStatus.Succeeded,
63+
};
64+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { createESLintConfiguration } from "../converters/lintConfigs/createESLintConfiguration";
2+
import { ESLintConfiguration } from "../input/findESLintConfiguration";
3+
import {
4+
AllOriginalConfigurations,
5+
OriginalConfigurations,
6+
} from "../input/findOriginalConfigurations";
7+
import { PackagesConfiguration } from "../input/findPackagesConfiguration";
8+
import { TSLintConfiguration } from "../input/findTSLintConfiguration";
9+
import { TypeScriptConfiguration } from "../input/findTypeScriptConfiguration";
10+
import { createESLintConfigurationDependencies } from "./dependencies";
11+
12+
export type AllOriginalConfigurationsOptionally = {
13+
eslint?: Partial<OriginalConfigurations<ESLintConfiguration>>;
14+
packages?: PackagesConfiguration;
15+
tslint: Partial<OriginalConfigurations<TSLintConfiguration>>;
16+
typescript?: TypeScriptConfiguration;
17+
};
18+
19+
/**
20+
* Creates a raw output ESLint configuration summary from input configuration values.
21+
*
22+
* @param originalConfigurations
23+
* Any input configuration objects, including 'raw' (exact configuration file contents)
24+
* and 'full' (tool-reported computed values) for both ESLint and TSLint.
25+
* @param prettier
26+
* Whether to always consider the output configuration as extending from the Prettier
27+
* ruleset, instead of inferring it from computed rule values (recommended).
28+
*/
29+
export const createESLintConfigurationStandalone = async (
30+
originalConfigurations: AllOriginalConfigurations,
31+
prettier?: boolean,
32+
) => {
33+
const allOriginalConfigurations = { ...originalConfigurations };
34+
35+
if (allOriginalConfigurations.eslint) {
36+
allOriginalConfigurations.eslint.full ??= allOriginalConfigurations.eslint.raw;
37+
}
38+
39+
if (allOriginalConfigurations.tslint) {
40+
allOriginalConfigurations.tslint.full ??= allOriginalConfigurations.tslint.raw;
41+
}
42+
43+
return createESLintConfiguration(
44+
createESLintConfigurationDependencies,
45+
originalConfigurations,
46+
prettier,
47+
new Map<string, string[]>(),
48+
);
49+
};

0 commit comments

Comments
 (0)