Skip to content

Commit

Permalink
feat: add post-fix cleanups (#1233)
Browse files Browse the repository at this point in the history
* feat: add post-fix cleanups

* Standardized infra a bit
  • Loading branch information
JoshuaKGoldberg authored Jun 8, 2023
1 parent 9334294 commit dfa87d3
Show file tree
Hide file tree
Showing 108 changed files with 497 additions and 119 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ To get a deeper understanding of TypeStat, read the following docs pages in orde
1. **[Usage.md](./docs/Usage.md)** for an explanation of how TypeStat works
2. **[Fixes.md](./docs/Fixes.md)** for the type of fixes TypeStat will generate mutations for
3. **[Types.md](./docs/Types.md)** for configuring how to work with types in mutations
4. **[Filters.md](./docs/Filters.md)** for using [tsquery](https://github.com/phenomnomnominal/tsquery) to ignore sections of source files
5. **[Custom Mutators.md](./docs/Custom%20Mutators.md)** for including or creating custom mutators
3. **[Cleanups.md](./docs/Cleanups.md)** for the post-fix cleaning TypeStat may apply to files
4. **[Types.md](./docs/Types.md)** for configuring how to work with types in mutations
5. **[Filters.md](./docs/Filters.md)** for using [tsquery](https://github.com/phenomnomnominal/tsquery) to ignore sections of source files
6. **[Custom Mutators.md](./docs/Custom%20Mutators.md)** for including or creating custom mutators
## Development
Expand Down
23 changes: 23 additions & 0 deletions docs/Cleanups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Cleanups

After TypeStat has applied all the fixes it can to files, there may still be some general cleanups that need to be applied.
Most commonly, any remaining TypeScript type errors may need to be suppressed with `// @ts-expect-error`.

Each classification of cleanups can be individually configured in your `typestat.json` file.
These all default to `false` but can be enabled by being set to `true`.

```json
{
"cleanups": {
"suppressTypeErrors": true
}
}
```

## Cleaners

### `suppressTypeErrors`

Whether to add a `// @ts-expect-error` comment directive before each remaining type error.

See [suppressTypeErrors/README.md](../src/cleanups/builtin/suppressTypeErrors/README.md).
26 changes: 26 additions & 0 deletions src/cleanups/builtin/suppressTypeErrors/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# `suppressTypeErrors`

Whether to add a `// @ts-expect-error` comment directive before each remaining type error.

## Use Cases

* Your existing code has type errors that are too complex or context-dependent for TypeStat to clean up.

## Configuration

```json
{
"cleanups": {
"suppressTypeErrors": true
}
}
```

## Mutations

For each type error still remaining in files, a `// @ts-expect-error` comment will be added with the text of the error:

```diff
+ // @ts-expect-error: Type 'number' is not assignable to type 'string'.
let incorrect: string = 0;
```
44 changes: 44 additions & 0 deletions src/cleanups/builtin/suppressTypeErrors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as ts from "typescript";

import {
DiagnosticWithStart,
getLineForDiagnostic,
isDiagnosticWithStart,
stringifyDiagnosticMessageText,
} from "../../../shared/diagnostics";
import { FileMutator } from "../../../shared/fileMutator";

export const suppressRemainingTypeIssues: FileMutator = (request) => {
if (!request.options.cleanups.suppressTypeErrors) {
return undefined;
}

const allDiagnostics = request.services.program.getSemanticDiagnostics(request.sourceFile).filter(isDiagnosticWithStart);
if (!allDiagnostics.length) {
return undefined;
}

const diagnosticsPerLine = new Map<number, DiagnosticWithStart[]>();

for (const diagnostic of allDiagnostics) {
const line = getLineForDiagnostic(diagnostic, request.sourceFile);
const existing = diagnosticsPerLine.get(line);

if (existing) {
existing.push(diagnostic);
} else {
diagnosticsPerLine.set(line, [diagnostic]);
}
}

return Array.from(diagnosticsPerLine).map(([line, diagnostics]) => {
const messages = diagnostics.map(stringifyDiagnosticMessageText).join(" ");
return {
insertion: `// @ts-expect-error -- TODO: ${messages}\n`,
range: {
begin: ts.getPositionOfLineAndCharacter(request.sourceFile, line, 0),
},
type: "text-insert",
};
});
};
20 changes: 20 additions & 0 deletions src/initialization/initializeJavaScript/cleanups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { prompt } from "enquirer";

export enum InitializationCleanups {
No = "No",
Yes = "Yes",
}

export const initializeCleanups = async () => {
const { cleanups } = await prompt<{ cleanups: InitializationCleanups }>([
{
choices: [InitializationCleanups.No, InitializationCleanups.Yes],
initial: 1,
message: "Would you like to suppress remaining type errors with // @ts-expect-error comments?",
name: "cleanups",
type: "select",
},
]);

return cleanups;
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { InitializationImports } from "./imports";
import { InitializationRenames } from "./renames";
import { createJavaScriptConfig } from "./createJavaScriptConfig";
import { InitializationCleanups } from "./cleanups";

describe(createJavaScriptConfig, () => {
test.each([
Expand All @@ -12,6 +13,24 @@ describe(createJavaScriptConfig, () => {
},
name: "Basic",
settings: {
cleanups: InitializationCleanups.No,
imports: InitializationImports.No,
project: {
filePath: "tsconfig.json",
},
renames: InitializationRenames.TS,
},
},
{
expected: {
files: { renameExtensions: "ts" },
fixes: { incompleteTypes: true, missingProperties: true, noImplicitAny: true },
cleanups: { suppressTypeErrors: true },
project: "tsconfig.json",
},
name: "Basic with Suppressions",
settings: {
cleanups: InitializationCleanups.Yes,
imports: InitializationImports.No,
project: {
filePath: "tsconfig.json",
Expand All @@ -36,6 +55,7 @@ describe(createJavaScriptConfig, () => {
name: "TS Renames (multiple sourceFiles extensions)",
settings: {
imports: InitializationImports.Yes,
cleanups: InitializationCleanups.No,
project: {
filePath: "tsconfig.json",
},
Expand All @@ -60,6 +80,7 @@ describe(createJavaScriptConfig, () => {
name: "TSX Renames (multiple sourceFiles extensions)",
settings: {
imports: InitializationImports.Yes,
cleanups: InitializationCleanups.No,
project: {
filePath: "tsconfig.json",
},
Expand All @@ -74,6 +95,7 @@ describe(createJavaScriptConfig, () => {
],
name: "Auto Renames (no sourceFiles)",
settings: {
cleanups: InitializationCleanups.No,
imports: InitializationImports.Yes,
project: {
filePath: "tsconfig.json",
Expand All @@ -98,6 +120,7 @@ describe(createJavaScriptConfig, () => {
name: "Auto Renames (single sourceFiles extension)",
settings: {
imports: InitializationImports.Yes,
cleanups: InitializationCleanups.No,
project: {
filePath: "tsconfig.json",
},
Expand All @@ -122,6 +145,7 @@ describe(createJavaScriptConfig, () => {
name: "Auto Renames (multiple sourceFiles extensions)",
settings: {
imports: InitializationImports.Yes,
cleanups: InitializationCleanups.No,
project: {
filePath: "tsconfig.json",
},
Expand All @@ -146,15 +170,16 @@ describe(createJavaScriptConfig, () => {
name: "Auto Renames (parenthesized sourceFiles extensions)",
settings: {
imports: InitializationImports.Yes,
cleanups: InitializationCleanups.No,
project: {
filePath: "tsconfig.json",
},
renames: InitializationRenames.Auto,
sourceFiles: "src/**/*.js(x)",
},
},
])("$name", async ({ expected, settings }) => {
const actual = await createJavaScriptConfig(settings);
])("$name", ({ expected, settings }) => {
const actual = createJavaScriptConfig(settings);

expect(actual).toEqual(expected);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
import { ProjectDescription } from "../initializeProject/shared";
import { InitializationImports } from "./imports";
import { InitializationRenames } from "./renames";
import { InitializationCleanups } from "./cleanups";

export interface JavaScriptConfigSettings {
cleanups: InitializationCleanups;
imports: InitializationImports;
project: ProjectDescription;
renames: InitializationRenames;
sourceFiles?: string;
}

export const createJavaScriptConfig = ({ imports, project, sourceFiles, renames }: JavaScriptConfigSettings) => {
export const createJavaScriptConfig = ({ imports, project, sourceFiles, cleanups, renames }: JavaScriptConfigSettings) => {
const fileConversion = {
files: {
renameExtensions: printRenames(renames),
},
};
const fixConversion = {
const coreConversion = {
fixes: {
incompleteTypes: true,
missingProperties: true,
noImplicitAny: true,
},
...(cleanups === InitializationCleanups.Yes ? { cleanups: { suppressTypeErrors: true } } : {}),
};
const shared = (include: string[] | undefined) => ({
...(include && { include }),
Expand All @@ -38,7 +41,7 @@ export const createJavaScriptConfig = ({ imports, project, sourceFiles, renames
...shared(sourceFiles ? [sourceFiles] : undefined),
},
{
...fixConversion,
...coreConversion,
...shared(
sourceFiles
? renames === InitializationRenames.Auto
Expand All @@ -52,7 +55,7 @@ export const createJavaScriptConfig = ({ imports, project, sourceFiles, renames
]
: {
...fileConversion,
...fixConversion,
...coreConversion,
...shared(sourceFiles ? [sourceFiles] : undefined),
};

Expand Down
5 changes: 4 additions & 1 deletion src/initialization/initializeJavaScript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { initializeSources } from "../sources";
import { initializeImports } from "./imports";
import { initializeRenames } from "./renames";
import { createJavaScriptConfig } from "./createJavaScriptConfig";
import { initializeCleanups } from "./cleanups";

export interface InitializeJavaScriptSettings {
fileName: string;
Expand All @@ -15,7 +16,9 @@ export const initializeJavaScript = async ({ fileName, project }: InitializeJava
const sourceFiles = await initializeSources({ fromJavaScript: true, project });
const renames = await initializeRenames();
const imports = await initializeImports();
const cleanups = await initializeCleanups();

const settings = createJavaScriptConfig({ imports, project, sourceFiles, cleanups, renames });

const settings = createJavaScriptConfig({ imports, project, sourceFiles, renames });
await writeFile(fileName, JSON.stringify(settings, undefined, 4));
};
2 changes: 1 addition & 1 deletion src/mutations/aliasing/aliases.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as ts from "typescript";

import { FileMutationsRequest } from "../../mutators/fileMutator";
import { FileMutationsRequest } from "../../shared/fileMutator";

/**
* Type flags and aliases to check when --strictNullChecks is not enabled.
Expand Down
2 changes: 1 addition & 1 deletion src/mutations/aliasing/joinIntoType.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as ts from "typescript";

import { FileMutationsRequest } from "../../mutators/fileMutator";
import { FileMutationsRequest } from "../../shared/fileMutator";
import { isNotUndefined, uniquify } from "../../shared/arrays";
import { getApplicableTypeAliases } from "./aliases";

Expand Down
2 changes: 1 addition & 1 deletion src/mutations/assignments.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as ts from "typescript";

import { FileMutationsRequest } from "../mutators/fileMutator";
import { FileMutationsRequest } from "../shared/fileMutator";
import { getTypeAtLocationIfNotError } from "../shared/types";

export interface AssignedTypeValue {
Expand Down
2 changes: 1 addition & 1 deletion src/mutations/codeFixes/addMissingProperty.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Mutation } from "automutate";
import * as ts from "typescript";

import { FileMutationsRequest } from "../../mutators/fileMutator";
import { FileMutationsRequest } from "../../shared/fileMutator";

import { createCodeFixCreationMutation } from "./creation";

Expand Down
2 changes: 1 addition & 1 deletion src/mutations/codeFixes/creation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { combineMutations, MultipleMutations, TextInsertMutation } from "automutate";
import * as ts from "typescript";
import { FileMutationsRequest } from "../../mutators/fileMutator";
import { FileMutationsRequest } from "../../shared/fileMutator";

export interface CodeFixCreationPreferences {
ignoreKnownBlankTypes?: boolean;
Expand Down
2 changes: 1 addition & 1 deletion src/mutations/codeFixes/getCodeFixIfMatchedByDiagnostic.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as ts from "typescript";
import { FileMutationsRequest } from "../../mutators/fileMutator";
import { FileMutationsRequest } from "../../shared/fileMutator";

/**
* Uses a requesting language service to get code fixes for a type of node.
Expand Down
2 changes: 1 addition & 1 deletion src/mutations/codeFixes/noImplicitAny.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Mutation } from "automutate";
import * as tsutils from "tsutils";
import * as ts from "typescript";

import { FileMutationsRequest } from "../../mutators/fileMutator";
import { FileMutationsRequest } from "../../shared/fileMutator";
import { getTypeAtLocationIfNotError } from "../../shared/types";

import { createCodeFixCreationMutation } from "./creation";
Expand Down
2 changes: 1 addition & 1 deletion src/mutations/codeFixes/noImplicitThis.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Mutation } from "automutate";
import * as ts from "typescript";

import { FileMutationsRequest } from "../../mutators/fileMutator";
import { FileMutationsRequest } from "../../shared/fileMutator";

import { createCodeFixCreationMutation } from "./creation";
import { getCodeFixIfMatchedByDiagnostic } from "./getCodeFixIfMatchedByDiagnostic";
Expand Down
2 changes: 1 addition & 1 deletion src/mutations/collecting.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as tsutils from "tsutils";
import * as ts from "typescript";

import { FileMutationsRequest } from "../mutators/fileMutator";
import { FileMutationsRequest } from "../shared/fileMutator";
import { isKnownGlobalBaseType } from "../shared/nodeTypes";
import { setSubtract } from "../shared/sets";

Expand Down
2 changes: 1 addition & 1 deletion src/mutations/creations/creationMutations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as ts from "typescript";

import { FileMutationsRequest } from "../../mutators/fileMutator";
import { FileMutationsRequest } from "../../shared/fileMutator";
import { printNewLine } from "../../shared/printing/newlines";
import { printNamedTypeSummaries } from "../../shared/printing/nodePrinting";
import { TypeSummariesByName } from "../expansions/summarization";
Expand Down
2 changes: 1 addition & 1 deletion src/mutations/creators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { TextInsertMutation, TextSwapMutation } from "automutate";
import * as tsutils from "tsutils";
import * as ts from "typescript";

import { FileMutationsRequest } from "../mutators/fileMutator";
import { FileMutationsRequest } from "../shared/fileMutator";
import { isKnownGlobalBaseType, NodeWithAddableType, NodeWithCreatableType } from "../shared/nodeTypes";

import { joinIntoType } from "./aliasing/joinIntoType";
Expand Down
2 changes: 1 addition & 1 deletion src/mutations/expansions/addIncompleteTypesToType.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { combineMutations, MultipleMutations, Mutation, TextInsertMutation, TextSwapMutation } from "automutate";
import * as ts from "typescript";

import { FileMutationsRequest } from "../../mutators/fileMutator";
import { FileMutationsRequest } from "../../shared/fileMutator";
import { isKnownGlobalBaseType, isNeverAndOrUnknownType, PropertySignatureWithType } from "../../shared/nodeTypes";

import { TypeSummary } from "./summarization";
Expand Down
Loading

0 comments on commit dfa87d3

Please sign in to comment.