UnusedRuleWarningPlugin.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Alexander Akait @alexander-akait
  4. */
  5. "use strict";
  6. const UnusedRuleWarning = require("../errors/UnusedRuleWarning");
  7. /**
  8. * @import {
  9. * RuleSetRule,
  10. * PerformanceOptions
  11. * } from "../../declarations/WebpackOptions"
  12. */
  13. /** @import Compiler from "../Compiler" */
  14. const PLUGIN_NAME = "UnusedRuleWarningPlugin";
  15. // `NormalModuleFactory` compiles `[{ rules: defaultRules }, { rules: rules }]`,
  16. // so only this subtree belongs to the configuration.
  17. const USER_RULES_PATH = "ruleSet[1].rules";
  18. /**
  19. * Names the rule the way its author would recognize it. `module.rules[7]` alone
  20. * is useless when a plugin injected the rule.
  21. * @param {RuleSetRule} rule the raw rule
  22. * @returns {string} a short descriptor, or an empty string when nothing identifies it
  23. */
  24. const describeRule = (rule) => {
  25. for (const key of ["test", "include", "exclude", "resource", "mimetype"]) {
  26. const value = rule[/** @type {keyof RuleSetRule} */ (key)];
  27. if (value instanceof RegExp) return ` (${key}: ${value})`;
  28. if (typeof value === "string") return ` (${key}: ${value})`;
  29. }
  30. if (typeof rule.loader === "string") return ` (loader: ${rule.loader})`;
  31. if (typeof rule.type === "string") return ` (type: ${rule.type})`;
  32. return "";
  33. };
  34. class UnusedRuleWarningPlugin {
  35. /**
  36. * Creates an instance of UnusedRuleWarningPlugin.
  37. * @param {PerformanceOptions} options the plugin options
  38. */
  39. constructor(options) {
  40. /** @type {PerformanceOptions["hints"]} */
  41. this.hints = options.hints;
  42. }
  43. /**
  44. * Applies the plugin by registering its hooks on the compiler.
  45. * @param {Compiler} compiler the compiler instance
  46. * @returns {void}
  47. */
  48. apply(compiler) {
  49. const hints = this.hints;
  50. compiler.hooks.compilation.tap(
  51. PLUGIN_NAME,
  52. (compilation, { normalModuleFactory }) => {
  53. compilation.hooks.afterSeal.tap(PLUGIN_NAME, () => {
  54. // Nothing was factorized, so no rule could match — an empty build is
  55. // not evidence that a rule is unused.
  56. if (compilation.modules.size === 0) return;
  57. /** @type {string[]} */
  58. const descriptions = [];
  59. for (const rule of normalModuleFactory.ruleSet.unusedRules()) {
  60. if (!rule.path.startsWith(USER_RULES_PATH)) continue;
  61. // A rule aimed at another compiler of a MultiCompiler is inactive
  62. // here by design, not by mistake.
  63. if (
  64. rule.conditions.some(
  65. (condition) => condition.property === "compiler"
  66. )
  67. ) {
  68. continue;
  69. }
  70. descriptions.push(
  71. `${rule.path.replace(USER_RULES_PATH, "module.rules")}${describeRule(
  72. rule.raw
  73. )}`
  74. );
  75. }
  76. if (descriptions.length === 0) return;
  77. const warning = new UnusedRuleWarning(descriptions);
  78. if (hints === "error") {
  79. compilation.errors.push(warning);
  80. } else if (hints === "stats") {
  81. compilation.hints.push(warning);
  82. } else {
  83. compilation.warnings.push(warning);
  84. }
  85. });
  86. }
  87. );
  88. }
  89. }
  90. module.exports = UnusedRuleWarningPlugin;