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
2 changes: 1 addition & 1 deletion .changeset/merge-eslint-and-stylelint-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"diagnostics-webpack-plugin": major
---

Renamed from `eslint-webpack-plugin` and merged with `stylelint-webpack-plugin`: one plugin runs every linter through the `checks` option, `new DiagnosticsPlugin({ checks: [{ use: "eslint" }, { use: "stylelint" }] })`. Errors are reported as webpack errors and warnings as webpack warnings, with `failOnError` and `failOnWarning` deciding whether the build fails; `stylelint` must be 17 or later. See the migration guides in the README.
Renamed from `eslint-webpack-plugin` and merged with `stylelint-webpack-plugin`: one plugin runs every linter through the `checks` option, `new DiagnosticsPlugin({ checks: [{ use: "eslint" }, { use: "stylelint" }] })`. Errors are reported as webpack errors and warnings as webpack warnings, with `reportAs` deciding what each is reported as; `stylelint` must be 17 or later. See the migration guides in the README.
5 changes: 5 additions & 0 deletions .changeset/union-report-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"diagnostics-webpack-plugin": major
---

`emitError`, `emitWarning`, `failOnError` and `failOnWarning` are one `reportAs` option taking `"error"`, `"warning"` or `false`: it says what a check reports its results as, and reporting one as a webpack error is what fails the build. Left unset each result keeps its own severity, `quiet` still drops the warnings, and the build is no longer aborted from inside the plugin. See the migration table in the README.
80 changes: 36 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ new DiagnosticsPlugin({
// Plugin options
context: "src",
// Shared options, every check uses them unless it says otherwise
failOnError: true,
reportAs: "warning", // report every check's results as warnings
exclude: ["node_modules", "vendor"],
// The checks to run, each with the options only it understands
checks: [
Expand Down Expand Up @@ -235,56 +235,37 @@ See the [ESLint formatters](https://eslint.org/docs/user-guide/formatters/) and

### Errors and warnings

Every check reports its errors as webpack errors and its warnings as webpack warnings. `emitError` and `emitWarning` choose what is reported at all, and `failOnError` and `failOnWarning` choose whether the build is failed over it.
Every check reports its errors as webpack errors and its warnings as webpack warnings, which is what fails the build. `reportAs` overrides that, and `quiet` drops the warnings.

#### `emitError`
#### `reportAs`

- Type:

```ts
type emitError = boolean;
type reportAs = "error" | "warning" | false;
```

- Default: `true`

The errors found will always be emitted, to disable set to `false`.

#### `emitWarning`

- Type:
- Default: unset — each result stays at the severity the check gave it

```ts
type emitWarning = boolean;
```
What a check reports its results as. Left unset, an error is a webpack error and a warning a webpack warning; naming one severity reports every result as that one, and `false` reports nothing.

- Default: `true`

The warnings found will always be emitted, to disable set to `false`.

#### `failOnError`

- Type:

```ts
type failOnError = boolean;
```
| Value | Effect |
| :---------- | :-------------------------------------------------------- |
| unset | Errors fail the build, warnings do not. |
| `"error"` | Everything fails the build, warnings included. |
| `"warning"` | Nothing fails the build; errors are reported as warnings. |
| `false` | Nothing is reported. An `outputReport` is still written. |

- Default: `true`, `false` in `development` mode
Together with [`quiet`](#quiet), which drops the warnings before any of this, that covers reporting and failing in one option:

Will cause the module build to fail if any errors are found, to disable set to `false`.

#### `failOnWarning`

- Type:

```ts
type failOnWarning = boolean;
```js
new DiagnosticsPlugin({
reportAs: "warning", // report everything without failing the build
quiet: true, // and leave the warnings out of it
checks: [{ use: "eslint" }],
});
```

- Default: `false`

Will cause the module build to fail if any warnings are found, if set to `true`.

#### `quiet`

- Type:
Expand All @@ -295,7 +276,7 @@ type quiet = boolean;

- Default: `false`

Will process and report errors only and ignore warnings, if set to `true`.
Will process and report errors only and ignore warnings, if set to `true`. It drops the warnings before [`reportAs`](#reportas) decides what the rest is reported as.

#### `outputReport`

Expand Down Expand Up @@ -440,7 +421,7 @@ new DiagnosticsPlugin({
});
```

Such an adapter is an object with a `name`, and a `create` returning the five functions the plugin drives it through — what to lint, what came back, which results are errors and which warnings, how to format them, and what to release afterwards:
Such an adapter is an object with a `name`, and a `create` returning the five functions the plugin drives it through — what to lint, what came back, which results are errors and which warnings, how to format them, and what to release afterwards. It splits its results by their own severity and nothing else; [`reportAs`](#reportas) and [`quiet`](#quiet) are applied to what it returns:

```js
module.exports = {
Expand Down Expand Up @@ -483,7 +464,19 @@ Move the options you were passing into a `checks` entry:
};
```

The shared options — `context`, `files`, `exclude`, `failOnError` and the rest of [Errors and warnings](#errors-and-warnings) — may stay at the top level instead. Everything else behaves as it did, and the default `cacheLocation` moved to `node_modules/.cache/diagnostics-webpack-plugin/.eslintcache`.
`emitError`, `emitWarning`, `failOnError` and `failOnWarning` are one [`reportAs`](#reportas) option now, because reporting a result as a webpack error is what fails the build — there is nothing left for a second option to say:

| Was | Is |
| :------------------------------------------ | :-------------------- |
| `emitWarning: false` | `quiet: true` |
| `emitError: false` and `emitWarning: false` | `reportAs: false` |
| `failOnError: true` | the default |
| `failOnError: false` | `reportAs: "warning"` |
| `failOnWarning: true` | `reportAs: "error"` |

The build is no longer aborted from inside the plugin: a result reported as a webpack error fails the build the way every other webpack error does, and the assets are still written. `emitError: false` on its own has no counterpart — reporting the warnings of a check while hiding its errors was never useful.

The shared options — `context`, `files`, `exclude`, `reportAs` and the rest of [Errors and warnings](#errors-and-warnings) — may stay at the top level instead. Everything else behaves as it did, and the default `cacheLocation` moved to `node_modules/.cache/diagnostics-webpack-plugin/.eslintcache`.

### From `stylelint-webpack-plugin`

Expand All @@ -506,8 +499,8 @@ Move the options you were passing into a `checks` entry:
Three things changed beyond the option shape:

- **Stylelint 17 or later is required.** `stylelint-webpack-plugin` accepted `13` through `17`; the merged plugin drops the older majors rather than carrying their compatibility branches forward. Stylelint 17 itself needs Node `>= 20.19`.
- **Errors and warnings are no longer swapped.** Errors are reported as webpack errors and warnings as webpack warnings, whatever `failOnError` and `failOnWarning` say; those two now decide whether the build is failed, not how a problem is reported. Previously `failOnError: false` turned errors into warnings, and `failOnWarning: true` turned warnings into errors.
- **`failOnError` defaults to `false` in `development` mode**, matching the rest of the plugin, rather than being `true` everywhere.
- **Errors and warnings are no longer swapped.** Errors are reported as webpack errors and warnings as webpack warnings, unless [`reportAs`](#reportas) names one severity for all of them. Previously `failOnError: false` turned errors into warnings, and `failOnWarning: true` turned warnings into errors.
- **The build is no longer aborted from inside the plugin.** A result reported as a webpack error fails the build the way every other webpack error does, and the assets are still written.

The default `cacheLocation` moved to `node_modules/.cache/diagnostics-webpack-plugin/.stylelintcache`.

Expand All @@ -522,7 +515,6 @@ The two plugins become one instance, and options they had in common are written
- new StylelintPlugin({ context: "src", failOnError: true, extensions: ["css"] }),
+ new DiagnosticsPlugin({
+ context: "src",
+ failOnError: true,
+ checks: [
+ { use: "eslint", extensions: ["js"] },
+ { use: "stylelint", extensions: ["css"] },
Expand Down
16 changes: 10 additions & 6 deletions src/check.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,16 @@ function createCheckRunner(key, { name, adapter, options }, compilation) {
/** @type {Report} */
const report = {};

if (warnings.length > 0) {
report.warnings = new DiagnosticError(name, await format(warnings));
}

if (errors.length > 0) {
report.errors = new DiagnosticError(name, await format(errors));
// `quiet` drops the warnings and `reportAs: false` everything, but an
// `outputReport` is still written from all of the results below.
if (options.reportAs !== false) {
if (warnings.length > 0 && !options.quiet) {
report.warnings = new DiagnosticError(name, await format(warnings));
}

if (errors.length > 0) {
report.errors = new DiagnosticError(name, await format(errors));
}
}

const { outputReport } = options;
Expand Down
12 changes: 4 additions & 8 deletions src/checks/eslint.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,22 +223,18 @@ async function create({ options }) {
for (const file of /** @type {LintResult[]} */ (results)) {
if (file.errorCount > 0) {
const messages = file.messages.filter(
(message) => options.emitError && message.severity === 2,
(message) => message.severity === 2,
);

if (messages.length > 0) {
errors.push({ ...file, messages });
}
if (messages.length > 0) errors.push({ ...file, messages });
}

if (file.warningCount > 0) {
const messages = file.messages.filter(
(message) => options.emitWarning && message.severity === 1,
(message) => message.severity === 1,
);

if (messages.length > 0) {
warnings.push({ ...file, messages });
}
if (messages.length > 0) warnings.push({ ...file, messages });
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/checks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import stylelint from "./stylelint.js";
* @typedef {object} CheckInstance
* @property {(files: string[]) => Promise<CheckResult[]>} lintFiles lints the given files
* @property {(results: CheckResult[]) => Promise<CheckResult[]>} getResults turns the raw results of every `lintFiles` call into the results to report
* @property {(results: CheckResult[]) => { errors: CheckResult[], warnings: CheckResult[] }} splitResults splits results into the ones reported as errors and as warnings
* @property {(results: CheckResult[]) => { errors: CheckResult[], warnings: CheckResult[] }} splitResults splits the results by their own severity, leaving `reportAs` and `quiet` to the plugin
* @property {(formatter?: FormatterOption) => Promise<Format>} getFormatter loads a formatter, falling back to the tool's default one
* @property {() => Promise<void>} cleanup releases whatever the tool holds after a run
*/
Expand Down
4 changes: 2 additions & 2 deletions src/checks/stylelint.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,15 +273,15 @@ async function create({ key, options, compilation }) {

for (const file of /** @type {LintResult[]} */ (results)) {
const fileErrors = file.warnings.filter(
(message) => options.emitError && message.severity === "error",
(message) => message.severity === "error",
);

if (fileErrors.length > 0) {
errors.push({ ...file, warnings: fileErrors });
}

const fileWarnings = file.warnings.filter(
(message) => options.emitWarning && message.severity === "warning",
(message) => message.severity === "warning",
);

if (fileWarnings.length > 0) {
Expand Down
30 changes: 15 additions & 15 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,6 @@ class DiagnosticsWebpackPlugin {
const resolved = {
...options,
context,
failOnError:
options.failOnError ?? compiler.options.mode !== "development",
exclude: options.exclude
? parseFiles(options.exclude, context)
: adapter.defaultExclude(compiler),
Expand Down Expand Up @@ -249,18 +247,28 @@ class DiagnosticsWebpackPlugin {
async (_, callback) => {
/** @type {Map<string, string[]>} */
const outputReports = new Map();
/** @type {Error | undefined} */
let failure;

for (const { options, runner } of runners) {
const { errors, warnings, outputReport } = await runner.report();

// `reportAs` names the one place every result goes; left unset, each
// stays at the severity the check gave it.
if (warnings) {
compilation.warnings.push(warnings);
const reported =
options.reportAs === "error"
? compilation.errors
: compilation.warnings;

reported.push(warnings);
}

if (errors) {
compilation.errors.push(errors);
const reported =
options.reportAs === "warning"
? compilation.warnings
: compilation.errors;

reported.push(errors);
}

if (outputReport) {
Expand All @@ -269,14 +277,6 @@ class DiagnosticsWebpackPlugin {
contents.push(outputReport.content);
outputReports.set(outputReport.filePath, contents);
}

if (!failure) {
if (warnings && options.failOnWarning) {
failure = warnings;
} else if (errors && options.failOnError) {
failure = errors;
}
}
}

await Promise.all(
Expand All @@ -285,7 +285,7 @@ class DiagnosticsWebpackPlugin {
),
);

callback(failure);
callback();
},
);
});
Expand Down
25 changes: 5 additions & 20 deletions src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const nodeRequire = createRequire(import.meta.url);
const PLUGIN_NAME = "Diagnostics Webpack Plugin";

/** @typedef {import("webpack").Compiler} Compiler */
/** @typedef {"error" | "warning" | false} ReportAs */
/** @typedef {import("./checks/index.js").FormatterOption} FormatterOption */
/** @typedef {import("./checks/index.js").CheckAdapter} CheckAdapter */
/** @typedef {import("./checks/index.js").CheckAdapterInput} CheckAdapterInput */
Expand All @@ -27,12 +28,9 @@ const PLUGIN_NAME = "Diagnostics Webpack Plugin";
* @typedef {object} SharedOptions
* @property {boolean=} cache enable the tool's cache to decrease execution time
* @property {string=} cacheLocation specify the path to the cache location
* @property {boolean=} emitError the errors found will always be emitted
* @property {boolean=} emitWarning the warnings found will always be emitted
* @property {ReportAs=} reportAs what a check reports its results as
* @property {string | string[]=} exclude specify the files and/or directories to exclude
* @property {string | string[]=} extensions specify the extensions that should be checked
* @property {boolean=} failOnError will cause the module build to fail if there are any errors
* @property {boolean=} failOnWarning will cause the module build to fail if there are any warnings
* @property {string | string[]=} files specify directories, files, or globs
* @property {boolean=} fix apply fixes
* @property {FormatterOption=} formatter specify the formatter you would like to use to format your results
Expand All @@ -46,6 +44,8 @@ const PLUGIN_NAME = "Diagnostics Webpack Plugin";
*/

/**
* The options of one check, as given and then as the plugin resolves them
* against a compiler.
* @typedef {SharedOptions & { [option: string]: EXPECTED_ANY }} CheckOptions
*/

Expand All @@ -72,11 +72,6 @@ const PLUGIN_NAME = "Diagnostics Webpack Plugin";
* @property {EnabledCheck[]} checks the checks to run
*/

const SHARED_DEFAULTS = {
emitError: true,
emitWarning: true,
};

const DEFAULT_FOLDER_TO_EXCLUDE = "**/node_modules/**";

/** @type {{ schema: EXPECTED_ANY, entrySchema: EXPECTED_ANY } | undefined} */
Expand Down Expand Up @@ -176,17 +171,7 @@ function getOptions(pluginOptions) {
const adapter = toAdapter(use);

/** @type {CheckOptions} */
const options = {
...SHARED_DEFAULTS,
...adapter.defaults,
...shared,
...own,
};

if (options.quiet) {
options.emitError = true;
options.emitWarning = false;
}
const options = { ...adapter.defaults, ...shared, ...own };

return { name: adapter.name, adapter, options };
});
Expand Down
Loading
Loading