Skip to content
This repository was archived by the owner on Aug 4, 2020. It is now read-only.

Commit 135622a

Browse files
villesauexistentialism
authored andcommitted
Camelcase - support for optional chaining (#163)
1 parent 585b8ec commit 135622a

File tree

4 files changed

+767
-1
lines changed

4 files changed

+767
-1
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ original ones as well!).
2929
{
3030
"rules": {
3131
"babel/new-cap": 1,
32+
"babel/camelcase": 1,
3233
"babel/no-invalid-this": 1,
3334
"babel/object-curly-spacing": 1,
3435
"babel/quotes": 1,
@@ -45,6 +46,7 @@ Each rule corresponds to a core `eslint` rule, and has the same options.
4546
🛠: means it's autofixable with `--fix`.
4647

4748
- `babel/new-cap`: Ignores capitalized decorators (`@Decorator`)
49+
- `babel/camelcase: doesn't complain about optional chaining (`var foo = bar?.a_b;`)
4850
- `babel/no-invalid-this`: doesn't fail when inside class properties (`class A { a = this.b; }`)
4951
- `babel/object-curly-spacing`: doesn't complain about `export x from "mod";` or `export * as x from "mod";` (🛠)
5052
- `babel/quotes`: doesn't complain about JSX fragment shorthand syntax (`<>foo</>;`)

index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module.exports = {
88
'func-params-comma-dangle': require('./rules/func-params-comma-dangle'),
99
'generator-star-spacing': require('./rules/generator-star-spacing'),
1010
'new-cap': require('./rules/new-cap'),
11+
'camelcase': require('./rules/camelcase'),
1112
'no-await-in-loop': require('./rules/no-await-in-loop'),
1213
'no-invalid-this': require('./rules/no-invalid-this'),
1314
'no-unused-expressions': require('./rules/no-unused-expressions'),
@@ -20,6 +21,7 @@ module.exports = {
2021
rulesConfig: {
2122
'array-bracket-spacing': 0,
2223
'arrow-parens': 0,
24+
'camelcase': 0,
2325
'flow-object-type': 0,
2426
'func-params-comma-dangle': 0,
2527
'generator-star-spacing': 0,
@@ -28,7 +30,7 @@ module.exports = {
2830
'no-invalid-this': 0,
2931
'no-unused-expressions': 0,
3032
'object-curly-spacing': 0,
31-
'object-shorthand': 0,
33+
'object-shorthand': 0,
3234
'quotes': 0,
3335
'semi': 0,
3436
'valid-typeof': 0,

rules/camelcase.js

+194
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* @fileoverview Rule to flag non-camelcased identifiers
3+
* @author Nicholas C. Zakas
4+
*/
5+
6+
"use strict";
7+
8+
//------------------------------------------------------------------------------
9+
// Rule Definition
10+
//------------------------------------------------------------------------------
11+
12+
module.exports = {
13+
meta: {
14+
docs: {
15+
description: "enforce camelcase naming convention",
16+
category: "Stylistic Issues",
17+
recommended: false,
18+
url: "https://eslint.org/docs/rules/camelcase"
19+
},
20+
21+
schema: [
22+
{
23+
type: "object",
24+
properties: {
25+
ignoreDestructuring: {
26+
type: "boolean"
27+
},
28+
properties: {
29+
enum: ["always", "never"]
30+
}
31+
},
32+
additionalProperties: false
33+
}
34+
],
35+
36+
messages: {
37+
notCamelCase: "Identifier '{{name}}' is not in camel case."
38+
}
39+
},
40+
41+
create(context) {
42+
43+
//--------------------------------------------------------------------------
44+
// Helpers
45+
//--------------------------------------------------------------------------
46+
47+
// contains reported nodes to avoid reporting twice on destructuring with shorthand notation
48+
const reported = [];
49+
const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
50+
const MEMBER_EXPRESSIONS = ["MemberExpression", "OptionalMemberExpression"];
51+
52+
/**
53+
* Checks if expression is supported member expression.
54+
*
55+
* @param {string} expression - An expression to check.
56+
* @returns {boolean} `true` if the expression type is supported
57+
*/
58+
function isMemberExpression(expression) {
59+
return MEMBER_EXPRESSIONS.indexOf(expression) >= 0;
60+
}
61+
62+
/**
63+
* Checks if a string contains an underscore and isn't all upper-case
64+
* @param {string} name The string to check.
65+
* @returns {boolean} if the string is underscored
66+
* @private
67+
*/
68+
function isUnderscored(name) {
69+
70+
// if there's an underscore, it might be A_CONSTANT, which is okay
71+
return name.indexOf("_") > -1 && name !== name.toUpperCase();
72+
}
73+
74+
/**
75+
* Checks if a parent of a node is an ObjectPattern.
76+
* @param {ASTNode} node The node to check.
77+
* @returns {boolean} if the node is inside an ObjectPattern
78+
* @private
79+
*/
80+
function isInsideObjectPattern(node) {
81+
let { parent } = node;
82+
83+
while (parent) {
84+
if (parent.type === "ObjectPattern") {
85+
return true;
86+
}
87+
88+
parent = parent.parent;
89+
}
90+
91+
return false;
92+
}
93+
94+
/**
95+
* Reports an AST node as a rule violation.
96+
* @param {ASTNode} node The node to report.
97+
* @returns {void}
98+
* @private
99+
*/
100+
function report(node) {
101+
if (reported.indexOf(node.parent) < 0) {
102+
reported.push(node.parent);
103+
context.report({ node, messageId: "notCamelCase", data: { name: node.name } });
104+
}
105+
}
106+
107+
const options = context.options[0] || {};
108+
let properties = options.properties || "";
109+
const ignoreDestructuring = options.ignoreDestructuring || false;
110+
111+
if (properties !== "always" && properties !== "never") {
112+
properties = "always";
113+
}
114+
115+
return {
116+
117+
Identifier(node) {
118+
119+
/*
120+
* Leading and trailing underscores are commonly used to flag
121+
* private/protected identifiers, strip them
122+
*/
123+
const name = node.name.replace(/^_+|_+$/g, ""),
124+
effectiveParent = isMemberExpression(node.parent.type) ? node.parent.parent : node.parent;
125+
126+
// MemberExpressions get special rules
127+
if (isMemberExpression(node.parent.type)) {
128+
129+
// "never" check properties
130+
if (properties === "never") {
131+
return;
132+
}
133+
134+
// Always report underscored object names
135+
if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && isUnderscored(name)) {
136+
report(node);
137+
138+
// Report AssignmentExpressions only if they are the left side of the assignment
139+
} else if (effectiveParent.type === "AssignmentExpression" && isUnderscored(name) && (!isMemberExpression(effectiveParent.right.type) || isMemberExpression(effectiveParent.left.type) && effectiveParent.left.property.name === node.name)) {
140+
report(node);
141+
}
142+
143+
/*
144+
* Properties have their own rules, and
145+
* AssignmentPattern nodes can be treated like Properties:
146+
* e.g.: const { no_camelcased = false } = bar;
147+
*/
148+
} else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") {
149+
150+
if (node.parent.parent && node.parent.parent.type === "ObjectPattern") {
151+
152+
const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name;
153+
154+
// prevent checking righthand side of destructured object
155+
if (node.parent.key === node && node.parent.value !== node) {
156+
return;
157+
}
158+
159+
const valueIsUnderscored = node.parent.value.name && isUnderscored(name);
160+
161+
// ignore destructuring if the option is set, unless a new identifier is created
162+
if (valueIsUnderscored && !(assignmentKeyEqualsValue && ignoreDestructuring)) {
163+
report(node);
164+
}
165+
}
166+
167+
// "never" check properties or always ignore destructuring
168+
if (properties === "never" || (ignoreDestructuring && isInsideObjectPattern(node))) {
169+
return;
170+
}
171+
172+
// don't check right hand side of AssignmentExpression to prevent duplicate warnings
173+
if (isUnderscored(name) && !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && !(node.parent.right === node)) {
174+
report(node);
175+
}
176+
177+
// Check if it's an import specifier
178+
} else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].indexOf(node.parent.type) >= 0) {
179+
180+
// Report only if the local imported identifier is underscored
181+
if (node.parent.local && node.parent.local.name === node.name && isUnderscored(name)) {
182+
report(node);
183+
}
184+
185+
// Report anything that is underscored that isn't a CallExpression
186+
} else if (isUnderscored(name) && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {
187+
report(node);
188+
}
189+
}
190+
191+
};
192+
193+
}
194+
};

0 commit comments

Comments
 (0)