Skip to content

Commit 8ec3a19

Browse files
authored
Merge pull request #86 from nlfurniss/fix-global-jquery
Fix no-global-jquery returning false positive for var assignment #85
2 parents df0b505 + 918f303 commit 8ec3a19

File tree

5 files changed

+428
-47
lines changed

5 files changed

+428
-47
lines changed

lib/rules/no-global-jquery.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@
22
* @fileOverview Disallow the use of global `$`.
33
*/
44
'use strict';
5-
5+
const { get } = require('../utils/get');
66
const { getEmberImportBinding } = require('../utils/imports');
7+
const {
8+
isVariableAssigmentGlobalJquery,
9+
JQUERY_ALIASES
10+
} = require('../utils/jquery');
711
const { collectObjectPatternBindings } = require('../utils/destructed-binding');
812

913
const MESSAGE = 'Do not use global `$` or `jQuery`.';
10-
const ALIASES = ['$', 'jQuery'];
1114

1215
/**
1316
* Determines if this expression matches a global jQuery invocation, either `$` or `jQuery`.
1417
* @param {ASTNode} node The identifier node.
1518
* @returns {Boolean} Returns true if the expression matches, otherwise false.
1619
*/
1720
function isGlobalJquery(node) {
18-
return node.callee && ALIASES.includes(node.callee.name);
21+
return node.callee && JQUERY_ALIASES.includes(node.callee.name);
1922
}
2023

