4 * Copyright 2018 Palantir Technologies, Inc.
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 Object.defineProperty(exports, "__esModule", { value: true });
19 var tslib_1 = require("tslib");
20 var tsutils_1 = require("tsutils");
21 var Lint = require("../index");
22 var Rule = /** @class */ (function (_super) {
23 tslib_1.__extends(Rule, _super);
25 return _super !== null && _super.apply(this, arguments) || this;
27 Rule.MAKE_NAMED_IMPORT_FAILURE_STRING = function (importName) {
28 return importName === "default"
29 ? "Importing (or re-exporting) the default export is blacklisted."
30 : "The export \"" + importName + "\" is blacklisted.";
32 Rule.prototype.isEnabled = function () {
33 return _super.prototype.isEnabled.call(this) && this.ruleArguments.length > 0;
35 Rule.prototype.apply = function (sourceFile) {
36 return this.applyWithFunction(sourceFile, walk, this.ruleArguments);
38 /* tslint:disable:object-literal-sort-keys */
40 ruleName: "import-blacklist",
41 description: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\n Disallows importing the specified modules via `import` and `require`,\n or importing specific named exports of the specified modules,\n or using imports matching specified regular expression patterns."], ["\n Disallows importing the specified modules via \\`import\\` and \\`require\\`,\n or importing specific named exports of the specified modules,\n or using imports matching specified regular expression patterns."]))),
42 rationale: Lint.Utils.dedent(templateObject_2 || (templateObject_2 = tslib_1.__makeTemplateObject(["\n For some libraries, importing the library directly can cause unused\n submodules to be loaded, so you may want to block these imports and\n require that users directly import only the submodules they need.\n In other cases, you may simply want to ban an import because using\n it is undesirable or unsafe."], ["\n For some libraries, importing the library directly can cause unused\n submodules to be loaded, so you may want to block these imports and\n require that users directly import only the submodules they need.\n In other cases, you may simply want to ban an import because using\n it is undesirable or unsafe."]))),
43 optionsDescription: "A list of blacklisted modules, named imports, or regular expression patterns.",
54 additionalProperties: {
75 [true, "rxjs", "lodash"],
76 [true, "lodash", { lodash: ["pull", "pullAll"] }],
77 [true, "rxjs", { lodash: ["pull", "pullAll"] }, [".*\\.temp$", ".*\\.tmp$"]],
79 type: "functionality",
80 typescriptOnly: false,
82 Rule.WHOLE_MODULE_FAILURE_STRING = "Importing this module is blacklisted. Try importing a submodule instead.";
83 Rule.IMPLICIT_NAMED_IMPORT_FAILURE_STRING = "Some named exports from this module are blacklisted for importing " +
84 "(or re-exporting). Import/re-export only the specific values you want, " +
85 "instead of the whole module.";
86 Rule.FAILURE_STRING_REGEX = "This import is blacklisted by ";
88 }(Lint.Rules.AbstractRule));
91 // Merge/normalize options.
92 // E.g., ["a", { "b": ["c"], "d": ["e", "e"] }, "f", { "f": ["g"] }]
93 // becomes { "a": true, "b": Set(["c"]), "d": Set(["e"]), "f": true }.
94 var bannedImports = ctx.options.reduce(function (acc, it) {
95 if (typeof it === "string") {
98 else if (!Array.isArray(it)) {
99 Object.keys(it).forEach(function (moduleName) {
100 if (acc[moduleName] instanceof Set) {
101 it[moduleName].forEach(function (bannedImport) {
102 acc[moduleName].add(bannedImport);
105 else if (acc[moduleName] !== true) {
106 acc[moduleName] = new Set(it[moduleName]);
111 }, Object.create(null));
112 var regexOptions = [];
113 for (var _i = 0, _a = ctx.options; _i < _a.length; _i++) {
115 if (Array.isArray(option)) {
116 for (var _b = 0, option_1 = option; _b < option_1.length; _b++) {
117 var pattern = option_1[_b];
118 regexOptions.push(RegExp(pattern));
122 for (var _c = 0, _d = tsutils_1.findImports(ctx.sourceFile, 63 /* All */); _c < _d.length; _c++) {
124 // TODO #3963: Resolve/normalize relative file imports to a canonical path?
125 var importedModule = name.text;
126 var bansForModule = bannedImports[importedModule];
127 // Check if at least some imports from this module are banned.
128 if (bansForModule !== undefined) {
129 // If importing this module is totally banned, we can error now,
130 // without determining whether the user is importing the whole
131 // module or named exports.
132 if (bansForModule === true) {
133 ctx.addFailure(name.getStart(ctx.sourceFile) + 1, name.end - 1, Rule.WHOLE_MODULE_FAILURE_STRING);
136 // Otherwise, find the named imports, if any, and fail if the
137 // user tried to import any of them. We don't have named imports
138 // when the user is importing the whole module, which includes:
140 // - ImportKind.Require (i.e., `require('module-specifier')`),
141 // - ImportKind.DynamicImport (i.e., `import("module-specifier")`),
142 // - ImportKind.ImportEquals (i.e., `import x = require()`),
143 // - and ImportKind.ImportDeclaration, where there's a full namespace
144 // import (i.e. `import * as x from "module-specifier"`)
146 // However, namedImports will be an array when we have one of the
147 // various permutations of `import x, { a, b as c } from "y"`.
149 // We treat re-exports from other modules the same as attempting to
150 // import the re-exported binding(s), as the re-export is essentially
151 // an import followed by an export, and not treating these as an
152 // import would allow backdoor imports of the banned bindings. So,
153 // our last case is `ImportKind.ExportFrom`, and for that:
155 // - `export nameForDefault from "module"` isn't part of the ESM
156 // syntax (yet), so we only have to handle two cases below:
157 // `export { x } from "y"` and `export * from "specifier"`.
158 var parentNode = name.parent;
159 // Disable strict-boolean-expressions for the next few lines so our &&
160 // checks can help type inference figure out if when don't have undefined.
161 // tslint:disable strict-boolean-expressions
162 var importClause = parentNode && tsutils_1.isImportDeclaration(parentNode) ? parentNode.importClause : undefined;
163 var importsDefaultExport = importClause && Boolean(importClause.name);
164 // Below, check isNamedImports to rule out the
165 // `import * as ns from "..."` case.
166 var importsSpecificNamedExports = importClause &&
167 importClause.namedBindings &&
168 tsutils_1.isNamedImports(importClause.namedBindings);
169 // If parentNode is an ExportDeclaration, it must represent an
170 // `export from`, as findImports verifies that. Then, if exportClause
171 // is undefined, we're dealing with `export * from ...`.
172 var reExportsSpecificNamedExports = parentNode && tsutils_1.isExportDeclaration(parentNode) && Boolean(parentNode.exportClause);
173 // tslint:enable strict-boolean-expressions
174 if (importsDefaultExport ||
175 importsSpecificNamedExports ||
176 reExportsSpecificNamedExports) {
177 // Add an import for the default import and any named bindings.
178 // For the named bindings, we use the name of the export from the
179 // module (i.e., .propertyName) over its alias in the import when
181 var toExportName = function (it) {
182 return (it.propertyName || it.name).text;
183 }; // tslint:disable-line strict-boolean-expressions
184 var exportClause = reExportsSpecificNamedExports
185 ? parentNode.exportClause
187 var namedImportsOrReExports = (importsDefaultExport ? ["default"] : []).concat((importsSpecificNamedExports
188 ? importClause.namedBindings.elements.map(toExportName)
189 : []), (exportClause !== undefined ? exportClause.elements.map(toExportName) : []));
190 for (var _e = 0, namedImportsOrReExports_1 = namedImportsOrReExports; _e < namedImportsOrReExports_1.length; _e++) {
191 var importName = namedImportsOrReExports_1[_e];
192 if (bansForModule.has(importName)) {
193 ctx.addFailureAtNode(exportClause !== undefined ? exportClause : importClause, Rule.MAKE_NAMED_IMPORT_FAILURE_STRING(importName));
198 // If we're here, the user tried to import/re-export the whole module
199 ctx.addFailure(name.getStart(ctx.sourceFile) + 1, name.end - 1, Rule.IMPLICIT_NAMED_IMPORT_FAILURE_STRING);
202 for (var _f = 0, regexOptions_1 = regexOptions; _f < regexOptions_1.length; _f++) {
203 var regex = regexOptions_1[_f];
204 if (regex.test(name.text)) {
205 ctx.addFailure(name.getStart(ctx.sourceFile) + 1, name.end - 1, Rule.FAILURE_STRING_REGEX + regex.toString());
210 var templateObject_1, templateObject_2;