diff --git a/README.md b/README.md index 577d0fbfa..64d5ee482 100644 --- a/README.md +++ b/README.md @@ -54,14 +54,22 @@ Each of these flags is optional: #### `comments` ```shell -npx tslint-to-eslint-config --comments 'src/**/*.ts' +npx tslint-to-eslint-config --comments ``` _Default: none_ -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. +Indicates to convert from [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). Comments such as `// tslint:disable: tslint-rule-name` will be converted to equivalents like `// eslint-disable eslint-rule-name`. +If passed without arguments, respects the `excludes`, `files`, and `includes` in your TypeScript configuration. + +Alternately, you can specify which files to convert comments in as globs: + +```shell +npx tslint-to-eslint-config --comments 'src/**/*.ts' +``` + #### `config` ```shell diff --git a/package.json b/package.json index 5923d156c..e8c7da026 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "commander": "6.1.0", "eslint-config-prettier": "6.11.0", "glob": "7.1.6", + "minimatch": "^3.0.4", "strip-json-comments": "3.1.1", "tslint": "6.1.3", "typescript": "4.0.2" @@ -27,6 +28,7 @@ "@types/eslint-config-prettier": "6.11.0", "@types/glob": "7.1.3", "@types/jest": "26.0.13", + "@types/minimatch": "^3.0.3", "@types/node": "12.12.21", "@typescript-eslint/eslint-plugin": "4.1.0", "@typescript-eslint/parser": "4.1.0", diff --git a/src/cli/runCli.ts b/src/cli/runCli.ts index e4fb6705b..f89f7b243 100644 --- a/src/cli/runCli.ts +++ b/src/cli/runCli.ts @@ -19,10 +19,7 @@ export const runCli = async ( const command = new Command() .storeOptionsAsProperties(false) .usage("[options] --language [language]") - .option( - "--comments [files]", - "convert tslint:disable rule flags in globbed files (experimental)", - ) + .option("--comments [files]", "convert tslint:disable rule flags in files (experimental)") .option("--config [config]", "eslint configuration file to output to") .option("--editor [editor]", "editor configuration file to convert") .option("--eslint [eslint]", "eslint configuration file to convert using") diff --git a/src/comments/convertComments.test.ts b/src/comments/convertComments.test.ts index 48d6b0f1c..ed17c7eb6 100644 --- a/src/comments/convertComments.test.ts +++ b/src/comments/convertComments.test.ts @@ -5,7 +5,7 @@ const createStubDependencies = ( overrides: Partial = {}, ): ConvertCommentsDependencies => ({ convertFileComments: jest.fn(), - globAsync: jest.fn().mockResolvedValue(["src/index.ts"]), + globAsync: jest.fn().mockResolvedValue(["src/a.ts", "src/b.ts"]), ...overrides, }); @@ -24,7 +24,7 @@ describe("convertComments", () => { }); }); - it("returns an error when --comments is given as a boolean value", async () => { + it("returns an error when --comments is given as a boolean value without a TypeScript configuration", async () => { // Arrange const dependencies = createStubDependencies(); @@ -38,6 +38,57 @@ describe("convertComments", () => { }); }); + it("includes TypeScript files when --comments is given as a boolean value with a TypeScript files configuration", async () => { + // Arrange + const dependencies = createStubDependencies({ + globAsync: jest.fn().mockResolvedValue(["src/a.ts"]), + }); + + // Act + const result = await convertComments(dependencies, true, { + files: ["src/a.ts"], + }); + + // Assert + expect(result).toEqual({ + data: ["src/a.ts"], + status: ResultStatus.Succeeded, + }); + }); + + it("includes TypeScript inclusions when --comments is given as a boolean value with a TypeScript include configuration", async () => { + // Arrange + const dependencies = createStubDependencies(); + + // Act + const result = await convertComments(dependencies, true, { + include: ["src/*.ts"], + }); + + // Assert + expect(result).toEqual({ + data: ["src/a.ts", "src/b.ts"], + status: ResultStatus.Succeeded, + }); + }); + + it("excludes TypeScript exclusions when --comments is given as a boolean value with a TypeScript excludes configuration", async () => { + // Arrange + const dependencies = createStubDependencies(); + + // Act + const result = await convertComments(dependencies, true, { + exclude: ["src/b.ts"], + include: ["src/*.ts"], + }); + + // Assert + expect(result).toEqual({ + data: ["src/a.ts"], + status: ResultStatus.Succeeded, + }); + }); + it("returns an error when there are no file path globs", async () => { // Arrange const dependencies = createStubDependencies(); @@ -95,7 +146,7 @@ describe("convertComments", () => { // Assert expect(result).toEqual({ - data: ["src/index.ts"], + data: ["src/a.ts", "src/b.ts"], status: ResultStatus.Succeeded, }); }); diff --git a/src/comments/convertComments.ts b/src/comments/convertComments.ts index 2332c0e28..4bf8ccef8 100644 --- a/src/comments/convertComments.ts +++ b/src/comments/convertComments.ts @@ -1,5 +1,8 @@ +import minimatch from "minimatch"; + import { GlobAsync } from "../adapters/globAsync"; import { SansDependencies } from "../binding"; +import { TypeScriptConfiguration } from "../input/findTypeScriptConfiguration"; import { ResultStatus, ResultWithDataStatus } from "../types"; import { separateErrors, uniqueFromSources, isError } from "../utils"; import { convertFileComments } from "./convertFileComments"; @@ -9,29 +12,48 @@ export type ConvertCommentsDependencies = { globAsync: GlobAsync; }; -const noGlobsResult: ResultWithDataStatus = { - errors: [new Error("--comments requires file path globs to be passed.")], - status: ResultStatus.Failed, -}; - export const convertComments = async ( dependencies: ConvertCommentsDependencies, filePathGlobs: true | string | string[] | undefined, + typescriptConfiguration?: TypeScriptConfiguration, ): Promise> => { + let fromTypeScriptConfiguration: TypeScriptConfiguration | undefined; + + if (filePathGlobs === true) { + if (!typescriptConfiguration) { + return { + errors: [ + new Error( + "--comments indicated to convert files listed in a tsconfig.json, but one was not found on disk or specified by with --typescript.", + ), + ], + status: ResultStatus.Failed, + }; + } + + filePathGlobs = [ + ...(typescriptConfiguration.files ?? []), + ...(typescriptConfiguration.include ?? []), + ]; + fromTypeScriptConfiguration = typescriptConfiguration; + } + if (filePathGlobs === undefined) { return { data: undefined, status: ResultStatus.Succeeded, }; } - - if (filePathGlobs === true) { - return noGlobsResult; - } - const uniqueFilePathGlobs = uniqueFromSources(filePathGlobs); if (uniqueFilePathGlobs.join("") === "") { - return noGlobsResult; + return { + errors: [ + new Error( + "--comments found no files. Consider passing no globs to it, to default to all TypeScript files.", + ), + ], + status: ResultStatus.Failed, + }; } const [fileGlobErrors, globbedFilePaths] = separateErrors( @@ -45,7 +67,13 @@ export const convertComments = async ( } const ruleConversionCache = new Map(); - const uniqueGlobbedFilePaths = uniqueFromSources(...globbedFilePaths); + const uniqueGlobbedFilePaths = uniqueFromSources(...globbedFilePaths).filter( + (filePathGlob) => + !fromTypeScriptConfiguration?.exclude?.some((exclude) => + minimatch(filePathGlob, exclude), + ), + ); + const fileFailures = ( await Promise.all( uniqueGlobbedFilePaths.map(async (filePath) => diff --git a/src/conversion/convertLintConfig.ts b/src/conversion/convertLintConfig.ts index 185ad27f6..435caf7ed 100644 --- a/src/conversion/convertLintConfig.ts +++ b/src/conversion/convertLintConfig.ts @@ -61,7 +61,10 @@ export const convertLintConfig = async ( } // 5. Files to transform comments in have source text rewritten using the same rule conversion logic - const commentsResult = await dependencies.convertComments(settings.comments); + const commentsResult = await dependencies.convertComments( + settings.comments, + originalConfigurations.data.typescript, + ); // 6. A summary of the results is printed to the user's console await dependencies.reportConversionResults(settings.config, summarizedConfiguration); diff --git a/src/input/findTypeScriptConfiguration.ts b/src/input/findTypeScriptConfiguration.ts index afc46e333..b5acc2459 100644 --- a/src/input/findTypeScriptConfiguration.ts +++ b/src/input/findTypeScriptConfiguration.ts @@ -8,6 +8,9 @@ export type TypeScriptConfiguration = { lib?: string[]; target?: string; }; + exclude?: string[]; + files?: string[]; + include?: string[]; }; const defaultTypeScriptConfiguration = { @@ -29,6 +32,7 @@ export const findTypeScriptConfiguration = async ( return rawConfiguration instanceof Error ? rawConfiguration : { + ...rawConfiguration, compilerOptions: { ...defaultTypeScriptConfiguration.compilerOptions, ...rawConfiguration.compilerOptions,