Nicolò Ribaudo b4c42601d1
Fix linting in ESLint packages (#10712)
* Lint eslint/*

* Run "make fix-js"

* Fix remaining problems

* Remove linting from subpackages

* Remove husky

* Add back eslint dep
2019-11-14 22:05:26 +01:00

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);
}
},
};
},
};