From fddb73653250daa5452263e586cb536673e68a48 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Wed, 12 Dec 2018 17:09:24 -0500 Subject: [PATCH] eslint-plugin: Add rule `no-unused-vars-before-return` --- packages/eslint-plugin/configs/custom.js | 4 ++ packages/eslint-plugin/index.js | 1 + packages/eslint-plugin/rules/index.js | 1 + .../rules/no-unused-vars-before-return.js | 55 +++++++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 packages/eslint-plugin/rules/index.js create mode 100644 packages/eslint-plugin/rules/no-unused-vars-before-return.js diff --git a/packages/eslint-plugin/configs/custom.js b/packages/eslint-plugin/configs/custom.js index 0318c85c324c9..5691c3c5d0c2b 100644 --- a/packages/eslint-plugin/configs/custom.js +++ b/packages/eslint-plugin/configs/custom.js @@ -1,5 +1,9 @@ module.exports = { + plugins: [ + '@wordpress', + ], rules: { + '@wordpress/no-unused-vars-before-return': 'error', 'no-restricted-syntax': [ 'error', { diff --git a/packages/eslint-plugin/index.js b/packages/eslint-plugin/index.js index 0933aba1cc826..fcba80b48203a 100644 --- a/packages/eslint-plugin/index.js +++ b/packages/eslint-plugin/index.js @@ -1,3 +1,4 @@ module.exports = { configs: require( './configs' ), + rules: require( './rules' ), }; diff --git a/packages/eslint-plugin/rules/index.js b/packages/eslint-plugin/rules/index.js new file mode 100644 index 0000000000000..035c09a8fa767 --- /dev/null +++ b/packages/eslint-plugin/rules/index.js @@ -0,0 +1 @@ +module.exports = require( 'requireindex' )( __dirname ); diff --git a/packages/eslint-plugin/rules/no-unused-vars-before-return.js b/packages/eslint-plugin/rules/no-unused-vars-before-return.js new file mode 100644 index 0000000000000..1ef00f21d7eb0 --- /dev/null +++ b/packages/eslint-plugin/rules/no-unused-vars-before-return.js @@ -0,0 +1,55 @@ +module.exports = { + meta: { + type: 'problem', + schema: [], + }, + create( context ) { + return { + ReturnStatement( node ) { + let functionScope = context.getScope(); + while ( functionScope.type !== 'function' && functionScope.upper ) { + functionScope = functionScope.upper; + } + + if ( ! functionScope ) { + return; + } + + for ( const variable of functionScope.variables ) { + const isAssignmentCandidate = variable.defs.some( ( def ) => { + return ( + def.node.type === 'VariableDeclarator' && + // Allow declarations which are not initialized. + def.node.init && + // Target function calls as "expensive". + def.node.init.type === 'CallExpression' && + // Allow unused if part of an object destructuring. + def.node.id.type !== 'ObjectPattern' && + // Only target assignments preceding `return`. + def.node.end < node.end + ); + } ); + + if ( ! isAssignmentCandidate ) { + continue; + } + + // The first entry in `references` is the declaration + // itself, which can be ignored. + const isUsedBeforeReturn = variable.references.slice( 1 ).some( ( reference ) => { + return reference.identifier.end < node.end; + } ); + + if ( isUsedBeforeReturn ) { + continue; + } + + context.report( + node, + `Declared variable \`${ variable.name }\` is unused before a return path` + ); + } + }, + }; + }, +};