Skip to content
Permalink
9bfb9ba527
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
github-actions[bot] Update checked-in dependencies
Latest commit 40a500c Jul 13, 2023 History
0 contributors

Users who have contributed to this file

335 lines (300 sloc) 12.8 KB
/**
* @fileoverview Rule to flag dangling underscores in variable declarations.
* @author Matt DuVall <http://www.mattduvall.com>
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "Disallow dangling underscores in identifiers",
recommended: false,
url: "https://eslint.org/docs/latest/rules/no-underscore-dangle"
},
schema: [
{
type: "object",
properties: {
allow: {
type: "array",
items: {
type: "string"
}
},
allowAfterThis: {
type: "boolean",
default: false
},
allowAfterSuper: {
type: "boolean",
default: false
},
allowAfterThisConstructor: {
type: "boolean",
default: false
},
enforceInMethodNames: {
type: "boolean",
default: false
},
allowFunctionParams: {
type: "boolean",
default: true
},
enforceInClassFields: {
type: "boolean",
default: false
},
allowInArrayDestructuring: {
type: "boolean",
default: true
},
allowInObjectDestructuring: {
type: "boolean",
default: true
}
},
additionalProperties: false
}
],
messages: {
unexpectedUnderscore: "Unexpected dangling '_' in '{{identifier}}'."
}
},
create(context) {
const options = context.options[0] || {};
const ALLOWED_VARIABLES = options.allow ? options.allow : [];
const allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false;
const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false;
const allowAfterThisConstructor = typeof options.allowAfterThisConstructor !== "undefined" ? options.allowAfterThisConstructor : false;
const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
const enforceInClassFields = typeof options.enforceInClassFields !== "undefined" ? options.enforceInClassFields : false;
const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true;
const allowInArrayDestructuring = typeof options.allowInArrayDestructuring !== "undefined" ? options.allowInArrayDestructuring : true;
const allowInObjectDestructuring = typeof options.allowInObjectDestructuring !== "undefined" ? options.allowInObjectDestructuring : true;
const sourceCode = context.sourceCode;
//-------------------------------------------------------------------------
// Helpers
//-------------------------------------------------------------------------
/**
* Check if identifier is present inside the allowed option
* @param {string} identifier name of the node
* @returns {boolean} true if its is present
* @private
*/
function isAllowed(identifier) {
return ALLOWED_VARIABLES.includes(identifier);
}
/**
* Check if identifier has a dangling underscore
* @param {string} identifier name of the node
* @returns {boolean} true if its is present
* @private
*/
function hasDanglingUnderscore(identifier) {
const len = identifier.length;
return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_");
}
/**
* Check if identifier is a special case member expression
* @param {string} identifier name of the node
* @returns {boolean} true if its is a special case
* @private
*/
function isSpecialCaseIdentifierForMemberExpression(identifier) {
return identifier === "__proto__";
}
/**
* Check if identifier is a special case variable expression
* @param {string} identifier name of the node
* @returns {boolean} true if its is a special case
* @private
*/
function isSpecialCaseIdentifierInVariableExpression(identifier) {
// Checks for the underscore library usage here
return identifier === "_";
}
/**
* Check if a node is a member reference of this.constructor
* @param {ASTNode} node node to evaluate
* @returns {boolean} true if it is a reference on this.constructor
* @private
*/
function isThisConstructorReference(node) {
return node.object.type === "MemberExpression" &&
node.object.property.name === "constructor" &&
node.object.object.type === "ThisExpression";
}
/**
* Check if function parameter has a dangling underscore.
* @param {ASTNode} node function node to evaluate
* @returns {void}
* @private
*/
function checkForDanglingUnderscoreInFunctionParameters(node) {
if (!allowFunctionParams) {
node.params.forEach(param => {
const { type } = param;
let nodeToCheck;
if (type === "RestElement") {
nodeToCheck = param.argument;
} else if (type === "AssignmentPattern") {
nodeToCheck = param.left;
} else {
nodeToCheck = param;
}
if (nodeToCheck.type === "Identifier") {
const identifier = nodeToCheck.name;
if (hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
context.report({
node: param,
messageId: "unexpectedUnderscore",
data: {
identifier
}
});
}
}
});
}
}
/**
* Check if function has a dangling underscore
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function checkForDanglingUnderscoreInFunction(node) {
if (node.type === "FunctionDeclaration" && node.id) {
const identifier = node.id.name;
if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
context.report({
node,
messageId: "unexpectedUnderscore",
data: {
identifier
}
});
}
}
checkForDanglingUnderscoreInFunctionParameters(node);
}
/**
* Check if variable expression has a dangling underscore
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function checkForDanglingUnderscoreInVariableExpression(node) {
sourceCode.getDeclaredVariables(node).forEach(variable => {
const definition = variable.defs.find(def => def.node === node);
const identifierNode = definition.name;
const identifier = identifierNode.name;
let parent = identifierNode.parent;
while (!["VariableDeclarator", "ArrayPattern", "ObjectPattern"].includes(parent.type)) {
parent = parent.parent;
}
if (
hasDanglingUnderscore(identifier) &&
!isSpecialCaseIdentifierInVariableExpression(identifier) &&
!isAllowed(identifier) &&
!(allowInArrayDestructuring && parent.type === "ArrayPattern") &&
!(allowInObjectDestructuring && parent.type === "ObjectPattern")
) {
context.report({
node,
messageId: "unexpectedUnderscore",
data: {
identifier
}
});
}
});
}
/**
* Check if member expression has a dangling underscore
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function checkForDanglingUnderscoreInMemberExpression(node) {
const identifier = node.property.name,
isMemberOfThis = node.object.type === "ThisExpression",
isMemberOfSuper = node.object.type === "Super",
isMemberOfThisConstructor = isThisConstructorReference(node);
if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
!(isMemberOfThis && allowAfterThis) &&
!(isMemberOfSuper && allowAfterSuper) &&
!(isMemberOfThisConstructor && allowAfterThisConstructor) &&
!isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) {
context.report({
node,
messageId: "unexpectedUnderscore",
data: {
identifier
}
});
}
}
/**
* Check if method declaration or method property has a dangling underscore
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function checkForDanglingUnderscoreInMethod(node) {
const identifier = node.key.name;
const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method;
if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
context.report({
node,
messageId: "unexpectedUnderscore",
data: {
identifier: node.key.type === "PrivateIdentifier"
? `#${identifier}`
: identifier
}
});
}
}
/**
* Check if a class field has a dangling underscore
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function checkForDanglingUnderscoreInClassField(node) {
const identifier = node.key.name;
if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
enforceInClassFields &&
!isAllowed(identifier)) {
context.report({
node,
messageId: "unexpectedUnderscore",
data: {
identifier: node.key.type === "PrivateIdentifier"
? `#${identifier}`
: identifier
}
});
}
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
FunctionDeclaration: checkForDanglingUnderscoreInFunction,
VariableDeclarator: checkForDanglingUnderscoreInVariableExpression,
MemberExpression: checkForDanglingUnderscoreInMemberExpression,
MethodDefinition: checkForDanglingUnderscoreInMethod,
PropertyDefinition: checkForDanglingUnderscoreInClassField,
Property: checkForDanglingUnderscoreInMethod,
FunctionExpression: checkForDanglingUnderscoreInFunction,
ArrowFunctionExpression: checkForDanglingUnderscoreInFunction
};
}
};