UNPKG

webpack

Version:

Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.

179 lines (146 loc) 4.72 kB
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Alexander Akait @alexander-akait */ "use strict"; const OsDependentRuleWarning = require("../errors/OsDependentRuleWarning"); /** * @import { * Falsy, * PerformanceOptions, * RuleSetCondition, * RuleSetRule * } from "../../declarations/WebpackOptions" */ /** @import Compiler from "../Compiler" */ const PLUGIN_NAME = "OsDependentRuleWarningPlugin"; // Only these carry a path. `resourceQuery`, `resourceFragment`, `mimetype` and // `scheme` legitimately contain a `/`. const PATH_PROPERTIES = [ "test", "include", "exclude", "resource", "realResource", "issuer" ]; /** @typedef {"posix" | "windows" | undefined} Separator */ /** * Which separator a pattern can match, walking its source so an escape is never * mistaken for the separator after it (`\.` is not `\\`); both means portable. * @param {RegExp} regExp the condition * @returns {Separator} the only separator it matches, or `undefined` when it is portable */ const onlyMatchedSeparator = (regExp) => { const { source } = regExp; let posix = false; let windows = false; for (let i = 0; i < source.length; i++) { const character = source[i]; if (character === "\\") { const escaped = source[i + 1]; if (escaped === "\\") { windows = true; } else if (escaped === "/") { posix = true; } i++; } else if (character === "/") { posix = true; } } if (posix === windows) return undefined; return posix ? "posix" : "windows"; }; /** * @param {RuleSetCondition} condition the condition to walk * @param {(regExp: RegExp) => void} callback called for every regexp within * @returns {void} */ const forEachRegExp = (condition, callback) => { if (condition instanceof RegExp) { callback(condition); } else if (Array.isArray(condition)) { for (const item of condition) forEachRegExp(item, callback); } else if (condition && typeof condition === "object") { // `not` is skipped: negated, a separator says which paths are *excluded*, // so the condition still matches the other OS. for (const key of ["and", "or"]) { const nested = condition[/** @type {keyof typeof condition} */ (key)]; if (nested !== undefined) { forEachRegExp(/** @type {RuleSetCondition} */ (nested), callback); } } } }; class OsDependentRuleWarningPlugin { /** * Creates an instance of OsDependentRuleWarningPlugin. * @param {PerformanceOptions} options the plugin options */ constructor(options) { /** @type {PerformanceOptions["hints"]} */ this.hints = options.hints; } /** * Applies the plugin by registering its hooks on the compiler. * @param {Compiler} compiler the compiler instance * @returns {void} */ apply(compiler) { const hints = this.hints; const rules = compiler.options.module && compiler.options.module.rules ? compiler.options.module.rules : []; /** @type {string[]} */ const descriptions = []; /** * @param {(RuleSetRule | Falsy)[]} ruleSet rules to walk * @param {string} path where `ruleSet` sits in the configuration * @returns {void} */ const walk = (ruleSet, path) => { for (let i = 0; i < ruleSet.length; i++) { const rule = ruleSet[i]; if (!rule || typeof rule !== "object") continue; const rulePath = `${path}[${i}]`; for (const property of PATH_PROPERTIES) { const condition = rule[/** @type {keyof RuleSetRule} */ (property)]; if (condition === undefined) continue; forEachRegExp( /** @type {RuleSetCondition} */ (condition), (regExp) => { const separator = onlyMatchedSeparator(regExp); if (separator === undefined) return; descriptions.push( `${rulePath}.${property} (${regExp}) only matches ${ separator === "posix" ? "'/'" : "'\\'" } paths` ); } ); } if (Array.isArray(rule.oneOf)) walk(rule.oneOf, `${rulePath}.oneOf`); if (Array.isArray(rule.rules)) walk(rule.rules, `${rulePath}.rules`); } }; walk(/** @type {RuleSetRule[]} */ (rules), "module.rules"); if (descriptions.length === 0) return; compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => { // Reported past the hash: `createHash` folds every message into it, so // a hint pushed earlier would change the build's identity. compilation.hooks.afterSeal.tap(PLUGIN_NAME, () => { const warning = new OsDependentRuleWarning(descriptions); if (hints === "error") { compilation.errors.push(warning); } else if (hints === "stats") { compilation.hints.push(warning); } else { compilation.warnings.push(warning); } }); }); } } module.exports = OsDependentRuleWarningPlugin;