| /** |
| * @fileoverview Rule that warns when identifier names that are |
| * specified in the configuration are used. |
| * @author Keith Cirkel (http://keithcirkel.co.uk) |
| */ |
| |
| "use strict"; |
| |
| //------------------------------------------------------------------------------ |
| // Helpers |
| //------------------------------------------------------------------------------ |
| |
| /** |
| * Checks whether the given node represents assignment target in a normal assignment or destructuring. |
| * @param {ASTNode} node The node to check. |
| * @returns {boolean} `true` if the node is assignment target. |
| */ |
| function isAssignmentTarget(node) { |
| const parent = node.parent; |
| |
| return ( |
| |
| // normal assignment |
| ( |
| parent.type === "AssignmentExpression" && |
| parent.left === node |
| ) || |
| |
| // destructuring |
| parent.type === "ArrayPattern" || |
| parent.type === "RestElement" || |
| ( |
| parent.type === "Property" && |
| parent.value === node && |
| parent.parent.type === "ObjectPattern" |
| ) || |
| ( |
| parent.type === "AssignmentPattern" && |
| parent.left === node |
| ) |
| ); |
| } |
| |
| /** |
| * Checks whether the given node represents an imported name that is renamed in the same import/export specifier. |
| * |
| * Examples: |
| * import { a as b } from 'mod'; // node `a` is renamed import |
| * export { a as b } from 'mod'; // node `a` is renamed import |
| * @param {ASTNode} node `Identifier` node to check. |
| * @returns {boolean} `true` if the node is a renamed import. |
| */ |
| function isRenamedImport(node) { |
| const parent = node.parent; |
| |
| return ( |
| ( |
| parent.type === "ImportSpecifier" && |
| parent.imported !== parent.local && |
| parent.imported === node |
| ) || |
| ( |
| parent.type === "ExportSpecifier" && |
| parent.parent.source && // re-export |
| parent.local !== parent.exported && |
| parent.local === node |
| ) |
| ); |
| } |
| |
| /** |
| * Checks whether the given node is an ObjectPattern destructuring. |
| * |
| * Examples: |
| * const { a : b } = foo; |
| * @param {ASTNode} node `Identifier` node to check. |
| * @returns {boolean} `true` if the node is in an ObjectPattern destructuring. |
| */ |
| function isPropertyNameInDestructuring(node) { |
| const parent = node.parent; |
| |
| return ( |
| ( |
| !parent.computed && |
| parent.type === "Property" && |
| parent.parent.type === "ObjectPattern" && |
| parent.key === node |
| ) |
| ); |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Rule Definition |
| //------------------------------------------------------------------------------ |
| |
| /** @type {import('../shared/types').Rule} */ |
| module.exports = { |
| meta: { |
| type: "suggestion", |
| |
| docs: { |
| description: "disallow specified identifiers", |
| recommended: false, |
| url: "https://eslint.org/docs/rules/id-denylist" |
| }, |
| |
| schema: { |
| type: "array", |
| items: { |
| type: "string" |
| }, |
| uniqueItems: true |
| }, |
| messages: { |
| restricted: "Identifier '{{name}}' is restricted.", |
| restrictedPrivate: "Identifier '#{{name}}' is restricted." |
| } |
| }, |
| |
| create(context) { |
| |
| const denyList = new Set(context.options); |
| const reportedNodes = new Set(); |
| |
| let globalScope; |
| |
| /** |
| * Checks whether the given name is restricted. |
| * @param {string} name The name to check. |
| * @returns {boolean} `true` if the name is restricted. |
| * @private |
| */ |
| function isRestricted(name) { |
| return denyList.has(name); |
| } |
| |
| /** |
| * Checks whether the given node represents a reference to a global variable that is not declared in the source code. |
| * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables. |
| * @param {ASTNode} node `Identifier` node to check. |
| * @returns {boolean} `true` if the node is a reference to a global variable. |
| */ |
| function isReferenceToGlobalVariable(node) { |
| const variable = globalScope.set.get(node.name); |
| |
| return variable && variable.defs.length === 0 && |
| variable.references.some(ref => ref.identifier === node); |
| } |
| |
| /** |
| * Determines whether the given node should be checked. |
| * @param {ASTNode} node `Identifier` node. |
| * @returns {boolean} `true` if the node should be checked. |
| */ |
| function shouldCheck(node) { |
| const parent = node.parent; |
| |
| /* |
| * Member access has special rules for checking property names. |
| * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over. |
| * Write access isn't allowed, because it potentially creates a new property with a restricted name. |
| */ |
| if ( |
| parent.type === "MemberExpression" && |
| parent.property === node && |
| !parent.computed |
| ) { |
| return isAssignmentTarget(parent); |
| } |
| |
| return ( |
| parent.type !== "CallExpression" && |
| parent.type !== "NewExpression" && |
| !isRenamedImport(node) && |
| !isPropertyNameInDestructuring(node) && |
| !isReferenceToGlobalVariable(node) |
| ); |
| } |
| |
| /** |
| * Reports an AST node as a rule violation. |
| * @param {ASTNode} node The node to report. |
| * @returns {void} |
| * @private |
| */ |
| function report(node) { |
| |
| /* |
| * We used the range instead of the node because it's possible |
| * for the same identifier to be represented by two different |
| * nodes, with the most clear example being shorthand properties: |
| * { foo } |
| * In this case, "foo" is represented by one node for the name |
| * and one for the value. The only way to know they are the same |
| * is to look at the range. |
| */ |
| if (!reportedNodes.has(node.range.toString())) { |
| const isPrivate = node.type === "PrivateIdentifier"; |
| |
| context.report({ |
| node, |
| messageId: isPrivate ? "restrictedPrivate" : "restricted", |
| data: { |
| name: node.name |
| } |
| }); |
| reportedNodes.add(node.range.toString()); |
| } |
| } |
| |
| return { |
| |
| Program() { |
| globalScope = context.getScope(); |
| }, |
| |
| [[ |
| "Identifier", |
| "PrivateIdentifier" |
| ]](node) { |
| if (isRestricted(node.name) && shouldCheck(node)) { |
| report(node); |
| } |
| } |
| }; |
| } |
| }; |