Skip to content

Commit

Permalink
ESLint Plugin: Fix no-unused-vars-before-return JSX identifier refere…
Browse files Browse the repository at this point in the history
…nce (#21358)

* ESLint Plugin: Fix no-unused-vars-before-return JSX identifier reference

* Block Library: Social Link: Remove fixed ESLint disabling
  • Loading branch information
aduth authored Apr 17, 2020
1 parent bed93c8 commit c14ae55
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 18 deletions.
5 changes: 0 additions & 5 deletions packages/block-library/src/social-link/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ const SocialLinkEdit = ( { attributes, setAttributes, isSelected } ) => {
'wp-social-link__is-incomplete': ! url,
} );

// Disable reason: The rule is currently not considering use as JSX tagName.
//
// See: https://github.com/WordPress/gutenberg/issues/16418

// eslint-disable-next-line @wordpress/no-unused-vars-before-return
const IconComponent = getIconBySite( service );
const socialLinkName = getNameBySite( service );

Expand Down
4 changes: 4 additions & 0 deletions packages/eslint-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Master

### Bug Fixes

- The `@wordpress/no-unused-vars-before-return` rule will now correctly identify valid usage of a variable as a JSX identifier.

## 5.0.1 (2020-04-15)

### Bug Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import rule from '../no-unused-vars-before-return';
const ruleTester = new RuleTester( {
parserOptions: {
ecmaVersion: 6,
ecmaFeatures: {
jsx: true,
},
},
} );

Expand Down Expand Up @@ -50,6 +53,13 @@ function example() {
}`,
options: [ { excludePattern: '^do' } ],
},
{
code: `
function MyComponent() {
const Foo = getSomeComponent();
return <Foo />;
}`,
},
],
invalid: [
{
Expand Down
81 changes: 68 additions & 13 deletions packages/eslint-plugin/rules/no-unused-vars-before-return.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,33 @@
module.exports = {
/** @typedef {import('eslint').Scope.Scope} ESLintScope */
/** @typedef {import('eslint').Rule.RuleContext} ESLintRuleContext */
/** @typedef {import('estree').Node} ESTreeNode */

/**
* Mapping of function scope objects to a set of identified JSX identifiers
* within that scope.
*
* @type {WeakMap<ESLintScope,Set<ESTreeNode>>}
*/
const FUNCTION_SCOPE_JSX_IDENTIFIERS = new WeakMap();

/**
* Returns the closest function scope for the current ESLint context object, or
* undefined if it cannot be determined.
*
* @param {ESLintRuleContext} context ESLint context object.
*
* @return {ESLintScope|undefined} Function scope, if known.
*/
function getClosestFunctionScope( context ) {
let functionScope = context.getScope();
while ( functionScope.type !== 'function' && functionScope.upper ) {
functionScope = functionScope.upper;
}

return functionScope;
}

module.exports = /** @type {import('eslint').Rule} */ ( {
meta: {
type: 'problem',
schema: [
Expand All @@ -13,6 +42,9 @@ module.exports = {
},
],
},
/**
* @param {ESLintRuleContext} context Rule context.
*/
create( context ) {
const options = context.options[ 0 ] || {};
const { excludePattern } = options;
Expand All @@ -36,15 +68,27 @@ module.exports = {
}

return {
ReturnStatement( node ) {
let functionScope = context.getScope();
while (
functionScope.type !== 'function' &&
functionScope.upper
) {
functionScope = functionScope.upper;
JSXIdentifier( node ) {
// Currently, a scope's variable references does not include JSX
// identifiers. Account for this by visiting JSX identifiers
// first, and tracking them in a map per function scope, which
// is later merged with the known variable references.
const functionScope = getClosestFunctionScope( context );
if ( ! functionScope ) {
return;
}

if ( ! FUNCTION_SCOPE_JSX_IDENTIFIERS.has( functionScope ) ) {
FUNCTION_SCOPE_JSX_IDENTIFIERS.set(
functionScope,
new Set()
);
}

FUNCTION_SCOPE_JSX_IDENTIFIERS.get( functionScope ).add( node );
},
'ReturnStatement:exit'( node ) {
const functionScope = getClosestFunctionScope( context );
if ( ! functionScope ) {
return;
}
Expand Down Expand Up @@ -79,11 +123,22 @@ module.exports = {

// The first entry in `references` is the declaration
// itself, which can be ignored.
const isUsedBeforeReturn = variable.references
const identifiers = variable.references
.slice( 1 )
.some( ( reference ) => {
return reference.identifier.end < node.end;
} );
.map( ( reference ) => reference.identifier );

// Merge with any JSX identifiers in scope, if any.
if ( FUNCTION_SCOPE_JSX_IDENTIFIERS.has( functionScope ) ) {
const jsxIdentifiers = FUNCTION_SCOPE_JSX_IDENTIFIERS.get(
functionScope
);

identifiers.push( ...jsxIdentifiers );
}

const isUsedBeforeReturn = identifiers.some(
( identifier ) => identifier.end < node.end
);

if ( isUsedBeforeReturn ) {
continue;
Expand All @@ -98,4 +153,4 @@ module.exports = {
},
};
},
};
} );

0 comments on commit c14ae55

Please sign in to comment.