Skip to content

Commit 6dd3cbc

Browse files
nzakasmdjermanovic
andauthored
feat: Option to track JSX components as references (#646)
* feat: Option to track JSX components as references fixes #645 * Ensure backwards compatible behavior * Fix JSXMemberExpression references and add more tests * Ignore JSXNamespacedNames * Fix tests * Update packages/eslint-scope/tests/jsx.js Co-authored-by: Milos Djermanovic <[email protected]> * Update packages/eslint-scope/tests/jsx.js Co-authored-by: Milos Djermanovic <[email protected]> * Update packages/eslint-scope/tests/jsx.js Co-authored-by: Milos Djermanovic <[email protected]> * Fix 'this' references --------- Co-authored-by: Milos Djermanovic <[email protected]>
1 parent 925fcb9 commit 6dd3cbc

File tree

7 files changed

+528
-3
lines changed

7 files changed

+528
-3
lines changed

packages/eslint-scope/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ In order to analyze scope, you'll need to have an [ESTree](https://github.com/es
3737
* `sourceType` (default: `"script"`) - The type of JavaScript file to evaluate. Change to `"module"` for ECMAScript module code.
3838
* `childVisitorKeys` (default: `null`) - An object with visitor key information (like [`eslint-visitor-keys`](https://github.com/eslint/js/tree/main/packages/eslint-visitor-keys)). Without this, `eslint-scope` finds child nodes to visit algorithmically. Providing this option is a performance enhancement.
3939
* `fallback` (default: `"iteration"`) - The strategy to use when `childVisitorKeys` is not specified. May be a function.
40+
* `jsx` (default: `false`) - Enables the tracking of JSX components as variable references.
4041

4142
Example:
4243

packages/eslint-scope/lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ function updateDeeply(target, override) {
121121
* (if ecmaVersion >= 5).
122122
* @param {string} [providedOptions.sourceType='script'] the source type of the script. one of 'script', 'module', and 'commonjs'
123123
* @param {number} [providedOptions.ecmaVersion=5] which ECMAScript version is considered
124+
* @param {boolean} [providedOptions.jsx=false] support JSX references
124125
* @param {Object} [providedOptions.childVisitorKeys=null] Additional known visitor keys. See [esrecurse](https://github.com/estools/esrecurse)'s the `childVisitorKeys` option.
125126
* @param {string} [providedOptions.fallback='iteration'] A kind of the fallback in order to encounter with unknown node. See [esrecurse](https://github.com/estools/esrecurse)'s the `fallback` option.
126127
* @returns {ScopeManager} ScopeManager

packages/eslint-scope/lib/referencer.js

+52
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,58 @@ class Referencer extends esrecurse.Visitor {
649649

650650
// do nothing.
651651
}
652+
653+
JSXIdentifier(node) {
654+
655+
// Special case: "this" should not count as a reference
656+
if (this.scopeManager.__isJSXEnabled() && node.name !== "this") {
657+
this.currentScope().__referencing(node);
658+
}
659+
}
660+
661+
JSXMemberExpression(node) {
662+
this.visit(node.object);
663+
}
664+
665+
JSXElement(node) {
666+
if (this.scopeManager.__isJSXEnabled()) {
667+
this.visit(node.openingElement);
668+
node.children.forEach(this.visit, this);
669+
} else {
670+
this.visitChildren(node);
671+
}
672+
}
673+
674+
JSXOpeningElement(node) {
675+
if (this.scopeManager.__isJSXEnabled()) {
676+
677+
const nameNode = node.name;
678+
const isComponentName = nameNode.type === "JSXIdentifier" && nameNode.name[0].toUpperCase() === nameNode.name[0];
679+
const isComponent = isComponentName || nameNode.type === "JSXMemberExpression";
680+
681+
// we only want to visit JSXIdentifier nodes if they are capitalized
682+
if (isComponent) {
683+
this.visit(nameNode);
684+
}
685+
}
686+
687+
node.attributes.forEach(this.visit, this);
688+
}
689+
690+
JSXAttribute(node) {
691+
if (node.value) {
692+
this.visit(node.value);
693+
}
694+
}
695+
696+
JSXExpressionContainer(node) {
697+
this.visit(node.expression);
698+
}
699+
700+
JSXNamespacedName(node) {
701+
this.visit(node.namespace);
702+
this.visit(node.name);
703+
}
652704
}
653705

654706
export default Referencer;

packages/eslint-scope/lib/scope-manager.js

+4
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ class ScopeManager {
5959
return this.__options.ignoreEval;
6060
}
6161

62+
__isJSXEnabled() {
63+
return this.__options.jsx === true;
64+
}
65+
6266
isGlobalReturn() {
6367
return this.__options.nodejsScope || this.__options.sourceType === "commonjs";
6468
}

packages/eslint-scope/lib/scope.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ class Scope {
421421
__referencing(node, assign, writeExpr, maybeImplicitGlobal, partial, init) {
422422

423423
// because Array element may be null
424-
if (!node || node.type !== Syntax.Identifier) {
424+
if (!node || (node.type !== Syntax.Identifier && node.type !== "JSXIdentifier")) {
425425
return;
426426
}
427427

0 commit comments

Comments
 (0)