2124
module.exports = {
@@ -30,6 +33,7 @@ module.exports = {
3033
create(context) {
3134
let emberImportBinding;
3235
let destructuredAssignment = null;
36+
let emberJqueryVariables = [];
3337

3438
function isDestructured(node) {
3539
return node && node.callee && node.callee.name === destructuredAssignment;
@@ -48,8 +52,18 @@ module.exports = {
4852
}
4953
},
5054

55+
VariableDeclarator(node) {
56+
if (isVariableAssigmentGlobalJquery(node)) {
57+
context.report(node, MESSAGE);
58+
} else {
59+
const varName = get(node, 'id.name');
60+
emberJqueryVariables.push(varName);
61+
}
62+
},
63+
5164
CallExpression(node) {
52-
if (!isDestructured(node) && isGlobalJquery(node)) {
65+
const calleeName = get(node, 'callee.name');
66+
if (!isDestructured(node) && isGlobalJquery(node) && !emberJqueryVariables.includes(calleeName)) {
5367
context.report(node, MESSAGE);
5468
}
5569
}

lib/rules/no-jquery-methods.js

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,13 @@
33
*/
44
'use strict';
55

6-
const { get } = require('../utils/get');
7-
const JQUERY_ALIASES = ['$', 'jQuery'];
6+
const { get, getParent } = require('../utils/get');
7+
const { isJQueryCallee, isVariableAssigmentJquery } = require('../utils/jquery');
88

99
function getMessage(blackListName) {
1010
return `The use of jQuery's ${blackListName} method has been deprecated.`;
1111
}
1212

13-
function isJQueryCaller(node, name = null) {
14-
const calleePropertyNameisJquery = get(node, 'object.callee.property.name') === '$';
15-
const calleeNameIsJquery = get(node, 'object.callee.name') === '$';
16-
const calleeNameIsLocalJquery = get(node, 'object.callee.name') === name;
17-
const ObjectNameisLocalJquery = get(node, 'object.name') === name;
18-
19-
return calleePropertyNameisJquery
20-
|| calleeNameIsJquery
21-
|| calleeNameIsLocalJquery
22-
|| ObjectNameisLocalJquery;
23-
}
24-
2513
function getLocalImportName(node, sourceName) {
2614
if (get(node, 'source.value').includes(sourceName)) {
2715
return get(node, 'specifiers').map((specifier) => specifier.local.name);
@@ -40,35 +28,35 @@ module.exports = {
4028
},
4129
create(context) {
4230
const BLACKLIST = context.options[0] || [];
43-
let jQueryLocalName = null;
31+
let funcScopeJqueryVariables = [];
32+
let moduleScopeJqueryVariables = [];
4433

4534
return {
4635

4736
ImportDeclaration(node) {
4837
let localName = getLocalImportName(node, 'jquery');
4938
if (localName.length > 0) {
50-
jQueryLocalName = localName[0];
39+
moduleScopeJqueryVariables.push(localName[0]);
5140
}
5241
},
5342

5443
VariableDeclarator(node) {
55-
const varDecIDName = get(node, 'id.name');
56-
// Get the name of what was assigned to the variable.
57-
const varAssignment = get(node, 'init.callee.property.name')
58-
|| get(node, 'init.property.name')
59-
|| get(node, 'init.name')
60-
|| '';
61-
const varAssignmentIsJquery = JQUERY_ALIASES.includes(varAssignment);
44+
if (isVariableAssigmentJquery(node)) {
45+
const varName = get(node, 'id.name');
46+
const isFuncScope = !!getParent(node, (parent) => parent.type === 'FunctionDeclaration'); // Determine if variable is in function scope or global scope.
6247

63-
if (varAssignmentIsJquery) {
64-
jQueryLocalName = varDecIDName;
48+
if (isFuncScope) {
49+
funcScopeJqueryVariables.push(varName);
50+
} else {
51+
moduleScopeJqueryVariables.push(varName);
52+
}
6553
}
6654
},
6755

6856
CallExpression(node) {
6957
const calleeName = get(node, 'callee.property.name');
7058
const parentCalleeName = get(node, 'callee.object.property.name');
71-
if (BLACKLIST.includes(calleeName) && parentCalleeName === '$') {
59+
if (BLACKLIST.includes(calleeName) && (parentCalleeName === '$' || isJQueryCallee(node, moduleScopeJqueryVariables, funcScopeJqueryVariables))) {
7260
context.report({ node: node.callee.property, message: getMessage(`${parentCalleeName}.${calleeName}()`) });
7361
}
7462
},
@@ -82,9 +70,14 @@ module.exports = {
8270
let blackListName = BLACKLIST.includes(propertyName) ? propertyName : false;
8371
let isThisExpression = get(node, 'object.type').includes('ThisExpression');
8472

85-
if (!isThisExpression && blackListName && isJQueryCaller(node, jQueryLocalName)) {
73+
if (!isThisExpression && blackListName && isJQueryCallee(node, moduleScopeJqueryVariables, funcScopeJqueryVariables)) {
8674
context.report({ node: node.property, message: getMessage(blackListName) });
8775
}
76+
},
77+
78+
FunctionDeclaration() {
79+
// Reset the function scope variables for each function declaration.
80+
funcScopeJqueryVariables.length = 0;
8881
}
8982
};
9083
}

lib/utils/jquery.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
const { get } = require('../utils/get');
2+
3+
const JQUERY_ALIASES = ['$', 'jQuery'];
4+
5+
/**
6+
* Determines whether a given node's callee is jQuery.
7+
* @param {ASTNode} node
8+
* @param {...Array} localJqueryVars Array(s) of local variables that have jQuery assigned to them.
9+
* @return {Boolean}
10+
*/
11+
function isJQueryCallee(node, ...localJqueryVars) {
12+
const allJqueryAliases = localJqueryVars.reduce((accumulator, newArray) => {
13+
return accumulator.concat(newArray);
14+
}, JQUERY_ALIASES);
15+
16+
const calleePropertyNameisJquery = allJqueryAliases.includes(get(node, 'object.callee.property.name'));
17+
const calleeNameIsJquery = allJqueryAliases.includes(get(node, 'object.callee.name'));
18+
const objectNameisJquery = allJqueryAliases.includes(get(node, 'object.name'));
19+
20+
return calleePropertyNameisJquery
21+
|| calleeNameIsJquery
22+
|| objectNameisJquery;
23+
}
24+
25+
/**
26+
* Helper function that returns the name of what was assigned to the variable.
27+
* @param {ASTNode} node
28+
* @return {String}
29+
*/
30+
function getVarAssignmentName(node) {
31+
// Get the name of what was assigned to the variable.
32+
return get(node, 'init.callee.property.name') || get(node, 'init.name') || get(node, 'init.property.name');
33+
}
34+
35+
/**
36+
* Determines whether a variable assignment's callee is Ember/this.
37+
* @param {ASTNode} node
38+
* @return {Boolean}
39+
*/
40+
function isVarAssignmentCalleeEmber(node) {
41+
// A variable's callee object's name tells us whether this is a usage of Ember.$ or global $.
42+
const isVarCalleeObjNameEmber = get(node, 'init.callee.object.name') === 'Ember' || get(node, 'init.object.name') === 'Ember';
43+
// Whether or not the callee oject is `this.$`.
44+
const isVarCalleeObjThis = get(node, 'init.callee.object.type') === 'ThisExpression';
45+
46+
return isVarCalleeObjNameEmber || isVarCalleeObjThis;
47+
}
48+
49+
/**
50+
* Determines whether a variable's assignment is global jQuery.
51+
* @param {ASTNode} node
52+
* @return {Boolean}
53+
*/
54+
function isVariableAssigmentGlobalJquery(node) {
55+
return isVariableAssigmentJquery(node) && !isVarAssignmentCalleeEmber(node);
56+
}
57+
58+
/**
59+
* Determines whether a variable's assignment is Ember jQuery.
60+
* @param {ASTNode} node
61+
* @return {Boolean}
62+
*/
63+
function isVariableAssigmentEmberJquery(node) {
64+
return isVariableAssigmentJquery(node) && isVarAssignmentCalleeEmber(node);
65+
}
66+
67+
/**
68+
* Determines whether a variable's assignment is Ember jQuery.
69+
* @param {ASTNode} node
70+
* @return {Boolean}
71+
*/
72+
function isVariableAssigmentJquery(node) {
73+
return JQUERY_ALIASES.includes(getVarAssignmentName(node));
74+
}
75+
76+
module.exports = {
77+
isJQueryCallee,
78+
isVariableAssigmentGlobalJquery,
79+
isVariableAssigmentEmberJquery,
80+
isVariableAssigmentJquery,
81+
JQUERY_ALIASES
82+
};

0 commit comments

Comments
 (0)