restrict-imports-loader
Advanced tools
Comparing version 2.0.0 to 3.0.0
import ts from "typescript"; | ||
export declare type ImportDetails = Readonly<{ | ||
declare type ImportDetails = Readonly<{ | ||
path: string; | ||
node: ts.Node; | ||
line: number; | ||
}>; | ||
export declare type Decider = RegExp | ((importPath: string) => boolean); | ||
export declare type Deciders = readonly Decider[]; | ||
export declare function check(x: { | ||
export declare type RestrictedImportDetails = ImportDetails & Readonly<{ | ||
info: string | undefined; | ||
}>; | ||
export declare type Decision = { | ||
restricted: false; | ||
} | { | ||
restricted: true; | ||
info?: string; | ||
}; | ||
export declare type AsyncDeciderFunction = (importPath: string) => Promise<Decision>; | ||
export declare function checkAsync(x: { | ||
source: string; | ||
deciders: Deciders; | ||
deciders: readonly AsyncDeciderFunction[]; | ||
fileName: string; | ||
setParentNodes: boolean; | ||
}): ReadonlyArray<ReadonlyArray<ImportDetails>>; | ||
}): Promise<ReadonlyArray<ReadonlyArray<RestrictedImportDetails>>>; | ||
export {}; |
@@ -7,32 +7,41 @@ "use strict"; | ||
const typescript_1 = __importDefault(require("typescript")); | ||
function check(x) { | ||
async function checkAsync(x) { | ||
const sourceFile = typescript_1.default.createSourceFile(x.fileName, x.source, typescript_1.default.ScriptTarget.Latest, x.setParentNodes); | ||
return x.deciders.map(decider => badImportsIn(sourceFile, decider)); | ||
const imports = importsIn(sourceFile); | ||
return Promise.all(x.deciders.map(decider => onlyRestricted(decider, imports))); | ||
} | ||
exports.check = check; | ||
function badImportsIn(rootNode, decider) { | ||
const errorAccumulator = []; | ||
exports.checkAsync = checkAsync; | ||
async function onlyRestricted(decider, is) { | ||
const isAndDecisions = await Promise.all(is.map(i => decider(i.path).then(decision => ({ i, decision })))); | ||
const results = []; | ||
for (const iAndD of isAndDecisions) { | ||
if (iAndD.decision.restricted) { | ||
results.push({ ...iAndD.i, info: iAndD.decision.info }); | ||
} | ||
} | ||
return results; | ||
} | ||
function importsIn(rootNode) { | ||
const accumulator = []; | ||
const getLineNumber = ((node) => 1 + rootNode.getLineAndCharacterOfPosition(node.pos).line); | ||
typescript_1.default.forEachChild(rootNode, node => { | ||
if (isInteresting(node)) | ||
checkInteresting(node, decider, errorAccumulator); | ||
lookForImportsIn(node, accumulator, getLineNumber); | ||
}); | ||
return errorAccumulator; | ||
return accumulator; | ||
} | ||
function checkInteresting(interestingNode, decider, errorAccumulator) { | ||
function lookForImportsIn(interestingNode, accumulator, getLineNumber) { | ||
interestingNode.forEachChild(node => { | ||
if (typescript_1.default.isStringLiteral(node) || typescript_1.default.isExternalModuleReference(node)) { | ||
const importPath = (typescript_1.default.isExternalModuleReference(node) | ||
const stringLiteral = (typescript_1.default.isExternalModuleReference(node) | ||
? node.expression | ||
: node).text; | ||
if (isRestricted(importPath, decider)) { | ||
errorAccumulator.push({ path: importPath, node: interestingNode }); | ||
} | ||
: node); | ||
accumulator.push({ | ||
path: stringLiteral.text, | ||
node: interestingNode, | ||
line: getLineNumber(stringLiteral), | ||
}); | ||
} | ||
}); | ||
} | ||
function isRestricted(name, decider) { | ||
return (decider instanceof RegExp | ||
? decider.test(name) | ||
: decider(name)); | ||
} | ||
function isInteresting(node) { | ||
@@ -39,0 +48,0 @@ return [ |
import * as webpack from "webpack"; | ||
import * as core from "./core"; | ||
export { Decider, Deciders, ImportDetails } from "./core"; | ||
export { LoaderOptions, Severity } from "./loader"; | ||
export default function (this: webpack.loader.LoaderContext, source: string): string; | ||
export declare function check(x: { | ||
source: string; | ||
restricted: core.Deciders; | ||
fileName?: string; | ||
setParentNodes?: boolean; | ||
}): ReadonlyArray<ReadonlyArray<core.ImportDetails>>; | ||
export declare function everythingIn(packageName: string): RegExp; | ||
export { RestrictedImportDetails, checkAsync } from "./core"; | ||
export { LoaderDecider, LoaderOptions, Severity } from "./loader"; | ||
export { everythingInPackage, everythingOutside, everythingInside, matchedBy } from "./deciders"; | ||
export default function (this: webpack.loader.LoaderContext, source: string): void; | ||
export declare type AsyncDecider = RegExp | core.AsyncDeciderFunction; |
@@ -10,22 +10,14 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const core = __importStar(require("./core")); | ||
const loader = __importStar(require("./loader")); | ||
const utilities_1 = require("./utilities"); | ||
var core_1 = require("./core"); | ||
exports.checkAsync = core_1.checkAsync; | ||
var deciders_1 = require("./deciders"); | ||
exports.everythingInPackage = deciders_1.everythingInPackage; | ||
exports.everythingOutside = deciders_1.everythingOutside; | ||
exports.everythingInside = deciders_1.everythingInside; | ||
exports.matchedBy = deciders_1.matchedBy; | ||
function default_1(source) { | ||
return loader.run(this, source); | ||
loader.run(this, source); | ||
} | ||
exports.default = default_1; | ||
function check(x) { | ||
return core.check({ | ||
source: x.source, | ||
deciders: x.restricted, | ||
fileName: utilities_1.defaultTo("", x.fileName), | ||
setParentNodes: utilities_1.defaultTo(true, x.setParentNodes), | ||
}); | ||
} | ||
exports.check = check; | ||
function everythingIn(packageName) { | ||
return new RegExp(String.raw `^${packageName}(\/.*)?$`); | ||
} | ||
exports.everythingIn = everythingIn; | ||
//# sourceMappingURL=index.js.map |
import * as webpack from "webpack"; | ||
import * as core from "./core"; | ||
export declare type Severity = "fatal" | "error" | "warning"; | ||
declare type LoaderContext = webpack.loader.LoaderContext; | ||
export declare type LoaderFunctionDecider = (importPath: string, loaderContext: webpack.loader.LoaderContext) => Promise<core.Decision>; | ||
export declare type LoaderDecider = RegExp | LoaderFunctionDecider; | ||
declare type LoaderRule = { | ||
restricted: RegExp; | ||
restricted: LoaderDecider; | ||
severity?: Severity; | ||
@@ -13,3 +17,3 @@ info?: string; | ||
}; | ||
export declare function run(loaderContext: webpack.loader.LoaderContext, source: string): string; | ||
export declare function run(loaderContext: LoaderContext, source: string): void; | ||
export {}; |
@@ -16,2 +16,3 @@ "use strict"; | ||
const core = __importStar(require("./core")); | ||
const deciders = __importStar(require("./deciders")); | ||
const text_1 = require("./text"); | ||
@@ -42,4 +43,7 @@ const utilities_1 = require("./utilities"); | ||
restricted: { | ||
description: `Regular expressions specifying which imports should be restricted.`, | ||
instanceof: "RegExp", | ||
description: `Regular expression or function (of type (string, webpack.loader.LoaderContext) => Promise<boolean>) specifying which imports should be restricted.`, | ||
anyOf: [ | ||
{ instanceof: "RegExp" }, | ||
{ instanceof: "Function" }, | ||
], | ||
}, | ||
@@ -69,36 +73,47 @@ severity: { | ||
function run(loaderContext, source) { | ||
const callback = loaderContext.async(); | ||
if (callback === undefined) | ||
throw new Error(`Webpack did not provide an async callback.`); | ||
const options = loader_utils_1.getOptions(loaderContext); | ||
schema_utils_1.default(SCHEMA, options, CONFIG); | ||
const rules = options.rules; | ||
const setParentNodes = utilities_1.defaultTo(true, options.detailedErrorMessages); | ||
const badImportMatrix = core.check({ | ||
const detailedErrorMessages = utilities_1.defaultTo(DEFAULT.detailedErrorMessages, options.detailedErrorMessages); | ||
core.checkAsync({ | ||
source: source, | ||
deciders: rules.map(r => r.restricted), | ||
deciders: rules.map(r => r.restricted).map(deciderFunction(loaderContext)), | ||
fileName: loaderContext.resourcePath, | ||
setParentNodes: setParentNodes, | ||
}); | ||
rules.forEach((rule, i) => { | ||
const badImports = badImportMatrix[i]; | ||
if (badImports.length > 0) { | ||
const severity = utilities_1.defaultTo(options.severity, rule.severity); | ||
const info = utilities_1.defaultTo(DEFAULT.info, rule.info); | ||
const err = new Error(errorMessageForAll(badImports, info, setParentNodes)); | ||
switch (severity) { | ||
case "fatal": | ||
throw err; | ||
case "error": | ||
loaderContext.emitError(err); | ||
break; | ||
case "warning": | ||
loaderContext.emitWarning(err); | ||
break; | ||
default: | ||
const _ = severity; | ||
throw _; | ||
setParentNodes: detailedErrorMessages, | ||
}).then(badImportMatrix => { | ||
rules.forEach((rule, i) => { | ||
const badImports = badImportMatrix[i]; | ||
if (badImports.length > 0) { | ||
const severity = utilities_1.defaultTo(options.severity, rule.severity); | ||
const info = utilities_1.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); | ||
}); | ||
return source; | ||
} | ||
exports.run = run; | ||
function deciderFunction(loaderContext) { | ||
return decider => (decider instanceof RegExp | ||
? deciders.matchedBy(decider) | ||
: importPath => decider(importPath, loaderContext)); | ||
} | ||
function errorMessageForAll(imports, info, setParentNodesWasUsed) { | ||
@@ -116,11 +131,11 @@ return [ | ||
? [ | ||
`, imported here:`, | ||
`:`, | ||
``, | ||
text_1.indentBy(6)(i.node.getText()), | ||
``, | ||
``, | ||
i.info ? text_1.indentBy(2)(i.info) + "\n\n" : "", | ||
].join("\n") | ||
: ""); | ||
return i => `• ` + text_1.quote(i.path) + details(i) + `\n`; | ||
return i => `• ` + text_1.quote(i.path) + `, imported on line ${i.line}` + details(i) + `\n`; | ||
} | ||
//# sourceMappingURL=loader.js.map |
{ | ||
"name": "restrict-imports-loader", | ||
"version": "2.0.0", | ||
"version": "3.0.0", | ||
"description": "A Webpack loader to restrict imports in ES and TypeScript", | ||
@@ -44,5 +44,5 @@ "keywords": [ | ||
"rename": "renamer --force --find \"/\\.js$/\" --replace \".mjs\" \"dist/**\"", | ||
"jest": "jest", | ||
"test": "npm run lint && npm run jest", | ||
"verify": "repository-check-dirty && npm run build && npm test && npm pack" | ||
"test": "jest", | ||
"test-clean": "jest --no-cache", | ||
"verify": "repository-check-dirty && npm run build && npm run lint && npm run test-clean && npm pack" | ||
}, | ||
@@ -66,2 +66,3 @@ "sideEffects": false, | ||
"devDependencies": { | ||
"@types/enhanced-resolve": "^3.0.6", | ||
"@types/jest": "^24.0.23", | ||
@@ -71,2 +72,3 @@ "@types/webpack": "^4.39.9", | ||
"cli-confirm": "^1.0.1", | ||
"enhanced-resolve": "^4.1.1", | ||
"jest": "^24.9.0", | ||
@@ -73,0 +75,0 @@ "no-emit-webpack-plugin": "^2.0.1", |
@@ -13,4 +13,6 @@ # restrict-imports-loader | ||
**NOTE:** Only static imports are supported; see _Limitations_. | ||
**NOTE:** Only static imports are supported; see [_Limitations_](#limitations). | ||
_See also [the complete example repo](https://github.com/SimonAlling/restrict-imports-loader-example)._ | ||
Configuration example (`webpack.config.js`): | ||
@@ -26,3 +28,3 @@ | ||
include: path.resolve(__dirname, "src"), | ||
loaders: [ | ||
use: [ | ||
{ | ||
@@ -54,3 +56,3 @@ loader: "awesome-typescript-loader", // or babel-loader, etc | ||
import * as _ from "lodash"; // error | ||
import * as fp from "lodash/fp"; // OK (see "Restrict Entire Package" for more info) | ||
import * as fp from "lodash/fp"; // OK (see "Restricting an entire package" for more info) | ||
``` | ||
@@ -65,3 +67,3 @@ | ||
• "lodash", imported here: | ||
• "lodash", imported on line 2: | ||
@@ -82,3 +84,3 @@ import * as _ from "lodash"; | ||
Must be a list in which each element has a `restricted` property with a `RegExp` value. | ||
Must be a list in which each element has a `restricted` property with a `RegExp` or function value. | ||
Each rule can also override the `severity` defined for the loader. | ||
@@ -108,6 +110,34 @@ Example: | ||
##### Using a function as decider | ||
If you provide a function as the `restricted` value, it must have the type | ||
```typescript | ||
(string, webpack.loader.LoaderContext) => Promise<boolean> | ||
``` | ||
where the `string` parameter represents the import path in each import statement, e.g. `typescript` in `import * as ts from "typescript";`. | ||
This way, you can use any algorithm you want to determine if an import should be restricted, possibly depending on the loader context. | ||
In the example below (written in TypeScript), if `decider` is used as the `restricted` value, all imports from outside the project root directory are restricted. | ||
(The "project root directory" is whatever directory you've specified using [Webpack's `context` option](https://webpack.js.org/configuration/entry-context/#context), or, if not specified, the "current working directory" as seen from Webpack's perspective.) | ||
```typescript | ||
import { LoaderDecider } from "restrict-imports-loader"; | ||
const decider: LoaderDecider = (importPath, loaderContext) => new Promise((resolve, reject) => { | ||
loaderContext.resolve(loaderContext.context, importPath, (err, result) => { | ||
if (err === null) { | ||
resolve(false === result.startsWith(loaderContext.rootContext)); | ||
} else { | ||
reject(err.message); | ||
} | ||
}); | ||
}); | ||
``` | ||
#### `detailedErrorMessages` | ||
By default, error messages include the faulty import statement exactly as written: | ||
By default, error messages include the faulty import statements exactly as written, as well as any extra info provided by the decider, for example: | ||
@@ -117,8 +147,10 @@ ``` | ||
• "typescript", imported here: | ||
• "typescript", imported on line 1: | ||
import * as _ from "typescript"; | ||
(resolved: node_modules/typescript/lib/typescript.js) | ||
``` | ||
Setting `detailedErrorMessages` to `false` means that error messages will only include the import path: | ||
Setting `detailedErrorMessages` to `false` means that error messages will only include the import path and line number: | ||
@@ -128,3 +160,3 @@ ``` | ||
• "typescript" | ||
• "typescript", imported on line 1 | ||
``` | ||
@@ -135,8 +167,8 @@ | ||
### Restrict Entire Package | ||
### Restricting an entire package | ||
If you want to restrict an entire package, including its submodules, you can use `everythingIn` for convenience and readability: | ||
If you want to restrict an entire package, including its submodules, you can use `everythingInPackage` for convenience and readability: | ||
```javascript | ||
const { everythingIn } = require("restrict-imports-loader"); | ||
const { everythingInPackage } = require("restrict-imports-loader"); | ||
@@ -151,3 +183,3 @@ module.exports = { | ||
{ | ||
restricted: everythingIn("lodash"), | ||
restricted: everythingInPackage("lodash"), | ||
}, | ||
@@ -164,2 +196,3 @@ ], | ||
import * as ts from "typescript"; // OK | ||
import * as ld from "lodasher"; // OK | ||
import * as _ from "lodash"; // error | ||
@@ -169,4 +202,40 @@ import * as fp from "lodash/fp"; // error | ||
**Note:** `everythingInPackage` is `RegExp`-based, so it can't prevent the programmer from importing the restricted package using a relative import: | ||
```typescript | ||
import * as _ from "../node_modules/lodash"; // OK | ||
``` | ||
You must [use a function as decider](#using-a-function-as-decider) if you want to prevent that. | ||
See [_Blacklisting or whitelisting directories_](#blacklisting-or-whitelisting-directories) for a convenient approach. | ||
### Blacklisting or whitelisting directories | ||
You can use `everythingInside` or `everythingOutside` to blacklist or whitelist, respectively, a set of **absolute** directories: | ||
```typescript | ||
const { everythingOutside } = require("restrict-imports-loader"); | ||
module.exports = { | ||
// ... | ||
{ | ||
loader: "restrict-imports-loader", | ||
options: { | ||
severity: "warning", | ||
rules: [ | ||
{ | ||
restricted: everythingOutside([ | ||
path.resolve(__dirname, "node_modules"), | ||
path.resolve(__dirname, "src"), | ||
]), | ||
info: `Imports should resolve to 'node_modules' or 'src'. These do not:`, | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
``` | ||
### Limitations | ||
@@ -173,0 +242,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
39608
27
543
253
14