You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
12 KiB
309 lines
12 KiB
/** |
|
* @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/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 |
|
} |
|
}, |
|
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; |
|
|
|
//------------------------------------------------------------------------- |
|
// 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) { |
|
const identifier = node.id.name; |
|
|
|
if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && |
|
!isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) { |
|
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 |
|
}; |
|
|
|
} |
|
};
|
|
|