@@ -346,32 +346,97 @@ module.exports = {
346346 }
347347
348348 /**
349- * Checks if a variable is inside the initializer of scopeVar .
349+ * Finds the uppermost expression node that can evaluate to the given one .
350350 *
351- * To avoid reporting at declarations such as `var a = function a() {};`.
352- * But it should report `var a = function(a) {};` or `var a = function() { function a() {} };`.
353- * @param {Object } variable The variable to check.
354- * @param {Object } scopeVar The scope variable to look for.
355- * @returns {boolean } Whether or not the variable is inside initializer of scopeVar.
351+ * Examples:
352+ * If given `a` in `a || foo`, it returns the `a || foo` node.
353+ * If given `a` in `foo ? a : bar`, it returns the `foo ? a : bar` node.
354+ * If given `a` in `foo ? bar : (baz && a)`, it returns the `foo ? bar : (baz && a)` node.
355+ * If given `a` in `a ? foo : bar`, it returns the `a` node.
356+ * If given `a` in `foo(a)`, it returns the `a` node.
357+ * @param {ASTNode } expression The expression node to unwrap.
358+ * @returns {ASTNode } The uppermost ancestor that can evaluate to the given node
359+ * or the given node if there is no such ancestor.
356360 */
357- function isOnInitializer ( variable , scopeVar ) {
358- const outerScope = scopeVar . scope ;
359- const outerDef = scopeVar . defs [ 0 ] ;
360- const outer = outerDef && outerDef . parent && outerDef . parent . range ;
361- const innerScope = variable . scope ;
362- const innerDef = variable . defs [ 0 ] ;
363- const inner = innerDef && innerDef . name . range ;
361+ function unwrapExpression ( expression ) {
362+ const { parent } = expression ;
364363
365- return (
366- outer &&
367- inner &&
368- outer [ 0 ] < inner [ 0 ] &&
369- inner [ 1 ] < outer [ 1 ] &&
370- ( ( innerDef . type === "FunctionName" &&
371- innerDef . node . type === "FunctionExpression" ) ||
372- innerDef . node . type === "ClassExpression" ) &&
373- outerScope === innerScope . upper
374- ) ;
364+ const shouldUnwrap =
365+ parent . type === "LogicalExpression" ||
366+ ( parent . type === "ConditionalExpression" &&
367+ parent . test !== expression ) ;
368+
369+ return shouldUnwrap ? unwrapExpression ( parent ) : expression ;
370+ }
371+
372+ /**
373+ * Checks if inner variable is the name of a function or class
374+ * that is assigned to outer variable as its initializer.
375+ *
376+ * To avoid reporting at declarations such as:
377+ * var a = function a() {};
378+ * var A = class A {};
379+ * var a = foo || function a() {};
380+ * var a = foo ? function a() {} : bar;
381+ * var { a = function a() {} } = foo;
382+ *
383+ * But it should report at declarations such as:
384+ * var a = function(a) {};
385+ * var a = function() { function a() {} };
386+ * var a = wrap(function a() {});
387+ * @param {Object } innerVariable The inner variable to check.
388+ * @param {Object } outerVariable The outer variable.
389+ * @returns {boolean } Whether or not inner variable is the name of a
390+ * function or class that is assigned to outer variable as its initializer.
391+ */
392+ function isFunctionNameInitializerException (
393+ innerVariable ,
394+ outerVariable ,
395+ ) {
396+ const outerDef = outerVariable . defs [ 0 ] ;
397+ const innerDef = innerVariable . defs [ 0 ] ;
398+
399+ if ( ! outerDef || ! innerDef ) {
400+ return false ;
401+ }
402+
403+ if (
404+ ! (
405+ ( innerDef . type === "FunctionName" &&
406+ innerDef . node . type === "FunctionExpression" ) ||
407+ ( innerDef . type === "ClassName" &&
408+ innerDef . node . type === "ClassExpression" )
409+ )
410+ ) {
411+ return false ;
412+ }
413+
414+ const outerIdentifier = outerDef . name ;
415+ let initializerNode ;
416+
417+ if ( outerIdentifier . parent . type === "VariableDeclarator" ) {
418+ initializerNode = outerIdentifier . parent . init ;
419+ } else if ( outerIdentifier . parent . type === "AssignmentPattern" ) {
420+ initializerNode = outerIdentifier . parent . right ;
421+ }
422+
423+ if ( ! initializerNode ) {
424+ return false ;
425+ }
426+
427+ const nodeToCheck = innerDef . node ; // FunctionExpression or ClassExpression node
428+
429+ // Exit early if the node to check isn't inside the initializer
430+ if (
431+ ! (
432+ initializerNode . range [ 0 ] <= nodeToCheck . range [ 0 ] &&
433+ nodeToCheck . range [ 1 ] <= initializerNode . range [ 1 ]
434+ )
435+ ) {
436+ return false ;
437+ }
438+
439+ return initializerNode === unwrapExpression ( nodeToCheck ) ;
375440 }
376441
377442 /**
@@ -577,7 +642,7 @@ module.exports = {
577642 shadowed &&
578643 ( shadowed . identifiers . length > 0 ||
579644 ( builtinGlobals && "writeable" in shadowed ) ) &&
580- ! isOnInitializer ( variable , shadowed ) &&
645+ ! isFunctionNameInitializerException ( variable , shadowed ) &&
581646 ! (
582647 ignoreOnInitialization &&
583648 isInitPatternNode ( variable , shadowed )
0 commit comments