Skip to content

Commit a20d14e

Browse files
committed
feat: undefined add formatter.pathType option for absolute path
Add `formatter.pathType` option with available values `relative` (default) and `absolute`. If you set it to `absolute`, the plugin will print absolute paths to error locations. ✅ Closes: #789
1 parent 4eb03ec commit a20d14e

11 files changed

+99
-42
lines changed

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,14 @@ you can place your configuration in the:
9393

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

96-
| Name | Type | Default value | Description |
97-
|--------------|--------------------------------------|-------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
98-
| `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. |
99-
| `typescript` | `object` | `{}` | See [TypeScript options](#typescript-options). |
100-
| `issue` | `object` | `{}` | See [Issues options](#issues-options). |
101-
| `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> } }`. |
102-
| `logger` | `{ log: function, error: function }` or `webpack-infrastructure` | `console` | Console-like object to print issues in `async` mode. |
103-
| `devServer` | `boolean` | `true` | If set to `false`, errors will not be reported to Webpack Dev Server. |
96+
| Name | Type | Default value | Description |
97+
|--------------|--------------------------------------|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
98+
| `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. |
99+
| `typescript` | `object` | `{}` | See [TypeScript options](#typescript-options). |
100+
| `issue` | `object` | `{}` | See [Issues options](#issues-options). |
101+
| `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: `{ type: 'codeframe', options: { <coderame options> } }`. To use absolute file path, pass: `{ type: 'codeframe', pathType: 'absolute' }`. |
102+
| `logger` | `{ log: function, error: function }` or `webpack-infrastructure` | `console` | Console-like object to print issues in `async` mode. |
103+
| `devServer` | `boolean` | `true` | If set to `false`, errors will not be reported to Webpack Dev Server. |
104104

105105
### TypeScript options
106106

src/formatter/formatter-config.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,46 @@
11
import { createBasicFormatter } from './basic-formatter';
22
import { createCodeFrameFormatter } from './code-frame-formatter';
3-
import type { Formatter } from './formatter';
3+
import type { Formatter, FormatterPathType } from './formatter';
44
import type { CodeframeFormatterOptions, FormatterOptions } from './formatter-options';
55

6-
type FormatterConfig = Formatter;
6+
type FormatterConfig = {
7+
format: Formatter;
8+
pathType: FormatterPathType;
9+
};
710

811
function createFormatterConfig(options: FormatterOptions | undefined): FormatterConfig {
912
if (typeof options === 'function') {
10-
return options;
13+
return {
14+
format: options,
15+
pathType: 'relative',
16+
};
1117
}
1218

1319
const type = options
1420
? typeof options === 'object'
1521
? options.type || 'codeframe'
1622
: options
1723
: 'codeframe';
24+
const pathType =
25+
options && typeof options === 'object' ? options.pathType || 'relative' : 'relative';
1826

1927
if (!type || type === 'basic') {
20-
return createBasicFormatter();
28+
return {
29+
format: createBasicFormatter(),
30+
pathType,
31+
};
2132
}
2233

2334
if (type === 'codeframe') {
2435
const config =
2536
options && typeof options === 'object'
2637
? (options as CodeframeFormatterOptions).options || {}
2738
: {};
28-
return createCodeFrameFormatter(config);
39+
40+
return {
41+
format: createCodeFrameFormatter(config),
42+
pathType,
43+
};
2944
}
3045

3146
throw new Error(

src/formatter/formatter-options.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import type { Formatter } from './formatter';
1+
import type { Formatter, FormatterPathType } from './formatter';
22
import type { BabelCodeFrameOptions } from './types/babel__code-frame';
33

44
type FormatterType = 'basic' | 'codeframe';
55

66
type BasicFormatterOptions = {
77
type: 'basic';
8+
pathType?: FormatterPathType;
89
};
910
type CodeframeFormatterOptions = {
1011
type: 'codeframe';
12+
pathType?: FormatterPathType;
1113
options?: BabelCodeFrameOptions;
1214
};
1315
type FormatterOptions =

src/formatter/formatter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Issue } from '../issue';
22

33
type Formatter = (issue: Issue) => string;
4+
type FormatterPathType = 'relative' | 'absolute';
45

5-
export { Formatter };
6+
export { Formatter, FormatterPathType };

src/formatter/webpack-formatter.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
import os from 'os';
2+
import path from 'path';
23

34
import chalk from 'chalk';
45

56
import { formatIssueLocation } from '../issue';
7+
import { forwardSlash } from '../utils/path/forward-slash';
68
import { relativeToContext } from '../utils/path/relative-to-context';
79

8-
import type { Formatter } from './formatter';
10+
import type { Formatter, FormatterPathType } from './formatter';
911

10-
function createWebpackFormatter(formatter: Formatter): Formatter {
12+
function createWebpackFormatter(formatter: Formatter, pathType: FormatterPathType): Formatter {
1113
// mimics webpack error formatter
1214
return function webpackFormatter(issue) {
1315
const color = issue.severity === 'warning' ? chalk.yellow.bold : chalk.red.bold;
1416

1517
const severity = issue.severity.toUpperCase();
1618

1719
if (issue.file) {
18-
let location = chalk.bold(relativeToContext(issue.file, process.cwd()));
20+
let location = chalk.bold(
21+
pathType === 'absolute'
22+
? forwardSlash(path.resolve(issue.file))
23+
: relativeToContext(issue.file, process.cwd())
24+
);
1925
if (issue.location) {
2026
location += `:${chalk.green.bold(formatIssueLocation(issue.location))}`;
2127
}

src/hooks/tap-after-compile-to-get-issues.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ function tapAfterCompileToGetIssues(
4444
issues = hooks.issues.call(issues, compilation);
4545

4646
issues.forEach((issue) => {
47-
const error = new IssueWebpackError(config.formatter(issue), issue);
47+
const error = new IssueWebpackError(
48+
config.formatter.format(issue),
49+
config.formatter.pathType,
50+
issue
51+
);
4852

4953
if (issue.severity === 'warning') {
5054
compilation.warnings.push(error);

src/hooks/tap-done-to-async-get-issues.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function tapDoneToAsyncGetIssues(
5959
// modify list of issues in the plugin hooks
6060
issues = hooks.issues.call(issues, stats.compilation);
6161

62-
const formatter = createWebpackFormatter(config.formatter);
62+
const formatter = createWebpackFormatter(config.formatter.format, config.formatter.pathType);
6363

6464
if (issues.length) {
6565
// follow webpack's approach - one process.write to stderr with all errors and warnings
@@ -75,7 +75,11 @@ function tapDoneToAsyncGetIssues(
7575
// skip reporting if there are no issues, to avoid an extra hot reload
7676
if (issues.length && state.webpackDevServerDoneTap) {
7777
issues.forEach((issue) => {
78-
const error = new IssueWebpackError(config.formatter(issue), issue);
78+
const error = new IssueWebpackError(
79+
config.formatter.format(issue),
80+
config.formatter.pathType,
81+
issue
82+
);
7983

8084
if (issue.severity === 'warning') {
8185
stats.compilation.warnings.push(error);

src/issue/issue-webpack-error.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import path from 'path';
2+
13
import chalk from 'chalk';
24
import webpack from 'webpack';
35

6+
import type { FormatterPathType } from '../formatter';
7+
import { forwardSlash } from '../utils/path/forward-slash';
48
import { relativeToContext } from '../utils/path/relative-to-context';
59

610
import type { Issue } from './issue';
@@ -9,14 +13,17 @@ import { formatIssueLocation } from './issue-location';
913
class IssueWebpackError extends webpack.WebpackError {
1014
readonly hideStack = true;
1115

12-
constructor(message: string, readonly issue: Issue) {
16+
constructor(message: string, pathType: FormatterPathType, readonly issue: Issue) {
1317
super(message);
1418

1519
// to display issue location using `loc` property, webpack requires `error.module` which
1620
// should be a NormalModule instance.
1721
// to avoid such a dependency, we do a workaround - error.file will contain formatted location instead
1822
if (issue.file) {
19-
this.file = relativeToContext(issue.file, process.cwd());
23+
this.file =
24+
pathType === 'absolute'
25+
? forwardSlash(path.resolve(issue.file))
26+
: relativeToContext(issue.file, process.cwd());
2027

2128
if (issue.location) {
2229
this.file += `:${chalk.green.bold(formatIssueLocation(issue.location))}`;

src/plugin-options.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
"type": {
3535
"$ref": "#/definitions/FormatterType"
3636
},
37+
"pathType": {
38+
"$ref": "#/definitions/FormatterPathType"
39+
},
3740
"options": {
3841
"type": "object",
3942
"additionalProperties": true
@@ -45,6 +48,10 @@
4548
"type": "string",
4649
"enum": ["basic", "codeframe"]
4750
},
51+
"FormatterPathType": {
52+
"type": "string",
53+
"enum": ["relative", "absolute"]
54+
},
4855
"IssueMatch": {
4956
"type": "object",
5057
"properties": {

test/unit/formatter/formatter-config.spec.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,24 @@ describe('formatter/formatter-config', () => {
6363
].join(os.EOL);
6464

6565
it.each([
66-
[undefined, CODEFRAME_FORMATTER_OUTPUT],
67-
['basic', BASIC_FORMATTER_OUTPUT],
68-
[customFormatter, CUSTOM_FORMATTER_OUTPUT],
69-
['codeframe', CODEFRAME_FORMATTER_OUTPUT],
70-
[{ type: 'basic' }, BASIC_FORMATTER_OUTPUT],
71-
[{ type: 'codeframe' }, CODEFRAME_FORMATTER_OUTPUT],
72-
[{ type: 'codeframe', options: { linesBelow: 1 } }, CUSTOM_CODEFRAME_FORMATTER_OUTPUT],
73-
])('creates configuration from options', (options, expectedFormat) => {
66+
[undefined, CODEFRAME_FORMATTER_OUTPUT, 'relative'],
67+
['basic', BASIC_FORMATTER_OUTPUT, 'relative'],
68+
[customFormatter, CUSTOM_FORMATTER_OUTPUT, 'relative'],
69+
['codeframe', CODEFRAME_FORMATTER_OUTPUT, 'relative'],
70+
[{ type: 'basic' }, BASIC_FORMATTER_OUTPUT, 'relative'],
71+
[{ type: 'codeframe' }, CODEFRAME_FORMATTER_OUTPUT, 'relative'],
72+
[
73+
{ type: 'codeframe', options: { linesBelow: 1 } },
74+
CUSTOM_CODEFRAME_FORMATTER_OUTPUT,
75+
'relative',
76+
],
77+
[{ type: 'basic', pathType: 'relative' }, BASIC_FORMATTER_OUTPUT, 'relative'],
78+
[{ type: 'basic', pathType: 'absolute' }, BASIC_FORMATTER_OUTPUT, 'absolute'],
79+
[{ type: 'codeframe', pathType: 'absolute' }, CODEFRAME_FORMATTER_OUTPUT, 'absolute'],
80+
])('creates configuration from options', (options, expectedFormat, expectedPathType) => {
7481
const formatter = createFormatterConfig(options as FormatterOptions);
7582

76-
expect(formatter(issue)).toEqual(expectedFormat);
83+
expect(formatter.format(issue)).toEqual(expectedFormat);
84+
expect(formatter.pathType).toEqual(expectedPathType);
7785
});
7886
});

test/unit/formatter/webpack-formatter.spec.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,37 +23,40 @@ describe('formatter/webpack-formatter', () => {
2323
},
2424
};
2525

26-
let formatter: Formatter;
26+
let relativeFormatter: Formatter;
27+
let absoluteFormatter: Formatter;
2728

2829
beforeEach(() => {
29-
formatter = createWebpackFormatter(createBasicFormatter());
30+
relativeFormatter = createWebpackFormatter(createBasicFormatter(), 'relative');
31+
absoluteFormatter = createWebpackFormatter(createBasicFormatter(), 'absolute');
3032
});
3133

32-
it('decorates existing formatter', () => {
33-
expect(formatter(issue)).toContain('TS123: Some issue content');
34+
it('decorates existing relativeFormatter', () => {
35+
expect(relativeFormatter(issue)).toContain('TS123: Some issue content');
3436
});
3537

3638
it('formats issue severity', () => {
37-
expect(formatter({ ...issue, severity: 'error' })).toContain('ERROR');
38-
expect(formatter({ ...issue, severity: 'warning' })).toContain('WARNING');
39+
expect(relativeFormatter({ ...issue, severity: 'error' })).toContain('ERROR');
40+
expect(relativeFormatter({ ...issue, severity: 'warning' })).toContain('WARNING');
3941
});
4042

4143
it('formats issue file', () => {
42-
expect(formatter(issue)).toContain(`./some/file.ts`);
44+
expect(relativeFormatter(issue)).toContain(`./some/file.ts`);
45+
expect(absoluteFormatter(issue)).toContain(`${process.cwd()}/some/file.ts`);
4346
});
4447

4548
it('formats location', () => {
46-
expect(formatter(issue)).toContain(':1:7');
49+
expect(relativeFormatter(issue)).toContain(':1:7');
4750
expect(
48-
formatter({
51+
relativeFormatter({
4952
...issue,
5053
location: { start: { line: 1, column: 7 }, end: { line: 10, column: 16 } },
5154
})
5255
).toContain(':1:7');
5356
});
5457

5558
it('formats issue header like webpack', () => {
56-
expect(formatter(issue)).toEqual(
59+
expect(relativeFormatter(issue)).toEqual(
5760
[`ERROR in ./some/file.ts:1:7`, 'TS123: Some issue content', ''].join(os.EOL)
5861
);
5962
});

0 commit comments

Comments
 (0)