UNPKG

restrict-imports-loader

Version:

A Webpack loader to restrict imports in ES and TypeScript

124 lines 4.72 kB
import { getOptions } from "loader-utils"; import validateOptions from "schema-utils"; import * as core from "./core"; import * as deciders from "./deciders"; import { indentBy, quote } from "./text"; import { defaultTo } from "./utilities"; const DEFAULT = { info: `Found restricted imports:`, detailedErrorMessages: true, }; const CONFIG = { name: "restrict-imports-loader", }; const SEVERITIES = ["fatal", "error", "warning"]; const SCHEMA = { type: "object", required: ["rules", "severity"], properties: { detailedErrorMessages: { description: `Include the faulty import statement when printing an error message (default: ${quote(DEFAULT.detailedErrorMessages.toString())}). If disabled, only the import path (e.g. "typescript") is included.`, type: "boolean", }, rules: { description: "List of rules to check against.", items: { type: "object", required: ["restricted"], properties: { restricted: { description: `Regular expression or function (of type (string, webpack.loader.LoaderContext) => Promise<boolean>) specifying which imports should be restricted.`, anyOf: [ { instanceof: "RegExp" }, { instanceof: "Function" }, ], }, severity: { description: `Severity for this specific rule.`, anyOf: [ { enum: SEVERITIES }, ], }, info: { description: `An informational message to show to the user (default: ${quote(DEFAULT.info)})`, type: "string", }, }, }, }, severity: { description: `Controls what happens if a restricted import is detected. Can be overridden for individual rules.`, anyOf: [ { enum: SEVERITIES }, ], }, }, additionalProperties: false, }; export function run(loaderContext, source) { const callback = loaderContext.async(); if (callback === undefined) throw new Error(`Webpack did not provide an async callback.`); const options = getOptions(loaderContext); validateOptions(SCHEMA, options, CONFIG); const rules = options.rules; const detailedErrorMessages = defaultTo(DEFAULT.detailedErrorMessages, options.detailedErrorMessages); core.checkAsync({ source: source, deciders: rules.map(r => r.restricted).map(deciderFunction(loaderContext)), fileName: loaderContext.resourcePath, setParentNodes: detailedErrorMessages, }).then(badImportMatrix => { rules.forEach((rule, i) => { const badImports = badImportMatrix[i]; if (badImports.length > 0) { const severity = defaultTo(options.severity, rule.severity); const info = defaultTo(DEFAULT.info, rule.info); const err = new Error(errorMessageForAll(badImports, info, detailedErrorMessages)); switch (severity) { case "fatal": throw err; case "error": loaderContext.emitError(err); break; case "warning": loaderContext.emitWarning(err); break; default: const _ = severity; throw _; } } }); callback(null, source); }).catch(err => { callback(err, source); }); } function deciderFunction(loaderContext) { return decider => (decider instanceof RegExp ? deciders.matchedBy(decider) : importPath => decider(importPath, loaderContext)); } function errorMessageForAll(imports, info, setParentNodesWasUsed) { return [ info, "", indentBy(2)(imports.map(errorMessage(setParentNodesWasUsed)).join("")).trimRight(), "", "", ].join("\n"); } function errorMessage(setParentNodesWasUsed) { const details = (i) => (setParentNodesWasUsed ? [ `:`, ``, indentBy(6)(i.node.getText()), ``, i.info ? indentBy(2)(i.info) + "\n\n" : "", ].join("\n") : ""); return i => `• ` + quote(i.path) + `, imported on line ${i.line}` + details(i) + `\n`; } //# sourceMappingURL=loader.js.map