restrict-imports-loader
Version:
A Webpack loader to restrict imports in ES and TypeScript
124 lines • 4.72 kB
JavaScript
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