Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/typescript-diagnostic-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"diagnostics-webpack-plugin": minor
---

Add `diagnosticOptions` to the `typescript` check, which says whether to report each kind of diagnostic — syntactic, semantic, declaration and global. Turning one off asks TypeScript for less rather than dropping what it answered.
79 changes: 57 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,27 +88,27 @@ Every check to run is an entry in `checks`, named by its `use`. The list may nam

### Every option

| Option | Layer | What it decides |
| :--------------------------------------------------------------------------------------------- | :----- | :--------------------------------------------------------------------------- |
| [`checks`](#checks) | Plugin | Which checks run, and the options only each of them understands. |
| [`context`](#context) | Plugin | The folder every relative `files` and `exclude` pattern is resolved against. |
| [`lintOnStart`](#lintonstart) | Plugin | Whether the first compilation checks everything it covers. |
| [`cache`](#cache) | Shared | Whether the tool keeps a cache of its own between runs. |
| [`cacheLocation`](#cachelocation) | Shared | Where that cache is written. |
| [`exclude`](#exclude) | Shared | What is left out. |
| [`extensions`](#extensions) | Shared | Which extensions a named folder is walked for. |
| [`files`](#files) | Shared | What to check: naming it checks every file it matches, built or not. |
| [`fix`](#fix) | Shared | Whether the tool writes back what it can fix. |
| [`formatter`](#formatter) | Shared | How results are turned into the message that is reported. |
| [`outputReport`](#outputreport) | Shared | A file the same results are written to. |
| [`reportAs`](#reportas) | Shared | What the build carries: an error, a warning, a log line, or nothing. |
| [`resourceQueryExclude`](#resourcequeryexclude) | Shared | Which module queries are left out. |
| [`threads`](#threads) | Shared | How wide the work is spread. |
| [`configType`, `eslintPath`](#eslint) | Check | ESLint's own. |
| [`stylelintPath`](#stylelint) | Check | Stylelint's own. |
| [`oxlintPath`, `configFile`, `args`](#oxlint) | Check | oxlint's own. |
| [`biomePath`, `command`, `configFile`, `args`](#biome) | Check | Biome's own. |
| [`typescriptPath`, `configFile`, `compilerOptions`, `build`, `ignoreDiagnostics`](#typescript) | Check | TypeScript's own. |
| Option | Layer | What it decides |
| :------------------------------------------------------------------------------------------------------------------ | :----- | :--------------------------------------------------------------------------- |
| [`checks`](#checks) | Plugin | Which checks run, and the options only each of them understands. |
| [`context`](#context) | Plugin | The folder every relative `files` and `exclude` pattern is resolved against. |
| [`lintOnStart`](#lintonstart) | Plugin | Whether the first compilation checks everything it covers. |
| [`cache`](#cache) | Shared | Whether the tool keeps a cache of its own between runs. |
| [`cacheLocation`](#cachelocation) | Shared | Where that cache is written. |
| [`exclude`](#exclude) | Shared | What is left out. |
| [`extensions`](#extensions) | Shared | Which extensions a named folder is walked for. |
| [`files`](#files) | Shared | What to check: naming it checks every file it matches, built or not. |
| [`fix`](#fix) | Shared | Whether the tool writes back what it can fix. |
| [`formatter`](#formatter) | Shared | How results are turned into the message that is reported. |
| [`outputReport`](#outputreport) | Shared | A file the same results are written to. |
| [`reportAs`](#reportas) | Shared | What the build carries: an error, a warning, a log line, or nothing. |
| [`resourceQueryExclude`](#resourcequeryexclude) | Shared | Which module queries are left out. |
| [`threads`](#threads) | Shared | How wide the work is spread. |
| [`configType`, `eslintPath`](#eslint) | Check | ESLint's own. |
| [`stylelintPath`](#stylelint) | Check | Stylelint's own. |
| [`oxlintPath`, `configFile`, `args`](#oxlint) | Check | oxlint's own. |
| [`biomePath`, `command`, `configFile`, `args`](#biome) | Check | Biome's own. |
| [`typescriptPath`, `configFile`, `compilerOptions`, `build`, `diagnosticOptions`, `ignoreDiagnostics`](#typescript) | Check | TypeScript's own. |

Anything else written in a `checks` entry is handed to the tool itself, so its
own Node.js API options go next to these.
Expand Down Expand Up @@ -808,6 +808,41 @@ that fails is never up to date. Pair it with
[`reportAs: "log"`](#reportas) if you would rather a watch rebuild did not wait
for that.

### `diagnosticOptions`

- Type:

```ts
interface diagnosticOptions {
syntactic?: boolean | undefined;
semantic?: boolean | undefined;
declaration?: boolean | undefined;
global?: boolean | undefined;
}
```

- Default: every kind reported

Which kinds of diagnostic to report. Turning one off asks TypeScript for less
rather than dropping what it answered, so it is also the one filter that saves
the work — `semantic: false` is what makes a check that only wants syntax errors
cheap.

```js
new DiagnosticsPlugin({
// webpack's own parser reports a syntax error in a file it builds
checks: [{ use: "typescript", diagnosticOptions: { syntactic: false } }],
});
```

What the `tsconfig.json` itself is wrong about is reported whatever is turned
off here: with the config file misread, nothing below it would be answering the
right question. [`build`](#build) reports the whole of what it finds either way,
since a solution is built rather than asked kind by kind.

`fork-ts-checker-webpack-plugin` spells this option the same way, with
`semantic` alone on by default.

### `ignoreDiagnostics`

- Type:
Expand Down Expand Up @@ -1035,7 +1070,7 @@ rather than the project again. What that costs and saves is under
| `typescript.typescriptPath` | [`typescriptPath`](#typescriptpath). |
| `typescript.mode` | Not an option: nothing is written without `build`, and `build` writes the declarations a referenced project publishes and nothing else. |
| `typescript.memoryLimit`, `profile` | Nothing to set — the check is not a forked process. |
| `typescript.diagnosticOptions` | No switch per kind; [`ignoreDiagnostics`](#ignorediagnostics) leaves out the codes you name. |
| `typescript.diagnosticOptions` | [`diagnosticOptions`](#diagnosticoptions), spelled the same way. Every kind is reported here unless you turn one off; there, only `semantic` is on to begin with. |
| `issue.include`, `issue.exclude` | [`files`](#files) and [`exclude`](#exclude) choose the files, [`ignoreDiagnostics`](#ignorediagnostics) the codes. A predicate of your own has no equivalent. |
| `formatter` | [`formatter`](#formatter). Unset, TypeScript's own formatter is used, with color and the source line. |
| `logger` | Webpack's logger is what the plugin writes to; `reportAs: "log"` is what sends results there rather than onto the compilation. |
Expand Down
18 changes: 14 additions & 4 deletions src/checks/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,15 +335,25 @@ function check(ts, options, held) {
: [];

const ignored = new Set(options.ignoreDiagnostics || []);
// What the config file itself is wrong about is reported whatever else is
// turned off: nothing below it would be answering the right question.
const kinds = {
syntactic: true,
semantic: true,
declaration: true,
global: true,
...options.diagnosticOptions,
};
const diagnostics = [
...unrecoverable,
...ts.sortAndDeduplicateDiagnostics([
...program.getConfigFileParsingDiagnostics(),
...program.getOptionsDiagnostics(),
...held.program.getSyntacticDiagnostics(),
...program.getGlobalDiagnostics(),
...held.program.getSemanticDiagnostics(),
...(parsed.options.declaration || parsed.options.composite
...(kinds.syntactic ? held.program.getSyntacticDiagnostics() : []),
...(kinds.global ? program.getGlobalDiagnostics() : []),
...(kinds.semantic ? held.program.getSemanticDiagnostics() : []),
...(kinds.declaration &&
(parsed.options.declaration || parsed.options.composite)
? program.getDeclarationDiagnostics()
: []),
]),
Expand Down
19 changes: 19 additions & 0 deletions src/checks/typescript.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@
"build": {
"description": "Build the projects the config file references first, the way `tsc -b` does, and report what the whole solution finds.",
"type": "boolean"
},
"diagnosticOptions": {
"description": "Which kinds of diagnostic to report. Every kind is reported unless it is turned off here; `build` reports the whole of what it finds either way.",
"type": "object",
"additionalProperties": false,
"properties": {
"syntactic": {
"type": "boolean"
},
"semantic": {
"type": "boolean"
},
"declaration": {
"type": "boolean"
},
"global": {
"type": "boolean"
}
}
}
}
}
3 changes: 3 additions & 0 deletions test/typescript/fixtures/kinds/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const entry = 1;

module.exports = entry;
1 change: 1 addition & 0 deletions test/typescript/fixtures/kinds/syntax.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const broken: number = ;
9 changes: 9 additions & 0 deletions test/typescript/fixtures/kinds/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"strict": true,
"target": "es2022",
"module": "preserve",
"skipLibCheck": true
},
"include": ["*.ts"]
}
4 changes: 4 additions & 0 deletions test/typescript/fixtures/kinds/tsconfig.unknown-option.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"compilerOptions": { "strict": true, "nonsense": true },
"include": ["*.ts"]
}
1 change: 1 addition & 0 deletions test/typescript/fixtures/kinds/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const wrong: string = 42;
69 changes: 69 additions & 0 deletions test/typescript/kinds.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import assert from "node:assert/strict";
import { join } from "node:path";
import { describe, it } from "node:test";

import pack from "./utils/pack.js";

const reported = async (/** @type {EXPECTED_ANY} */ diagnosticOptions) => {
const stats = await pack("kinds", { diagnosticOptions }).runAsync();

return stats.compilation.errors.map(({ message }) => message).join("\n");
};

describe("diagnostic options", () => {
it("should report every kind when none is turned off", async () => {
const messages = await reported(undefined);

assert.match(messages, /TS1109/u);
assert.match(messages, /TS2322/u);
});

it("should leave out the syntactic ones", async () => {
const messages = await reported({ syntactic: false });

assert.doesNotMatch(messages, /TS1109/u);
assert.match(messages, /TS2322/u);
});

it("should leave out the semantic ones", async () => {
const messages = await reported({ semantic: false });

assert.match(messages, /TS1109/u);
assert.doesNotMatch(messages, /TS2322/u);
});

it("should report nothing of a program with every kind turned off", async () => {
const stats = await pack("kinds", {
diagnosticOptions: {
declaration: false,
global: false,
semantic: false,
syntactic: false,
},
}).runAsync();

assert.strictEqual(stats.hasErrors(), false);
assert.strictEqual(stats.hasWarnings(), false);
});

it("should report what the config file itself is wrong about either way", async () => {
const stats = await pack("kinds", {
// The config file names an option TypeScript does not have.
configFile: join(
import.meta.dirname,
"fixtures",
"kinds",
"tsconfig.unknown-option.json",
),
diagnosticOptions: {
declaration: false,
global: false,
semantic: false,
syntactic: false,
},
}).runAsync();

assert.strictEqual(stats.hasErrors(), true);
assert.match(stats.compilation.errors[0].message, /TS5023/u);
});
});
Loading