Skip to content

Commit 571291b

Browse files
authored
feat: add defaultSeverity configuration (#53)
1 parent 1b7ab85 commit 571291b

9 files changed

Lines changed: 111 additions & 41 deletions

File tree

README.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ Options for the issues filtering (`issue` option object).
126126
interface IssueOptions {
127127
include?: IssuePredicateOption;
128128
exclude?: IssuePredicateOption;
129+
defaultSeverity?: 'auto' | 'warning' | 'error';
129130
}
130131

131132
interface Issue {
@@ -140,10 +141,17 @@ type IssuePredicate = (issue: Issue) => boolean;
140141
type IssuePredicateOption = IssuePredicate | IssueMatch | (IssuePredicate | IssueMatch)[];
141142
```
142143

143-
| Name | Type | Default value | Description |
144-
| --------- | ------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
145-
| `include` | `IssueFilter` | `undefined` | If `object`, defines issue properties that should be [matched](src/issue/issue-match.ts). If `function`, acts as a predicate where `issue` is an argument. |
146-
| `exclude` | `IssueFilter` | `undefined` | Same as `include` but issues that match this predicate will be excluded. |
144+
| Name | Type | Default value | Description |
145+
| ----------------- | -------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
146+
| `include` | `IssueFilter` | `undefined` | If `object`, defines issue properties that should be [matched](src/issue/issue-match.ts). If `function`, acts as a predicate where `issue` is an argument. |
147+
| `exclude` | `IssueFilter` | `undefined` | Same as `include` but issues that match this predicate will be excluded. |
148+
| `defaultSeverity` | `'auto' \| 'warning' \| 'error'` | `'auto'` | Controls how the plugin assigns the severity of emitted issues. |
149+
150+
`defaultSeverity` behavior:
151+
152+
- `auto`: Uses the default mapping based on the TypeScript diagnostic category (`Error``error`, `Warning``warning`).
153+
- `warning`: Forces all issues to be emitted as warnings.
154+
- `error`: Forces all issues to be emitted as errors.
147155

148156
- **Example**:
149157

@@ -168,6 +176,16 @@ new TsCheckerRspackPlugin({
168176
});
169177
```
170178

179+
Force all issues to be emitted as warnings and do not break the build:
180+
181+
```js
182+
new TsCheckerRspackPlugin({
183+
issue: {
184+
defaultSeverity: 'warning',
185+
},
186+
});
187+
```
188+
171189
## Plugin hooks
172190

173191
This plugin provides some custom Rspack hooks:

src/hooks/tap-start-to-run-workers.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function tapStartToRunWorkers(
2020
getIssuesWorker: RpcWorker<GetIssuesWorker>,
2121
getDependenciesWorker: RpcWorker<GetDependenciesWorker>,
2222
config: TsCheckerRspackPluginConfig,
23-
state: TsCheckerRspackPluginState
23+
state: TsCheckerRspackPluginState,
2424
) {
2525
const hooks = getPluginHooks(compiler);
2626
const { log, debug } = getInfrastructureLogger(compiler);
@@ -81,7 +81,7 @@ function tapStartToRunWorkers(
8181
'Calling reporter service for incremental check.',
8282
` Changed files: ${JSON.stringify(filesChange.changedFiles)}`,
8383
` Deleted files: ${JSON.stringify(filesChange.deletedFiles)}`,
84-
].join('\n')
84+
].join('\n'),
8585
);
8686
} else {
8787
log('Calling reporter service for single check.');
@@ -96,7 +96,7 @@ function tapStartToRunWorkers(
9696
`Aggregating with previous files change, iteration ${iteration}.`,
9797
` Changed files: ${JSON.stringify(aggregatedFilesChange.changedFiles)}`,
9898
` Deleted files: ${JSON.stringify(aggregatedFilesChange.deletedFiles)}`,
99-
].join('\n')
99+
].join('\n'),
100100
);
101101
}
102102
state.aggregatedFilesChange = aggregatedFilesChange;
@@ -115,7 +115,11 @@ function tapStartToRunWorkers(
115115
return issuesPool.submit(async () => {
116116
try {
117117
debug(`Running the getIssuesWorker, iteration ${iteration}.`);
118-
const issues = await getIssuesWorker(aggregatedFilesChange, state.watching);
118+
const issues = await getIssuesWorker(
119+
aggregatedFilesChange,
120+
state.watching,
121+
config.issue.defaultSeverity,
122+
);
119123
if (state.aggregatedFilesChange === aggregatedFilesChange) {
120124
state.aggregatedFilesChange = undefined;
121125
}

src/issue/issue-config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import { createIssuePredicateFromIssueMatch } from './issue-match';
44
import type { IssuePredicateOption, IssueOptions } from './issue-options';
55
import type { IssuePredicate } from './issue-predicate';
66
import { composeIssuePredicates, createTrivialIssuePredicate } from './issue-predicate';
7+
import type { IssueDefaultSeverity } from './issue-severity';
78

89
interface IssueConfig {
910
predicate: IssuePredicate;
11+
defaultSeverity: IssueDefaultSeverity;
1012
}
1113

1214
function createIssuePredicateFromOption(
@@ -42,9 +44,11 @@ function createIssueConfig(
4244
const exclude = options.exclude
4345
? createIssuePredicateFromOption(context, options.exclude)
4446
: createTrivialIssuePredicate(false);
47+
const defaultSeverity = options.defaultSeverity ?? 'auto';
4548

4649
return {
4750
predicate: (issue) => include(issue) && !exclude(issue),
51+
defaultSeverity,
4852
};
4953
}
5054

src/issue/issue-options.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import type { IssueMatch } from './issue-match';
22
import type { IssuePredicate } from './issue-predicate';
3+
import type { IssueDefaultSeverity } from './issue-severity';
34

45
type IssuePredicateOption = IssuePredicate | IssueMatch | (IssuePredicate | IssueMatch)[];
56

67
interface IssueOptions {
78
include?: IssuePredicateOption;
89
exclude?: IssuePredicateOption;
10+
defaultSeverity?: IssueDefaultSeverity;
911
}
1012

1113
export { IssueOptions, IssuePredicateOption };

src/issue/issue-severity.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
type IssueSeverity = 'error' | 'warning';
1+
export type IssueSeverity = 'error' | 'warning';
2+
export type IssueDefaultSeverity = IssueSeverity | 'auto';
23

3-
function isIssueSeverity(value: unknown): value is IssueSeverity {
4+
export function isIssueSeverity(value: unknown): value is IssueSeverity {
45
return ['error', 'warning'].includes(value as IssueSeverity);
56
}
67

7-
function compareIssueSeverities(severityA: IssueSeverity, severityB: IssueSeverity) {
8+
export function compareIssueSeverities(severityA: IssueSeverity, severityB: IssueSeverity) {
89
const [priorityA, priorityB] = [severityA, severityB].map((severity) =>
9-
['warning' /* 0 */, 'error' /* 1 */].indexOf(severity)
10+
['warning' /* 0 */, 'error' /* 1 */].indexOf(severity),
1011
);
1112

1213
return Math.sign(priorityB - priorityA);
1314
}
14-
15-
export { IssueSeverity, isIssueSeverity, compareIssueSeverities };

src/typescript/worker/get-issues-worker.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { FilesChange } from '../../files-change';
2-
import type { Issue } from '../../issue';
2+
import type { Issue, IssueDefaultSeverity } from '../../issue';
33
import { exposeRpc } from '../../rpc';
44

55
import { invalidateArtifacts, registerArtifacts } from './lib/artifacts';
@@ -29,7 +29,11 @@ import { dumpTracingLegendIfNeeded } from './lib/tracing';
2929
import { invalidateTsBuildInfo } from './lib/tsbuildinfo';
3030
import { config } from './lib/worker-config';
3131

32-
const getIssuesWorker = async (change: FilesChange, watching: boolean): Promise<Issue[]> => {
32+
const getIssuesWorker = async (
33+
change: FilesChange,
34+
watching: boolean,
35+
defaultSeverity: IssueDefaultSeverity,
36+
): Promise<Issue[]> => {
3337
system.invalidateCache();
3438

3539
if (didConfigFileChanged(change)) {
@@ -57,7 +61,7 @@ const getIssuesWorker = async (change: FilesChange, watching: boolean): Promise<
5761
registerArtifacts();
5862
enablePerformanceIfNeeded();
5963

60-
const parseConfigIssues = getParseConfigIssues();
64+
const parseConfigIssues = getParseConfigIssues(defaultSeverity);
6165
if (parseConfigIssues.length) {
6266
// report config parse issues and exit
6367
return parseConfigIssues;
@@ -84,7 +88,7 @@ const getIssuesWorker = async (change: FilesChange, watching: boolean): Promise<
8488
await system.waitForQueued();
8589

8690
// retrieve all collected diagnostics as normalized issues
87-
const issues = getIssues();
91+
const issues = getIssues(defaultSeverity);
8892

8993
dumpTracingLegendIfNeeded();
9094
printPerformanceMeasuresIfNeeded();

src/typescript/worker/lib/config.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type * as ts from 'typescript';
44

55
import type { FilesChange } from '../../../files-change';
66
import type { FilesMatch } from '../../../files-match';
7-
import type { Issue } from '../../../issue';
7+
import type { Issue, IssueDefaultSeverity } from '../../../issue';
88
import { forwardSlash } from '../../../utils/path/forward-slash';
99
import type { TypeScriptConfigOverwrite } from '../../type-script-config-overwrite';
1010

@@ -84,25 +84,25 @@ function applyConfigOverwrite(
8484

8585
export function parseConfig(
8686
configFileName: string,
87-
configFileContext: string
87+
configFileContext: string,
8888
): ts.ParsedCommandLine {
8989
const configFilePath = forwardSlash(configFileName);
9090

9191
const { config: baseConfig, error: readConfigError } = typescript.readConfigFile(
9292
configFilePath,
93-
parseConfigFileHost.readFile
93+
parseConfigFileHost.readFile,
9494
);
9595

9696
const overwrittenConfig = applyConfigOverwrite(
9797
baseConfig || {},
9898
getImplicitConfigOverwrite(),
99-
getUserProvidedConfigOverwrite()
99+
getUserProvidedConfigOverwrite(),
100100
);
101101

102102
const parsedConfigFile = typescript.parseJsonConfigFileContent(
103103
overwrittenConfig,
104104
parseConfigFileHost,
105-
configFileContext
105+
configFileContext,
106106
);
107107

108108
return {
@@ -115,8 +115,8 @@ export function parseConfig(
115115
};
116116
}
117117

118-
export function getParseConfigIssues(): Issue[] {
119-
const issues = createIssuesFromDiagnostics(parseConfigDiagnostics);
118+
export function getParseConfigIssues(defaultSeverity: IssueDefaultSeverity): Issue[] {
119+
const issues = createIssuesFromDiagnostics(parseConfigDiagnostics, defaultSeverity);
120120

121121
issues.forEach((issue) => {
122122
if (!issue.file) {
@@ -168,13 +168,13 @@ export function didConfigFileChanged({ changedFiles = [], deletedFiles = [] }: F
168168

169169
export function didDependenciesProbablyChanged(
170170
dependencies: FilesMatch,
171-
{ changedFiles = [], deletedFiles = [] }: FilesChange
171+
{ changedFiles = [], deletedFiles = [] }: FilesChange,
172172
) {
173173
const didSomeDependencyHasBeenAdded = changedFiles.some(
174-
(changeFile) => !dependencies.files.includes(changeFile)
174+
(changeFile) => !dependencies.files.includes(changeFile),
175175
);
176176
const didSomeDependencyHasBeenDeleted = deletedFiles.some((deletedFile) =>
177-
dependencies.files.includes(deletedFile)
177+
dependencies.files.includes(deletedFile),
178178
);
179179

180180
return didSomeDependencyHasBeenAdded || didSomeDependencyHasBeenDeleted;

src/typescript/worker/lib/diagnostics.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as os from 'os';
22

33
import type * as ts from 'typescript';
44

5-
import type { Issue, IssueLocation } from '../../../issue';
5+
import type { Issue, IssueDefaultSeverity, IssueLocation } from '../../../issue';
66
import { deduplicateAndSortIssues } from '../../../issue';
77

88
import { typescript } from './typescript';
@@ -14,14 +14,14 @@ export function updateDiagnostics(configFile: string, diagnostics: ts.Diagnostic
1414
diagnosticsPerConfigFile.set(configFile, diagnostics);
1515
}
1616

17-
export function getIssues(): Issue[] {
17+
export function getIssues(defaultSeverity: IssueDefaultSeverity): Issue[] {
1818
const allDiagnostics: ts.Diagnostic[] = [];
1919

2020
diagnosticsPerConfigFile.forEach((diagnostics) => {
2121
allDiagnostics.push(...diagnostics);
2222
});
2323

24-
return createIssuesFromDiagnostics(allDiagnostics);
24+
return createIssuesFromDiagnostics(allDiagnostics, defaultSeverity);
2525
}
2626

2727
export function invalidateDiagnostics(): void {
@@ -59,7 +59,10 @@ ${e.stack}`,
5959
return programDiagnostics;
6060
}
6161

62-
function createIssueFromDiagnostic(diagnostic: ts.Diagnostic): Issue {
62+
function createIssueFromDiagnostic(
63+
diagnostic: ts.Diagnostic,
64+
defaultSeverity: IssueDefaultSeverity,
65+
): Issue {
6366
let file: string | undefined;
6467
let location: IssueLocation | undefined;
6568

@@ -85,18 +88,28 @@ function createIssueFromDiagnostic(diagnostic: ts.Diagnostic): Issue {
8588
}
8689
}
8790

91+
const getSeverity = () => {
92+
if (defaultSeverity === 'auto') {
93+
// we don't handle Suggestion and Message diagnostics
94+
return diagnostic.category === 0 ? 'warning' : 'error';
95+
}
96+
return defaultSeverity;
97+
};
98+
8899
return {
89100
code: 'TS' + String(diagnostic.code),
90-
// we don't handle Suggestion and Message diagnostics
91-
severity: diagnostic.category === 0 ? 'warning' : 'error',
101+
severity: getSeverity(),
92102
message: typescript.flattenDiagnosticMessageText(diagnostic.messageText, os.EOL),
93103
file,
94104
location,
95105
};
96106
}
97107

98-
export function createIssuesFromDiagnostics(diagnostics: ts.Diagnostic[]): Issue[] {
108+
export function createIssuesFromDiagnostics(
109+
diagnostics: ts.Diagnostic[],
110+
defaultSeverity: IssueDefaultSeverity,
111+
): Issue[] {
99112
return deduplicateAndSortIssues(
100-
diagnostics.map((diagnostic) => createIssueFromDiagnostic(diagnostic))
113+
diagnostics.map((diagnostic) => createIssueFromDiagnostic(diagnostic, defaultSeverity)),
101114
);
102115
}

test/e2e/with-rsbuild/index.test.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const createRsbuild = async (config: CreateRsbuildOptions) => {
3232
provider: webpackProvider,
3333
plugins: [pluginSwc()],
3434
}
35-
: {}
35+
: {},
3636
);
3737

3838
return await baseCreateRsbuild({
@@ -60,8 +60,8 @@ test('should throw error when exist type errors', async () => {
6060

6161
expect(
6262
logs.find((log) =>
63-
log.includes(`Argument of type 'string' is not assignable to parameter of type 'number'.`)
64-
)
63+
log.includes(`Argument of type 'string' is not assignable to parameter of type 'number'.`),
64+
),
6565
).toBeTruthy();
6666

6767
restore();
@@ -92,8 +92,8 @@ test('should throw error when exist type errors in dev mode', async ({ page }) =
9292

9393
expect(
9494
logs.find((log) =>
95-
log.includes(`Argument of type 'string' is not assignable to parameter of type 'number'.`)
96-
)
95+
log.includes(`Argument of type 'string' is not assignable to parameter of type 'number'.`),
96+
),
9797
).toBeTruthy();
9898

9999
restore();
@@ -120,6 +120,32 @@ test('should not throw error when the file is excluded', async () => {
120120
await expect(rsbuild.build()).resolves.toBeTruthy();
121121
});
122122

123+
test('should downgrade type errors to warnings when defaultSeverity is warning', async () => {
124+
const rsbuild = await createRsbuild({
125+
rsbuildConfig: {
126+
tools: {
127+
rspack: {
128+
plugins: [
129+
new TsCheckerRspackPlugin({
130+
async: false,
131+
issue: {
132+
defaultSeverity: 'warning',
133+
},
134+
}),
135+
],
136+
},
137+
},
138+
},
139+
});
140+
141+
const { stats } = await rsbuild.build();
142+
const statsJson = stats?.toJson('errors-warnings') || {};
143+
expect(statsJson.warnings?.[0]?.message).toContain(
144+
`Argument of type 'string' is not assignable to parameter of type 'number'`,
145+
);
146+
expect(statsJson.errors?.length).toEqual(0);
147+
});
148+
123149
test('should not throw error when the file is excluded by code', async () => {
124150
const rsbuild = await createRsbuild({
125151
rsbuildConfig: {

0 commit comments

Comments
 (0)