* Lint eslint/* * Run "make fix-js" * Fix remaining problems * Remove linting from subpackages * Remove husky * Add back eslint dep
232 lines
6.7 KiB
JavaScript
232 lines
6.7 KiB
JavaScript
/**
|
|
* @fileoverview Rule to flag non-camelcased identifiers
|
|
* @author Nicholas C. Zakas
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Rule Definition
|
|
//------------------------------------------------------------------------------
|
|
|
|
module.exports = {
|
|
meta: {
|
|
docs: {
|
|
description: "enforce camelcase naming convention",
|
|
category: "Stylistic Issues",
|
|
recommended: false,
|
|
url: "https://eslint.org/docs/rules/camelcase",
|
|
},
|
|
|
|
schema: [
|
|
{
|
|
type: "object",
|
|
properties: {
|
|
ignoreDestructuring: {
|
|
type: "boolean",
|
|
},
|
|
properties: {
|
|
enum: ["always", "never"],
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
|
|
messages: {
|
|
notCamelCase: "Identifier '{{name}}' is not in camel case.",
|
|
},
|
|
},
|
|
|
|
create(context) {
|
|
//--------------------------------------------------------------------------
|
|
// Helpers
|
|
//--------------------------------------------------------------------------
|
|
|
|
// contains reported nodes to avoid reporting twice on destructuring with shorthand notation
|
|
const reported = [];
|
|
const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
|
|
const MEMBER_EXPRESSIONS = ["MemberExpression", "OptionalMemberExpression"];
|
|
|
|
/**
|
|
* Checks if expression is supported member expression.
|
|
*
|
|
* @param {string} expression - An expression to check.
|
|
* @returns {boolean} `true` if the expression type is supported
|
|
*/
|
|
function isMemberExpression(expression) {
|
|
return MEMBER_EXPRESSIONS.indexOf(expression) >= 0;
|
|
}
|
|
|
|
/**
|
|
* Checks if a string contains an underscore and isn't all upper-case
|
|
* @param {string} name The string to check.
|
|
* @returns {boolean} if the string is underscored
|
|
* @private
|
|
*/
|
|
function isUnderscored(name) {
|
|
// if there's an underscore, it might be A_CONSTANT, which is okay
|
|
return name.indexOf("_") > -1 && name !== name.toUpperCase();
|
|
}
|
|
|
|
/**
|
|
* Checks if a parent of a node is an ObjectPattern.
|
|
* @param {ASTNode} node The node to check.
|
|
* @returns {boolean} if the node is inside an ObjectPattern
|
|
* @private
|
|
*/
|
|
function isInsideObjectPattern(node) {
|
|
let { parent } = node;
|
|
|
|
while (parent) {
|
|
if (parent.type === "ObjectPattern") {
|
|
return true;
|
|
}
|
|
|
|
parent = parent.parent;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Reports an AST node as a rule violation.
|
|
* @param {ASTNode} node The node to report.
|
|
* @returns {void}
|
|
* @private
|
|
*/
|
|
function report(node) {
|
|
if (reported.indexOf(node.parent) < 0) {
|
|
reported.push(node.parent);
|
|
context.report({
|
|
node,
|
|
messageId: "notCamelCase",
|
|
data: { name: node.name },
|
|
});
|
|
}
|
|
}
|
|
|
|
const options = context.options[0] || {};
|
|
let properties = options.properties || "";
|
|
const ignoreDestructuring = options.ignoreDestructuring || false;
|
|
|
|
if (properties !== "always" && properties !== "never") {
|
|
properties = "always";
|
|
}
|
|
|
|
return {
|
|
Identifier(node) {
|
|
/*
|
|
* Leading and trailing underscores are commonly used to flag
|
|
* private/protected identifiers, strip them
|
|
*/
|
|
const name = node.name.replace(/^_+|_+$/g, ""),
|
|
effectiveParent = isMemberExpression(node.parent.type)
|
|
? node.parent.parent
|
|
: node.parent;
|
|
|
|
// MemberExpressions get special rules
|
|
if (isMemberExpression(node.parent.type)) {
|
|
// "never" check properties
|
|
if (properties === "never") {
|
|
return;
|
|
}
|
|
|
|
// Always report underscored object names
|
|
if (
|
|
node.parent.object.type === "Identifier" &&
|
|
node.parent.object.name === node.name &&
|
|
isUnderscored(name)
|
|
) {
|
|
report(node);
|
|
|
|
// Report AssignmentExpressions only if they are the left side of the assignment
|
|
} else if (
|
|
effectiveParent.type === "AssignmentExpression" &&
|
|
isUnderscored(name) &&
|
|
(!isMemberExpression(effectiveParent.right.type) ||
|
|
(isMemberExpression(effectiveParent.left.type) &&
|
|
effectiveParent.left.property.name === node.name))
|
|
) {
|
|
report(node);
|
|
}
|
|
|
|
/*
|
|
* Properties have their own rules, and
|
|
* AssignmentPattern nodes can be treated like Properties:
|
|
* e.g.: const { no_camelcased = false } = bar;
|
|
*/
|
|
} else if (
|
|
node.parent.type === "Property" ||
|
|
node.parent.type === "AssignmentPattern"
|
|
) {
|
|
if (
|
|
node.parent.parent &&
|
|
node.parent.parent.type === "ObjectPattern"
|
|
) {
|
|
const assignmentKeyEqualsValue =
|
|
node.parent.key.name === node.parent.value.name;
|
|
|
|
// prevent checking righthand side of destructured object
|
|
if (node.parent.key === node && node.parent.value !== node) {
|
|
return;
|
|
}
|
|
|
|
const valueIsUnderscored =
|
|
node.parent.value.name && isUnderscored(name);
|
|
|
|
// ignore destructuring if the option is set, unless a new identifier is created
|
|
if (
|
|
valueIsUnderscored &&
|
|
!(assignmentKeyEqualsValue && ignoreDestructuring)
|
|
) {
|
|
report(node);
|
|
}
|
|
}
|
|
|
|
// "never" check properties or always ignore destructuring
|
|
if (
|
|
properties === "never" ||
|
|
(ignoreDestructuring && isInsideObjectPattern(node))
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// don't check right hand side of AssignmentExpression to prevent duplicate warnings
|
|
if (
|
|
isUnderscored(name) &&
|
|
!ALLOWED_PARENT_TYPES.has(effectiveParent.type) &&
|
|
!(node.parent.right === node)
|
|
) {
|
|
report(node);
|
|
}
|
|
|
|
// Check if it's an import specifier
|
|
} else if (
|
|
[
|
|
"ImportSpecifier",
|
|
"ImportNamespaceSpecifier",
|
|
"ImportDefaultSpecifier",
|
|
].indexOf(node.parent.type) >= 0
|
|
) {
|
|
// Report only if the local imported identifier is underscored
|
|
if (
|
|
node.parent.local &&
|
|
node.parent.local.name === node.name &&
|
|
isUnderscored(name)
|
|
) {
|
|
report(node);
|
|
}
|
|
|
|
// Report anything that is underscored that isn't a CallExpression
|
|
} else if (
|
|
isUnderscored(name) &&
|
|
!ALLOWED_PARENT_TYPES.has(effectiveParent.type)
|
|
) {
|
|
report(node);
|
|
}
|
|
},
|
|
};
|
|
},
|
|
};
|