Skip to content

Commit 3a2981d

Browse files
authored
feat: enhance the issue formatter to get a customized function (#578)
Adds option to pass a custom formatter function
1 parent fe34ce1 commit 3a2981d

10 files changed

+16651
-18
lines changed

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,13 @@ you can place your configuration in the:
137137

138138
Options passed to the plugin constructor will overwrite options from the cosmiconfig (using [deepmerge](https://github.com/TehShrike/deepmerge)).
139139

140-
| Name | Type | Default value | Description |
141-
| ----------------- | --------------------- | ------------------------------------------------------------------ | ----------- |
142-
| `async` | `boolean` | `compiler.options.mode === 'development'` | If `true`, reports issues **after** webpack's compilation is done. Thanks to that it doesn't block the compilation. Used only in the `watch` mode. |
143-
| `typescript` | `object` or `boolean` | `true` | If a `boolean`, it enables/disables TypeScript checker. If an `object`, see [TypeScript options](#typescript-options). |
144-
| `eslint` | `object` | `undefined` | If `undefined`, it disables ESLint linter. If an `object`, see [ESLint options](#eslint-options). |
145-
| `issue` | `object` | `{}` | See [Issues options](#issues-options). |
146-
| `formatter` | `string` or `object` | `codeframe` | Available formatters are `basic` and `codeframe`. To [configure](https://babeljs.io/docs/en/babel-code-frame#options) `codeframe` formatter, pass object: `{ type: 'codeframe', options: { <coderame options> } }`. |
140+
| Name | Type | Default value | Description |
141+
| ----------------- | ---------------------------------- | ------------------------------------------------------------------ | ----------- |
142+
| `async` | `boolean` | `compiler.options.mode === 'development'` | If `true`, reports issues **after** webpack's compilation is done. Thanks to that it doesn't block the compilation. Used only in the `watch` mode. |
143+
| `typescript` | `object` or `boolean` | `true` | If a `boolean`, it enables/disables TypeScript checker. If an `object`, see [TypeScript options](#typescript-options). |
144+
| `eslint` | `object` | `undefined` | If `undefined`, it disables ESLint linter. If an `object`, see [ESLint options](#eslint-options). |
145+
| `issue` | `object` | `{}` | See [Issues options](#issues-options). |
146+
| `formatter` | `string` or `object` or `function` | `codeframe` | Available formatters are `basic`, `codeframe` and a custom `function`. To [configure](https://babeljs.io/docs/en/babel-code-frame#options) `codeframe` formatter, pass object: `{ type: 'codeframe', options: { <coderame options> } }`. |
147147
| `logger` | `object` | `{ infrastructure: 'silent', issues: 'console', devServer: true }` | Available loggers are `silent`, `console`, and `webpack-infrastructure`. Infrastructure logger prints additional information, issue logger prints `issues` in the `async` mode. If `devServer` is set to `false`, errors will not be reported to Webpack Dev Server. |
148148

149149
### TypeScript options

src/formatter/FormatterConfiguration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ type FormatterConfiguration = Formatter;
66

77
function createFormatterConfiguration(options: FormatterOptions | undefined) {
88
return createFormatter(
9-
options ? (typeof options === 'string' ? options : options.type || 'codeframe') : 'codeframe',
9+
options ? (typeof options === 'object' ? options.type || 'codeframe' : options) : 'codeframe',
1010
options && typeof options === 'object' ? options.options || {} : {}
1111
);
1212
}

src/formatter/FormatterFactory.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Formatter } from './Formatter';
22
import { BabelCodeFrameOptions, createCodeFrameFormatter } from './CodeFrameFormatter';
33
import { createBasicFormatter } from './BasicFormatter';
44

5-
type NotConfigurableFormatterType = undefined | 'basic';
5+
type NotConfigurableFormatterType = undefined | 'basic' | Formatter;
66
type ConfigurableFormatterType = 'codeframe';
77
type FormatterType = NotConfigurableFormatterType | ConfigurableFormatterType;
88

@@ -25,17 +25,21 @@ function createFormatter<T extends ConfigurableFormatterType>(
2525
function createFormatter<T extends FormatterType>(type: T, options?: object): Formatter;
2626
// declare function implementation
2727
function createFormatter(type?: FormatterType, options?: object): Formatter {
28-
switch (type) {
29-
case 'basic':
30-
case undefined:
31-
return createBasicFormatter();
28+
if (typeof type === 'function') {
29+
return type;
30+
}
3231

33-
case 'codeframe':
34-
return createCodeFrameFormatter(options);
32+
if (typeof type === 'undefined' || type === 'basic') {
33+
return createBasicFormatter();
34+
}
3535

36-
default:
37-
throw new Error(`Unknown "${type}" formatter. Available types are: basic, codeframe.`);
36+
if (type === 'codeframe') {
37+
return createCodeFrameFormatter(options);
3838
}
39+
40+
throw new Error(
41+
`Unknown "${type}" formatter. Available types are: "basic", "codeframe" or a custom function.`
42+
);
3943
}
4044

4145
export {
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { readFixture } from './sandbox/Fixture';
2+
import { join } from 'path';
3+
import os from 'os';
4+
import { createSandbox, Sandbox } from './sandbox/Sandbox';
5+
import {
6+
createWebpackDevServerDriver,
7+
WEBPACK_CLI_VERSION,
8+
WEBPACK_DEV_SERVER_VERSION,
9+
} from './sandbox/WebpackDevServerDriver';
10+
import { FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION } from './sandbox/Plugin';
11+
12+
describe('TypeScript Context Option', () => {
13+
let sandbox: Sandbox;
14+
15+
beforeAll(async () => {
16+
sandbox = await createSandbox();
17+
});
18+
19+
beforeEach(async () => {
20+
await sandbox.reset();
21+
});
22+
23+
afterAll(async () => {
24+
await sandbox.cleanup();
25+
});
26+
27+
it.each([
28+
{ async: true, typescript: '2.7.1' },
29+
{ async: false, typescript: '~3.0.0' },
30+
{ async: true, typescript: '~3.6.0' },
31+
{ async: false, typescript: '~3.8.0' },
32+
])(
33+
'uses the custom formatter to format the error message for %p',
34+
async ({ async, typescript }) => {
35+
await sandbox.load([
36+
await readFixture(join(__dirname, 'fixtures/environment/typescript-basic.fixture'), {
37+
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION: JSON.stringify(
38+
FORK_TS_CHECKER_WEBPACK_PLUGIN_VERSION
39+
),
40+
TS_LOADER_VERSION: JSON.stringify('^7.0.0'),
41+
TYPESCRIPT_VERSION: JSON.stringify(typescript),
42+
WEBPACK_VERSION: JSON.stringify('^4.0.0'),
43+
WEBPACK_CLI_VERSION: JSON.stringify(WEBPACK_CLI_VERSION),
44+
WEBPACK_DEV_SERVER_VERSION: JSON.stringify(WEBPACK_DEV_SERVER_VERSION),
45+
ASYNC: JSON.stringify(async),
46+
}),
47+
await readFixture(join(__dirname, 'fixtures/implementation/typescript-basic.fixture')),
48+
]);
49+
50+
// update sandbox to use context option
51+
await sandbox.remove('tsconfig.json');
52+
await sandbox.write(
53+
'build/tsconfig.json',
54+
JSON.stringify({
55+
compilerOptions: {
56+
target: 'es5',
57+
module: 'commonjs',
58+
lib: ['ES6', 'DOM'],
59+
moduleResolution: 'node',
60+
esModuleInterop: true,
61+
skipLibCheck: true,
62+
skipDefaultLibCheck: true,
63+
strict: true,
64+
baseUrl: './src',
65+
outDir: './dist',
66+
},
67+
include: ['./src'],
68+
exclude: ['node_modules'],
69+
})
70+
);
71+
await sandbox.patch(
72+
'webpack.config.js',
73+
"entry: './src/index.ts',",
74+
["entry: './src/index.ts',", 'context: path.resolve(__dirname),'].join('\n')
75+
);
76+
await sandbox.patch(
77+
'webpack.config.js',
78+
' logger: {',
79+
[
80+
' typescript: {',
81+
' enabled: true,',
82+
' configFile: path.resolve(__dirname, "build/tsconfig.json"),',
83+
' context: __dirname,',
84+
' },',
85+
' logger: {',
86+
].join('\n')
87+
);
88+
await sandbox.patch(
89+
'webpack.config.js',
90+
' logger: {',
91+
[
92+
' formatter: (issue) => {',
93+
' return `It is the custom issue statement - ${issue.code}: ${issue.message}`',
94+
' },',
95+
' logger: {',
96+
].join('\n')
97+
);
98+
await sandbox.patch(
99+
'webpack.config.js',
100+
' transpileOnly: true',
101+
[
102+
' transpileOnly: true,',
103+
' configFile: path.resolve(__dirname, "build/tsconfig.json"),',
104+
' context: __dirname,',
105+
].join('\n')
106+
);
107+
// create additional directory for cwd test
108+
await sandbox.write('foo/.gitignore', '');
109+
110+
const driver = createWebpackDevServerDriver(
111+
sandbox.spawn(
112+
`../node_modules/.bin/webpack-dev-server${os.platform() === 'win32' ? '.cmd' : ''}`,
113+
{},
114+
join(sandbox.context, 'foo')
115+
),
116+
async
117+
);
118+
119+
// first compilation is successful
120+
await driver.waitForNoErrors();
121+
122+
// then we introduce semantic error by removing "firstName" and "lastName" from the User model
123+
await sandbox.patch(
124+
'src/model/User.ts',
125+
[' firstName?: string;', ' lastName?: string;'].join('\n'),
126+
''
127+
);
128+
129+
// we should receive 2 semantic errors
130+
const errors = await driver.waitForErrors();
131+
expect(errors).toEqual([
132+
[
133+
'ERROR in ../src/model/User.ts:11:16',
134+
"It is the custom issue statement - TS2339: Property 'firstName' does not exist on type 'User'.",
135+
].join('\n'),
136+
[
137+
'ERROR in ../src/model/User.ts:11:32',
138+
"It is the custom issue statement - TS2339: Property 'lastName' does not exist on type 'User'.",
139+
].join('\n'),
140+
]);
141+
}
142+
);
143+
});

0 commit comments

Comments
 (0)