Skip to content

Commit 8847e54

Browse files
[Experimental Feature] Opt in flag convert disable comments (#246)
* Set basic replace comment conversion * Fix regex pattern and cli command name for convert comments * Fix regex pattern condition to match rules without carriage return * Replace regex and use tslint functions to retrieve tslint comment data * Switching rule handler Convert the input TSLint rule into equivalent ESLint * Write file after switching rule comments This is still a work in progress, need to check the start and end position of rule comments to avoid overwriting the wrong lines. * replace old path to rulesConverter * Working replacements for single line changes * 4am and it works * Revert .prettierrc testing change * Love that nullish operator * Comments WIP * Seems to be all working and tested now * Feature add: logged directive counts as a summary * Docs in CLI and README.md * Filled in basic test for separateErrors * Normalized output per new norms Co-authored-by: Josh Goldberg <[email protected]>
1 parent 3c6fdb6 commit 8847e54

23 files changed

+804
-30
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ We **strongly** advise reading [docs/FAQs.md](./docs/FAQs.md) before planning yo
4242

4343
Each of these flags is optional:
4444

45+
- **[`comments`](#comments)**: File glob path(s) to convert TSLint rule flags to ESLint within.
4546
- **[`config`](#config)**: Path to print the generated ESLint configuration file to.
4647
- **[`editor`](#editor)**: Path to an editor configuration file to convert linter settings within.
4748
- **[`eslint`](#eslint)**: Path to an ESLint configuration file to read settings from.
@@ -50,6 +51,17 @@ Each of these flags is optional:
5051
- **[`tslint`](#tslint)**: Path to a TSLint configuration file to read settings from.
5152
- **[`typescript`](#typescript)**: Path to a TypeScript configuration file to read TypeScript compiler options from.
5253

54+
#### `comments`
55+
56+
```shell
57+
npx tslint-to-eslint-config --comments src/**/*.ts
58+
```
59+
60+
_Default: none_
61+
62+
File glob path(s) to convert [TSLint rule flags](https://palantir.github.io/tslint/usage/rule-flags) to [ESLint inline comments](https://eslint.org/docs/user-guide/configuring#disabling-rules-with-inline-comments) in.
63+
Comments such as `// tslint:disable: tslint-rule-name` will be converted to equivalents like `// eslint-disable eslint-rule-name`.
64+
5365
#### `config`
5466

5567
```shell

docs/Architecture.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ Within `src/conversion/convertConfig.ts`, the following steps occur:
1616
3. ESLint configurations are simplified based on extended ESLint and TSLint presets
1717
- 3a. If no output rules conflict with `eslint-config-prettier`, it's added in
1818
4. The simplified configuration is written to the output config file
19-
5. A summary of the results is printed to the user's console
19+
5. Files to transform comments in have source text rewritten using the same rule conversion logic
20+
6. A summary of the results is printed to the user's console
2021

2122
### Conversion Results
2223

package-lock.json

Lines changed: 26 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"chalk": "4.0.0",
1414
"commander": "5.1.0",
1515
"eslint-config-prettier": "6.11.0",
16+
"glob": "7.1.6",
1617
"strip-json-comments": "3.1.0",
1718
"tslint": "6.1.2",
1819
"typescript": "3.8.3"
@@ -23,6 +24,7 @@
2324
"@babel/plugin-proposal-optional-chaining": "7.9.0",
2425
"@babel/preset-env": "7.9.5",
2526
"@babel/preset-typescript": "7.9.0",
27+
"@types/glob": "^7.1.1",
2628
"@types/jest": "25.2.1",
2729
"@types/node": "12.12.21",
2830
"@typescript-eslint/eslint-plugin": "2.29.0",

src/adapters/fileSystem.stub.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,3 @@ export const createStubFileSystem = ({ data = {}, exists = true } = {}) => ({
33
readFile: jest.fn().mockReturnValue(Promise.resolve(data)),
44
writeFile: jest.fn(),
55
});
6-
7-
export const createStubThrowingFileSystem = ({ err = "" } = {}) => ({
8-
fileExists: jest.fn().mockRejectedValue(Promise.resolve(new Error(err))),
9-
readFile: jest.fn().mockRejectedValue(Promise.resolve(new Error(err))),
10-
writeFile: jest.fn().mockRejectedValue(Promise.resolve(new Error(err))),
11-
});

src/adapters/globAsync.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import glob from "glob";
2+
3+
export const globAsync = async (pattern: string) => {
4+
return new Promise<string[] | Error>((resolve) => {
5+
glob(pattern, (error, matches) => {
6+
resolve(error ?? matches);
7+
});
8+
});
9+
};
10+
11+
export type GlobAsync = typeof globAsync;

src/cli/main.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@ import { EOL } from "os";
22

33
import { childProcessExec } from "../adapters/childProcessExec";
44
import { fsFileSystem } from "../adapters/fsFileSystem";
5+
import { globAsync } from "../adapters/globAsync";
56
import { nativeImporter } from "../adapters/nativeImporter";
67
import { processLogger } from "../adapters/processLogger";
78
import { bind } from "../binding";
9+
import { convertComments, ConvertCommentsDependencies } from "../comments/convertComments";
10+
import {
11+
ConvertFileCommentsDependencies,
12+
convertFileComments,
13+
} from "../comments/convertFileComments";
814
import { convertConfig, ConvertConfigDependencies } from "../conversion/convertConfig";
915
import {
1016
convertEditorConfig,
@@ -45,9 +51,13 @@ import { findTypeScriptConfiguration } from "../input/findTypeScriptConfiguratio
4551
import { importer, ImporterDependencies } from "../input/importer";
4652
import { mergeLintConfigurations } from "../input/mergeLintConfigurations";
4753
import {
48-
ChoosePackageManagerDependencies,
4954
choosePackageManager,
55+
ChoosePackageManagerDependencies,
5056
} from "../reporting/packages/choosePackageManager";
57+
import {
58+
reportCommentResults,
59+
ReportCommentResultsDependencies,
60+
} from "../reporting/reportCommentResults";
5161
import {
5262
logMissingPackages,
5363
LogMissingPackagesDependencies,
@@ -62,6 +72,16 @@ import { mergers } from "../rules/mergers";
6272
import { rulesConverters } from "../rules/rulesConverters";
6373
import { runCli, RunCliDependencies } from "./runCli";
6474

75+
const convertFileCommentsDependencies: ConvertFileCommentsDependencies = {
76+
converters: rulesConverters,
77+
fileSystem: fsFileSystem,
78+
};
79+
80+
const convertCommentsDependencies: ConvertCommentsDependencies = {
81+
convertFileComments: bind(convertFileComments, convertFileCommentsDependencies),
82+
globAsync,
83+
};
84+
6585
const convertRulesDependencies: ConvertRulesDependencies = {
6686
converters: rulesConverters,
6787
mergers,
@@ -97,6 +117,10 @@ const findOriginalConfigurationsDependencies: FindOriginalConfigurationsDependen
97117
mergeLintConfigurations,
98118
};
99119

120+
const reportCommentResultsDependencies: ReportCommentResultsDependencies = {
121+
logger: processLogger,
122+
};
123+
100124
const choosePackageManagerDependencies: ChoosePackageManagerDependencies = {
101125
fileSystem: fsFileSystem,
102126
};
@@ -124,12 +148,16 @@ const writeConversionResultsDependencies: WriteConversionResultsDependencies = {
124148
fileSystem: fsFileSystem,
125149
};
126150

151+
const reportEditorSettingConversionResultsDependencies = {
152+
logger: processLogger,
153+
};
154+
127155
const convertEditorConfigDependencies: ConvertEditorConfigDependencies = {
128156
findEditorConfiguration: bind(findEditorConfiguration, findEditorConfigurationDependencies),
129157
convertEditorSettings: bind(convertEditorSettings, convertEditorSettingsDependencies),
130158
reportConversionResults: bind(
131159
reportEditorSettingConversionResults,
132-
reportConversionResultsDependencies,
160+
reportEditorSettingConversionResultsDependencies,
133161
),
134162
writeConversionResults: bind(
135163
writeEditorConfigConversionResults,
@@ -138,12 +166,14 @@ const convertEditorConfigDependencies: ConvertEditorConfigDependencies = {
138166
};
139167

140168
const convertConfigDependencies: ConvertConfigDependencies = {
169+
convertComments: bind(convertComments, convertCommentsDependencies),
141170
convertRules: bind(convertRules, convertRulesDependencies),
142171
findOriginalConfigurations: bind(
143172
findOriginalConfigurations,
144173
findOriginalConfigurationsDependencies,
145174
),
146175
logMissingPackages: bind(logMissingPackages, logMissingPackagesDependencies),
176+
reportCommentResults: bind(reportCommentResults, reportCommentResultsDependencies),
147177
reportConversionResults: bind(reportConversionResults, reportConversionResultsDependencies),
148178
simplifyPackageRules: bind(simplifyPackageRules, simplifyPackageRulesDependencies),
149179
writeConversionResults: bind(writeConversionResults, writeConversionResultsDependencies),

src/cli/runCli.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export const runCli = async (
1919
): Promise<ResultStatus> => {
2020
const command = new Command()
2121
.usage("[options] <file ...> --language [language]")
22+
.option(
23+
"--comments [files]",
24+
"convert tslint:disable rule flags in globbed files (experimental)",
25+
)
2226
.option("--config [config]", "eslint configuration file to output to")
2327
.option("--editor [editor]", "editor configuration file to convert")
2428
.option("--eslint [eslint]", "eslint configuration file to convert using")

src/comments/convertComments.test.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { ResultStatus } from "../types";
2+
import { convertComments, ConvertCommentsDependencies } from "./convertComments";
3+
4+
const createStubDependencies = (
5+
overrides: Partial<ConvertCommentsDependencies> = {},
6+
): ConvertCommentsDependencies => ({
7+
convertFileComments: jest.fn(),
8+
globAsync: jest.fn().mockResolvedValue(["src/index.ts"]),
9+
...overrides,
10+
});
11+
12+
describe("convertComments", () => {
13+
it("returns an error when --comment is given as a boolean value", async () => {
14+
// Arrange
15+
const dependencies = createStubDependencies();
16+
17+
// Act
18+
const result = await convertComments(dependencies, true);
19+
20+
// Assert
21+
expect(result).toEqual({
22+
errors: expect.arrayContaining([expect.any(Error)]),
23+
status: ResultStatus.Failed,
24+
});
25+
});
26+
27+
it("returns an error when there are no file path globs", async () => {
28+
// Arrange
29+
const dependencies = createStubDependencies();
30+
31+
// Act
32+
const result = await convertComments(dependencies, []);
33+
34+
// Assert
35+
expect(result).toEqual({
36+
errors: expect.arrayContaining([expect.any(Error)]),
37+
status: ResultStatus.Failed,
38+
});
39+
});
40+
41+
it("returns the failure result when a file path glob fails", async () => {
42+
// Arrange
43+
const globAsyncError = new Error();
44+
const dependencies = createStubDependencies({
45+
globAsync: jest.fn().mockResolvedValueOnce(globAsyncError),
46+
});
47+
48+
// Act
49+
const result = await convertComments(dependencies, ["*.ts"]);
50+
51+
// Assert
52+
expect(result).toEqual({
53+
errors: [globAsyncError],
54+
status: ResultStatus.Failed,
55+
});
56+
});
57+
58+
it("returns the failure result when a file conversion fails", async () => {
59+
// Arrange
60+
const fileConversionError = new Error();
61+
const dependencies = createStubDependencies({
62+
convertFileComments: jest.fn().mockResolvedValueOnce(fileConversionError),
63+
});
64+
65+
// Act
66+
const result = await convertComments(dependencies, ["*.ts"]);
67+
68+
// Assert
69+
expect(result).toEqual({
70+
errors: [fileConversionError],
71+
status: ResultStatus.Failed,
72+
});
73+
});
74+
75+
it("returns a success result when all steps succeed", async () => {
76+
// Arrange
77+
const dependencies = createStubDependencies();
78+
79+
// Act
80+
const result = await convertComments(dependencies, ["*.ts"]);
81+
82+
// Assert
83+
expect(result).toEqual({
84+
data: ["src/index.ts"],
85+
status: ResultStatus.Succeeded,
86+
});
87+
});
88+
});

src/comments/convertComments.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { GlobAsync } from "../adapters/globAsync";
2+
import { SansDependencies } from "../binding";
3+
import { ResultStatus, ResultWithDataStatus } from "../types";
4+
import { separateErrors, uniqueFromSources, isError } from "../utils";
5+
import { convertFileComments } from "./convertFileComments";
6+
7+
export type ConvertCommentsDependencies = {
8+
convertFileComments: SansDependencies<typeof convertFileComments>;
9+
globAsync: GlobAsync;
10+
};
11+
12+
const noGlobsResult: ResultWithDataStatus<string[]> = {
13+
errors: [new Error("--comment requires file path globs to be passed.")],
14+
status: ResultStatus.Failed,
15+
};
16+
17+
export const convertComments = async (
18+
dependencies: ConvertCommentsDependencies,
19+
filePathGlobs: true | string | string[] | undefined,
20+
): Promise<ResultWithDataStatus<string[]>> => {
21+
if (filePathGlobs === true) {
22+
return noGlobsResult;
23+
}
24+
25+
const uniqueFilePathGlobs = uniqueFromSources(filePathGlobs);
26+
if (uniqueFilePathGlobs.join("") === "") {
27+
return noGlobsResult;
28+
}
29+
30+
const [fileGlobErrors, globbedFilePaths] = separateErrors(
31+
await Promise.all(uniqueFilePathGlobs.map(dependencies.globAsync)),
32+
);
33+
if (fileGlobErrors.length !== 0) {
34+
return {
35+
errors: fileGlobErrors,
36+
status: ResultStatus.Failed,
37+
};
38+
}
39+
40+
const ruleConversionCache = new Map<string, string | undefined>();
41+
const uniqueGlobbedFilePaths = uniqueFromSources(...globbedFilePaths);
42+
const fileFailures = (
43+
await Promise.all(
44+
uniqueGlobbedFilePaths.map(async (filePath) =>
45+
dependencies.convertFileComments(filePath, ruleConversionCache),
46+
),
47+
)
48+
).filter(isError);
49+
if (fileFailures.length !== 0) {
50+
return {
51+
errors: fileFailures,
52+
status: ResultStatus.Failed,
53+
};
54+
}
55+
56+
return {
57+
data: uniqueGlobbedFilePaths,
58+
status: ResultStatus.Succeeded,
59+
};
60+
};

0 commit comments

Comments
 (0)