Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(tests): enable suppressTypeErrors on all integration tests #1549

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,12 @@
"commander": "^12.0.0",
"enquirer": "^2.4.1",
"glob": "^10.3.10",
"minimatch": "^9.0.3",
"strip-ansi": "^7.1.0",
"ts-api-utils": "^1.3.0"
},
"devDependencies": {
"@types/eslint": "^8.56.6",
"@types/glob": "8.1.0",
"@types/minimatch": "^5.1.2",
"@types/node": "^20.11.30",
"@types/prop-types": "15.7.12",
"@types/react": "18.2.74",
Expand Down
6 changes: 0 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions src/cleanups/builtin/suppressTypeErrors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
DiagnosticWithStart,
getLineForDiagnostic,
isDiagnosticWithStart,
stringifyDiagnosticMessageText,
userFriendlyDiagnosticMessageText,
} from "../../../shared/diagnostics.js";
import { FileMutator } from "../../../shared/fileMutator.js";

Expand Down Expand Up @@ -33,8 +33,14 @@ export const suppressRemainingTypeIssues: FileMutator = (request) => {
}
}

const currentDir = request.services.program.getCurrentDirectory();

return Array.from(diagnosticsPerLine).map(([line, diagnostics]) => {
const messages = diagnostics.map(stringifyDiagnosticMessageText).join(" ");
const messages = diagnostics
.map((diagnostic) =>
userFriendlyDiagnosticMessageText(diagnostic, currentDir),
)
.join(" ");
return {
insertion: `// @ts-expect-error -- TODO: ${messages}\n`,
range: {
Expand Down
2 changes: 1 addition & 1 deletion src/cli/runCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const runCli = async (
let result: TypeStatResult;

try {
result = await runtime.mainRunner(rawOptions, runtime.output);
result = await runtime.mainRunner(rawOptions.config, runtime.output);
} catch (error) {
result = {
error: error instanceof Error ? error : new Error(error as string),
Expand Down
34 changes: 34 additions & 0 deletions src/collectFileNames.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import path from "node:path";
import { describe, expect, it } from "vitest";

import { collectFileNames } from "./collectFileNames.js";

describe("collectFileNames", () => {
it("should collect files with wildcard", async () => {
const cwd = path.resolve(import.meta.dirname, "..");
const fileNames = await collectFileNames(
path.resolve(import.meta.dirname),
["*"],
);
const names = Array.isArray(fileNames)
? (fileNames as string[]).map((item) => item.replace(cwd, "<rootDir>"))
: undefined;

// Assert
expect(names).toContain("<rootDir>/src/collectFileNames.test.ts");
});

it("return error if node modules are included", async () => {
const cwd = path.resolve(import.meta.dirname, "..");
const fileNames = await collectFileNames(cwd, ["*"]);

// Assert
const error =
typeof fileNames === "string"
? fileNames.replace(cwd, "<rootDir>")
: undefined;
expect(error).toEqual(
"At least one path including node_modules was included implicitly: '<rootDir>/node_modules'. Either adjust TypeStat's included files to not include node_modules (recommended) or explicitly include node_modules/ (not recommended).",
);
});
});
10 changes: 1 addition & 9 deletions src/collectFileNames.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { glob } from "glob";
import * as path from "node:path";

import { TypeStatArgv } from "./index.js";

export const collectFileNames = async (
argv: TypeStatArgv,
cwd: string,
include: readonly string[] | undefined,
): Promise<readonly string[] | string | undefined> => {
const globsAndNames = await collectFileNamesFromGlobs(argv, cwd, include);
const globsAndNames = await collectFileNamesFromGlobs(cwd, include);
if (!globsAndNames) {
return undefined;
}
Expand All @@ -27,14 +24,9 @@ export const collectFileNames = async (
};

const collectFileNamesFromGlobs = async (
argv: TypeStatArgv,
cwd: string,
include: readonly string[] | undefined,
): Promise<[readonly string[], readonly string[]] | undefined> => {
if (argv.args.length) {
return [argv.args, await glob(argv.args)];
}

if (include === undefined) {
return undefined;
}
Expand Down
11 changes: 5 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ export interface SucceededResult {
}

export const typeStat = async (
argv: TypeStatArgv,
configPath: string | undefined,
output: ProcessOutput,
): Promise<TypeStatResult> => {
const allPendingOptions = await tryLoadingPendingOptions(argv, output);
const allPendingOptions = await tryLoadingPendingOptions(configPath, output);
if (
allPendingOptions instanceof Error ||
typeof allPendingOptions === "string"
Expand Down Expand Up @@ -82,7 +82,7 @@ export const typeStat = async (
chalk.green(
` options ${pluralize(allPendingOptions.length, "object")} specified in `,
),
chalk.greenBright(argv.config),
chalk.greenBright(configPath),
chalk.green(` to modify your source code.`),
].join(""),
);
Expand All @@ -91,7 +91,6 @@ export const typeStat = async (
for (let i = 0; i < allPendingOptions.length; i += 1) {
// Collect all files to be run on this option iteration from the include glob(s)
const fileNames = await collectFileNames(
argv,
process.cwd(),
allPendingOptions[i].include,
);
Expand Down Expand Up @@ -146,11 +145,11 @@ export const typeStat = async (
};

const tryLoadingPendingOptions = async (
argv: TypeStatArgv,
configPath: string | undefined,
output: ProcessOutput,
): Promise<Error | PendingTypeStatOptions[] | string> => {
try {
return await loadPendingOptions(argv, output);
return await loadPendingOptions(configPath, output);
} catch (error) {
return error instanceof Error ? error : new Error(error as string);
}
Expand Down
31 changes: 15 additions & 16 deletions src/options/fillOutRawOptions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { TypeStatArgv } from "../index.js";
import { ProcessOutput } from "../output/types.js";
import { collectOptionals } from "../shared/arrays.js";
import { ReactPropTypesHint, ReactPropTypesOptionality } from "./enums.js";
import { ParsedCompilerOptions } from "./parseRawCompilerOptions.js";
import { ParsedTsConfig } from "./parseRawCompilerOptions.js";
import { collectAddedMutators } from "./parsing/collectAddedMutators.js";
import { collectFileOptions } from "./parsing/collectFileOptions.js";
import { collectNoImplicitAny } from "./parsing/collectNoImplicitAny.js";
Expand All @@ -12,31 +11,25 @@ import { collectStrictNullChecks } from "./parsing/collectStrictNullChecks.js";
import { PendingTypeStatOptions, RawTypeStatOptions } from "./types.js";

export interface OptionsFromRawOptionsSettings {
argv: TypeStatArgv;
compilerOptions: Readonly<ParsedCompilerOptions>;
cwd: string;
output: ProcessOutput;
projectPath: string;
rawOptions: RawTypeStatOptions;
tsConfig: Readonly<ParsedTsConfig>;
}

/**
* Combines Node and CLi argument options with project and file metadata into pending TypeStat options.
* @returns Parsed TypeStat options, or a string for an error complaint.
*/
export const fillOutRawOptions = ({
compilerOptions,
cwd,
output,
projectPath,
rawOptions,
tsConfig,
}: OptionsFromRawOptionsSettings): PendingTypeStatOptions => {
const rawOptionTypes = rawOptions.types ?? {};
const noImplicitAny = collectNoImplicitAny(compilerOptions, rawOptions);
const noImplicitThis = collectNoImplicitThis(compilerOptions, rawOptions);
const { compilerStrictNullChecks, typeStrictNullChecks } =
collectStrictNullChecks(compilerOptions, rawOptionTypes);

const packageOptions = collectPackageOptions(cwd, rawOptions);

const shell: (readonly string[])[] = [];
Expand All @@ -50,10 +43,16 @@ export const fillOutRawOptions = ({
...rawOptions.cleanups,
},
compilerOptions: {
...compilerOptions,
noImplicitAny,
noImplicitThis,
strictNullChecks: compilerStrictNullChecks,
...tsConfig.compilerOptions,
noImplicitAny: collectNoImplicitAny(tsConfig.compilerOptions, rawOptions),
noImplicitThis: collectNoImplicitThis(
tsConfig.compilerOptions,
rawOptions,
),
strictNullChecks: collectStrictNullChecks(
tsConfig.compilerOptions,
rawOptionTypes,
),
},
files: collectFileOptions(rawOptions),
filters: collectOptionals(rawOptions.filters),
Expand All @@ -76,7 +75,7 @@ export const fillOutRawOptions = ({
ReactPropTypesOptionality.AsWritten,
},
},
include: rawOptions.include ?? compilerOptions.include,
include: rawOptions.include ?? tsConfig.include,
mutators: collectAddedMutators(
rawOptions,
packageOptions.directory,
Expand All @@ -87,7 +86,7 @@ export const fillOutRawOptions = ({
postProcess: { shell },
projectPath,
types: {
strictNullChecks: typeStrictNullChecks,
strictNullChecks: rawOptionTypes.strictNullChecks,
},
};
};
14 changes: 5 additions & 9 deletions src/options/loadPendingOptions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as path from "node:path";

import { TypeStatArgv } from "../index.js";
import { ProcessOutput } from "../output/types.js";
import { normalizeAndSlashify } from "../shared/paths.js";
import { fillOutRawOptions } from "./fillOutRawOptions.js";
Expand All @@ -11,20 +10,18 @@ import { PendingTypeStatOptions, RawTypeStatOptions } from "./types.js";

/**
* Reads pre-file-rename TypeStat options using a config path.
* @param argv Root arguments passed to TypeStat.
* @param output Wraps process and logfile output.
* @returns Promise for filled-out TypeStat options, or a string complaint from failing to make them.
*/
export const loadPendingOptions = async (
argv: TypeStatArgv,
configPath: string | undefined,
output: ProcessOutput,
): Promise<PendingTypeStatOptions[] | string> => {
if (argv.config === undefined) {
if (configPath === undefined) {
return "-c/--config file must be provided.";
}

const cwd = process.cwd();
const foundRawOptions = findRawOptions(cwd, argv.config);
const foundRawOptions = findRawOptions(cwd, configPath);
if (typeof foundRawOptions === "string") {
return foundRawOptions;
}
Expand All @@ -37,15 +34,14 @@ export const loadPendingOptions = async (
for (let i = 0; i < allRawOptions.length; i += 1) {
const rawOptions = allRawOptions[i];
const projectPath = getProjectPath(cwd, filePath, rawOptions);
const compilerOptions = await parseRawCompilerOptions(cwd, projectPath);
const tsConfig = await parseRawCompilerOptions(cwd, projectPath);

const filledOutOptions = fillOutRawOptions({
argv,
compilerOptions,
cwd,
output,
projectPath,
rawOptions,
tsConfig,
});
const complaint = findComplaintForOptions(filledOutOptions);
if (complaint) {
Expand Down
37 changes: 9 additions & 28 deletions src/options/parseRawCompilerOptions.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,30 @@
import { glob } from "glob";
import { minimatch } from "minimatch";
import * as fs from "node:fs";
import * as fsp from "node:fs/promises";
import * as path from "node:path";
import ts from "typescript";

import { parseJsonConfigFileContent } from "../services/parseJsonConfigFileContent.js";
import { stringifyDiagnosticMessageText } from "../shared/diagnostics.js";

export interface ParsedCompilerOptions extends ts.CompilerOptions {
export interface ParsedTsConfig {
compilerOptions: ts.CompilerOptions | undefined;
include?: string[];
}

export const parseRawCompilerOptions = async (
cwd: string,
projectPath: string,
): Promise<ts.CompilerOptions> => {
): Promise<ParsedTsConfig> => {
const configRaw = (await fsp.readFile(projectPath)).toString();
const compilerOptions = ts.parseConfigFileTextToJson(projectPath, configRaw);
if (compilerOptions.error !== undefined) {
const configResult = ts.parseConfigFileTextToJson(projectPath, configRaw);
if (configResult.error !== undefined) {
throw new Error(
`Could not parse compiler options from '${projectPath}': ${stringifyDiagnosticMessageText(compilerOptions.error)}`,
`Could not parse compiler options from '${projectPath}': ${stringifyDiagnosticMessageText(configResult.error)}`,
);
}

const config = compilerOptions.config as ParsedCompilerOptions;
const config = configResult.config as ParsedTsConfig;

// TSConfig includes often come in a glob form like ["src"]
config.include &&= ts.parseJsonConfigFileContent(
compilerOptions.config,
{
fileExists: (filePath) => fs.statSync(filePath).isFile(),
readDirectory: (rootDir, extensions, excludes, includes) =>
includes
.flatMap((include) => glob.sync(path.join(rootDir, include)))
.filter(
(filePath) =>
!excludes?.some((exclude) => minimatch(filePath, exclude)) &&
extensions.some((extension) => filePath.endsWith(extension)),
)
.map((filePath) => path.relative(rootDir, filePath)),
readFile: (filePath) => fs.readFileSync(filePath).toString(),
useCaseSensitiveFileNames: true,
},
cwd,
).fileNames;
config.include &&= parseJsonConfigFileContent(config, cwd).fileNames;

return config;
};
16 changes: 4 additions & 12 deletions src/options/parsing/collectNoImplicitAny.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,8 @@ import ts from "typescript";
import { RawTypeStatOptions } from "../types.js";

export const collectNoImplicitAny = (
compilerOptions: Readonly<ts.CompilerOptions>,
compilerOptions: Readonly<ts.CompilerOptions> | undefined,
rawOptions: RawTypeStatOptions,
): boolean => {
if (rawOptions.fixes?.noImplicitAny !== undefined) {
return rawOptions.fixes.noImplicitAny;
}

if (compilerOptions.noImplicitAny !== undefined) {
return compilerOptions.noImplicitAny;
}

return false;
};
): boolean =>
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
rawOptions.fixes?.noImplicitAny || compilerOptions?.noImplicitAny || false;
Loading
Loading