Skip to content

Commit 35adb28

Browse files
4am and it works
1 parent bacf9e8 commit 35adb28

18 files changed

+306
-271
lines changed

.prettierrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
2+
"arrowParens": "avoid",
23
"printWidth": 100,
34
"tabWidth": 4,
45
"trailingComma": "all"
5-
}
6+
}

docs/Architecture.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ Within `src/conversion/convertConfig.ts`, the following steps occur:
1515
2. TSLint rules are converted into their ESLint configurations
1616
3. ESLint configurations are simplified based on extended ESLint and TSLint presets
1717
4. The simplified configuration is written to the output config file
18-
5. A summary of the results is printed to the user's console
18+
5. Files to transform comments in have source text rewritten using the same rule conversion logic
19+
6. A summary of the results is printed to the user's console
1920

2021
### Conversion Results
2122

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
@@ -12,6 +12,7 @@
1212
"dependencies": {
1313
"chalk": "4.0.0",
1414
"commander": "5.0.0",
15+
"glob": "^7.1.6",
1516
"strip-json-comments": "3.1.0",
1617
"tslint": "6.1.1",
1718
"typescript": "3.8.3"
@@ -22,6 +23,7 @@
2223
"@babel/plugin-proposal-optional-chaining": "7.9.0",
2324
"@babel/preset-env": "7.9.5",
2425
"@babel/preset-typescript": "7.9.0",
26+
"@types/glob": "^7.1.1",
2527
"@types/jest": "25.2.1",
2628
"@types/node": "12.12.21",
2729
"@typescript-eslint/eslint-plugin": "2.28.0",

src/adapters/fileSystem.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
import * as fs from "fs";
2-
3-
export type ReadDirOptions = { encoding?: string | null; withFileTypes: true };
4-
51
export type FileSystem = {
62
fileExists: (filePath: string) => Promise<boolean>;
73
readFile: (filePath: string) => Promise<Error | string>;
84
writeFile: (filePath: string, contents: string) => Promise<Error | undefined>;
9-
writeFileSync: (filePath: string, contents: string) => undefined;
10-
readDir: (dirPath: string, options: ReadDirOptions) => Promise<Error | fs.Dirent[]>;
115
};

src/adapters/fsFileSystem.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import * as fs from "fs";
22
import { promisify } from "util";
33

4-
import { FileSystem, ReadDirOptions } from "./fileSystem";
4+
import { FileSystem } from "./fileSystem";
55

66
const readFile = promisify(fs.readFile);
7-
const readDir = promisify(fs.readdir);
87
const writeFile = promisify(fs.writeFile);
9-
const writeFileSync = fs.writeFileSync;
108

119
export const fsFileSystem: FileSystem = {
1210
fileExists: async (filePath: string) => {
@@ -30,18 +28,4 @@ export const fsFileSystem: FileSystem = {
3028
return error;
3129
}
3230
},
33-
writeFileSync: (filePath: string, contents: string) => {
34-
try {
35-
return writeFileSync(filePath, contents);
36-
} catch (error) {
37-
return error;
38-
}
39-
},
40-
readDir: async (dirPath: string, options: ReadDirOptions) => {
41-
try {
42-
return readDir(dirPath, options);
43-
} catch (error) {
44-
return error;
45-
}
46-
},
4731
};

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: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,20 @@ 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";
15+
import {
16+
replaceFileComments,
17+
ReplaceFileCommentsDependencies,
18+
} from "../comments/replaceFileComments";
919
import {
1020
convertEditorConfig,
1121
ConvertEditorConfigDependencies,
@@ -50,7 +60,20 @@ import { convertRules, ConvertRulesDependencies } from "../rules/convertRules";
5060
import { mergers } from "../rules/mergers";
5161
import { rulesConverters } from "../rules/rulesConverters";
5262
import { runCli, RunCliDependencies } from "./runCli";
53-
import { convertComments, ConvertCommentsResultsDependencies } from "../rules/convertComments";
63+
64+
const replaceFileCommentsDependencies: ReplaceFileCommentsDependencies = {
65+
converters: rulesConverters,
66+
};
67+
68+
const convertFileCommentsDependencies: ConvertFileCommentsDependencies = {
69+
fileSystem: fsFileSystem,
70+
replaceFileComments: bind(replaceFileComments, replaceFileCommentsDependencies),
71+
};
72+
73+
const convertCommentsDependencies: ConvertCommentsDependencies = {
74+
convertFileComments: bind(convertFileComments, convertFileCommentsDependencies),
75+
globAsync,
76+
};
5477

5578
const convertRulesDependencies: ConvertRulesDependencies = {
5679
converters: rulesConverters,
@@ -100,10 +123,6 @@ const simplifyPackageRulesDependencies: SimplifyPackageRulesDependencies = {
100123
retrieveExtendsValues: bind(retrieveExtendsValues, retrieveExtendsValuesDependencies),
101124
};
102125

103-
const convertCommentsResultsDependencies: ConvertCommentsResultsDependencies = {
104-
fileSystem: fsFileSystem,
105-
};
106-
107126
const writeConversionResultsDependencies: WriteConversionResultsDependencies = {
108127
fileSystem: fsFileSystem,
109128
};
@@ -122,14 +141,14 @@ const convertEditorConfigDependencies: ConvertEditorConfigDependencies = {
122141
};
123142

124143
const convertConfigDependencies: ConvertConfigDependencies = {
144+
convertComments: bind(convertComments, convertCommentsDependencies),
125145
convertRules: bind(convertRules, convertRulesDependencies),
126146
findOriginalConfigurations: bind(
127147
findOriginalConfigurations,
128148
findOriginalConfigurationsDependencies,
129149
),
130150
reportConversionResults: bind(reportConversionResults, reportConversionResultsDependencies),
131151
simplifyPackageRules: bind(simplifyPackageRules, simplifyPackageRulesDependencies),
132-
convertComments: bind(convertComments, convertCommentsResultsDependencies),
133152
writeConversionResults: bind(writeConversionResults, writeConversionResultsDependencies),
134153
};
135154

src/cli/runCli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ export const runCli = async (
1919
): Promise<ResultStatus> => {
2020
const command = new Command()
2121
.usage("[options] <file ...> --language [language]")
22+
.option("--comments [files]", "convert tslint:disable comments in matching files")
2223
.option("--config [config]", "eslint configuration file to output to")
24+
.option("--editor [editor]", "editor configuration file to convert")
2325
.option("--eslint [eslint]", "eslint configuration file to convert using")
2426
.option("--package [package]", "package configuration file to convert using")
2527
.option("--tslint [tslint]", "tslint configuration file to convert using")
2628
.option("--typescript [typescript]", "typescript configuration file to convert using")
27-
.option("--editor [editor]", "editor configuration file to convert")
28-
.option("-C --convertComments", "convert all tslint:disable comments into eslint-disable")
2929
.option("-V --version", "output the package version");
3030

3131
const parsedArgv = {

src/comments/convertComments.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { GlobAsync } from "../adapters/globAsync";
2+
import { SansDependencies } from "../binding";
3+
import { ResultStatus, ResultWithStatus } 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+
export const convertComments = async (
13+
dependencies: ConvertCommentsDependencies,
14+
filePathGlobs: string | string[] | undefined,
15+
): Promise<ResultWithStatus> => {
16+
if (filePathGlobs === undefined) {
17+
return {
18+
status: ResultStatus.Succeeded,
19+
};
20+
}
21+
22+
const [fileGlobErrors, globbedFilePaths] = separateErrors(
23+
await Promise.all(uniqueFromSources(filePathGlobs).map(dependencies.globAsync)),
24+
);
25+
if (fileGlobErrors.length !== 0) {
26+
return {
27+
errors: fileGlobErrors,
28+
status: ResultStatus.Failed,
29+
};
30+
}
31+
32+
const ruleConversionCache = new Map<string, string | undefined>();
33+
const fileFailures = (
34+
await Promise.all(
35+
uniqueFromSources(...globbedFilePaths).map(async filePath =>
36+
dependencies.convertFileComments(filePath, ruleConversionCache),
37+
),
38+
)
39+
).filter(isError);
40+
if (fileFailures.length !== 0) {
41+
return {
42+
errors: fileFailures,
43+
status: ResultStatus.Failed,
44+
};
45+
}
46+
47+
return {
48+
status: ResultStatus.Succeeded,
49+
};
50+
};

src/comments/convertFileComments.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { FileSystem } from "../adapters/fileSystem";
2+
import { SansDependencies } from "../binding";
3+
import { parseFileComments } from "./parseFileComments";
4+
import { replaceFileComments } from "./replaceFileComments";
5+
6+
export type ConvertFileCommentsDependencies = {
7+
fileSystem: Pick<FileSystem, "readFile" | "writeFile">;
8+
replaceFileComments: SansDependencies<typeof replaceFileComments>;
9+
};
10+
11+
export const convertFileComments = async (
12+
dependencies: ConvertFileCommentsDependencies,
13+
filePath: string,
14+
ruleConversionCache: Map<string, string | undefined>,
15+
) => {
16+
const content = await dependencies.fileSystem.readFile(filePath);
17+
if (content instanceof Error) {
18+
return content;
19+
}
20+
21+
const comments = parseFileComments(filePath, content);
22+
const newFileContent = dependencies.replaceFileComments(content, comments, ruleConversionCache);
23+
24+
return await dependencies.fileSystem.writeFile(filePath, newFileContent);
25+
};

src/comments/parseFileComments.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import * as utils from "tsutils";
2+
import * as ts from "typescript";
3+
4+
export type FileComment = {
5+
commentKind: ts.CommentKind;
6+
directive: TSLintDirective;
7+
end: number;
8+
pos: number;
9+
ruleNames: string[];
10+
};
11+
12+
export type TSLintDirective =
13+
| "tslint:disable"
14+
| "tslint:disable-next-line"
15+
| "tslint:enable"
16+
| "tslint:enable-next-line";
17+
18+
export const parseFileComments = (filePath: string, content: string) => {
19+
// See https://github.com/Microsoft/TypeScript/issues/21049
20+
const sourceFile = ts.createSourceFile(
21+
filePath,
22+
content,
23+
ts.ScriptTarget.Latest,
24+
/*setParentNodes */ true,
25+
);
26+
const directives: FileComment[] = [];
27+
28+
utils.forEachComment(sourceFile, (fullText, comment) => {
29+
const parsedComment = parseFileComment(fullText, comment);
30+
if (parsedComment !== undefined) {
31+
directives.push(parsedComment);
32+
}
33+
});
34+
35+
return directives;
36+
};
37+
38+
/**
39+
* @see https://github.com/palantir/tslint/blob/master/src/enableDisableRules.ts
40+
*/
41+
const tslintRegex = new RegExp(/tslint:(enable|disable)(?:-(line|next-line))?(:|\s|$)/g);
42+
43+
const parseFileComment = (fullText: string, comment: ts.CommentRange): FileComment | undefined => {
44+
const commentText = (comment.kind === ts.SyntaxKind.SingleLineCommentTrivia
45+
? fullText.substring(comment.pos + 2, comment.end)
46+
: fullText.substring(comment.pos + 2, comment.end - 2)
47+
).trim();
48+
const match = tslintRegex.exec(commentText);
49+
if (match === null) {
50+
return undefined;
51+
}
52+
53+
return {
54+
commentKind: comment.kind,
55+
directive: match[0].replace("e:", "e") as TSLintDirective,
56+
end: comment.end,
57+
pos: comment.pos,
58+
ruleNames: commentText.slice(match[0].length).split(/\s+/).map(trimColons).filter(Boolean),
59+
};
60+
};
61+
62+
const trimColons = (text: string) =>
63+
text
64+
.replace(/^(:|\s)*/, "")
65+
.replace(/(:|\s)*$/, "")
66+
.trim();

0 commit comments

Comments
 (0)