@@ -3,6 +3,7 @@ package reactutil
33import (
44 "github.com/microsoft/TypeScript/tsc/shim/ast"
55 "github.com/microsoft/TypeScript/tsc/shim/checker"
6+ "github.com/web-infra-dev/rslint/internal/utils"
67 scopeAnalysis "github.com/web-infra-dev/rslint/internal/utils/scopeanalysis"
78)
89
@@ -325,8 +326,8 @@ func GetParentStatelessComponent(node *ast.Node, pragma string, wrappers []Compo
325326// upstream: VariableDeclarator/PropertyAssignment use the binding name;
326327// `Id = fn` assignments use the LHS Identifier; MemberExpression LHS
327328// uses the rightmost property name (with `module.exports = ...` as a
328- // special blanket-true case); a named FunctionExpression defers to its
329- // own Identifier.
329+ // special blanket-true case). A named FunctionExpression uses its own
330+ // Identifier only after these position-specific checks .
330331//
331332// Pass the empty string for `pragma` to default to `DefaultReactPragma`.
332333//
@@ -386,10 +387,8 @@ func isStatelessReactComponentCore(fn *ast.Node, pragma string, tc *checker.Chec
386387 switch fn .Kind {
387388 case ast .KindMethodDeclaration , ast .KindGetAccessor , ast .KindSetAccessor :
388389 // Object-literal shorthand method / accessor. Upstream's Property
389- // branch (method && !computed) | (!id && !computed) classifies the
390- // inner FE as a component when the property key is a capitalized
391- // Identifier AND the function returns strict JSX (isReturningJSX).
392- // Setters naturally fail functionReturnsJSX (no return value).
390+ // branch requires a capitalized Identifier and a JSX return for
391+ // non-computed keys. Computed keys reach the later name/null gates.
393392 // Class-body occurrences have a ClassLike parent — NOT
394393 // ObjectLiteralExpression — and are excluded so they continue to go
395394 // through the ES6-class path.
@@ -398,6 +397,16 @@ func isStatelessReactComponentCore(fn *ast.Node, pragma string, tc *checker.Chec
398397 return false
399398 }
400399 name := fn .Name ()
400+ if name != nil && name .Kind == ast .KindComputedPropertyName {
401+ key := utils .ESTreeRuntimeExpression (name .AsComputedPropertyName ().Expression )
402+ if key .Kind == ast .KindIdentifier && IsLowercaseFirstLetter (key .Text ()) && len (utils .ESTreeParameters (fn )) > 0 {
403+ return false
404+ }
405+ if ast .IsAccessExpression (key ) && ! ast .IsOptionalChain (key ) {
406+ return functionReturnsJSXInternal (fn , false , pragma , tc , scopes )
407+ }
408+ return functionReturnsJSXInternal (fn , true , pragma , tc , scopes ) && ! functionReturnsOnlyNull (fn )
409+ }
401410 if name == nil || name .Kind != ast .KindIdentifier {
402411 return false
403412 }
@@ -419,7 +428,7 @@ func isStatelessReactComponentCore(fn *ast.Node, pragma string, tc *checker.Chec
419428 return false
420429 }
421430
422- parent := fn . Parent
431+ parent := utils . ESTreeParent ( fn )
423432 if parent == nil {
424433 return false
425434 }
@@ -428,17 +437,16 @@ func isStatelessReactComponentCore(fn *ast.Node, pragma string, tc *checker.Chec
428437 // `isModuleExportsAssignment`.
429438 isMEAssign := false
430439 isModuleExportsAssign := false
440+ var assignedProperty * ast.Node
431441 if parent .Kind == ast .KindBinaryExpression {
432442 bin := parent .AsBinaryExpression ()
433- if bin .OperatorToken != nil && bin .OperatorToken .Kind == ast .KindEqualsToken && bin .Right == fn {
443+ if bin .OperatorToken != nil && bin .OperatorToken .Kind == ast .KindEqualsToken && utils . ESTreeRuntimeExpression ( bin .Right ) == fn {
434444 left := ast .SkipParentheses (bin .Left )
435- if left . Kind == ast . KindPropertyAccessExpression {
445+ if object , property := utils . MemberExpressionParts ( left ); object != nil {
436446 isMEAssign = true
437- pa := left .AsPropertyAccessExpression ()
438- obj := ast .SkipParentheses (pa .Expression )
439- name := pa .Name ()
440- if obj .Kind == ast .KindIdentifier && obj .AsIdentifier ().Text == "module" &&
441- name != nil && name .Kind == ast .KindIdentifier && name .AsIdentifier ().Text == "exports" {
447+ obj := utils .ESTreeRuntimeExpression (object )
448+ assignedProperty = utils .ESTreeRuntimeExpression (property )
449+ if IdentifierOrPrivateName (obj ) == "module" && IdentifierOrPrivateName (assignedProperty ) == "exports" {
442450 isModuleExportsAssign = true
443451 }
444452 }
@@ -465,7 +473,7 @@ func isStatelessReactComponentCore(fn *ast.Node, pragma string, tc *checker.Chec
465473 // Branch 3 — early-reject in ReturnStatement / arrow-expression-body
466474 // when not strictly returning JSX.
467475 if parent .Kind == ast .KindReturnStatement ||
468- (parent .Kind == ast .KindArrowFunction && parent .AsArrowFunction ().Body == fn ) {
476+ (parent .Kind == ast .KindArrowFunction && utils . ESTreeRuntimeExpression ( parent .AsArrowFunction ().Body ) == fn ) {
469477 if ! functionReturnsJSXInternal (fn , false , pragma , tc , scopes ) {
470478 return false
471479 }
@@ -475,19 +483,10 @@ func isStatelessReactComponentCore(fn *ast.Node, pragma string, tc *checker.Chec
475483 // (handled; Identifier LHS path).
476484 if parent .Kind == ast .KindBinaryExpression && ! isMEAssign {
477485 bin := parent .AsBinaryExpression ()
478- if bin .OperatorToken != nil && bin .OperatorToken .Kind == ast .KindEqualsToken && bin .Right == fn {
486+ if bin .OperatorToken != nil && bin .OperatorToken .Kind == ast .KindEqualsToken && utils . ESTreeRuntimeExpression ( bin .Right ) == fn {
479487 if ! functionReturnsJSXInternal (fn , true , pragma , tc , scopes ) {
480488 return false
481489 }
482- // Named FE defers to its own id (matches upstream's final
483- // `if (node.id)` check, which runs before the lowercase-LHS
484- // reject in the property-assignment tail).
485- if fn .Kind == ast .KindFunctionExpression {
486- name := fn .Name ()
487- if name != nil && name .Kind == ast .KindIdentifier {
488- return isFirstLetterCapitalized (name .AsIdentifier ().Text )
489- }
490- }
491490 left := ast .SkipParentheses (bin .Left )
492491 if left .Kind == ast .KindIdentifier {
493492 return isFirstLetterCapitalized (left .AsIdentifier ().Text )
@@ -498,13 +497,13 @@ func isStatelessReactComponentCore(fn *ast.Node, pragma string, tc *checker.Chec
498497
499498 // Branches 5 & 6 — nested Arrow whose outer Arrow is itself in an
500499 // AssignmentExpression / PropertyAssignment position.
501- if parent .Kind == ast .KindArrowFunction && parent .AsArrowFunction ().Body == fn {
502- grand := parent . Parent
500+ if parent .Kind == ast .KindArrowFunction && utils . ESTreeRuntimeExpression ( parent .AsArrowFunction ().Body ) == fn {
501+ grand := utils . ESTreeParent ( parent )
503502 if grand != nil && ! isMEAssign && functionReturnsJSXInternal (fn , true , pragma , tc , scopes ) {
504503 switch grand .Kind {
505504 case ast .KindBinaryExpression :
506505 bin := grand .AsBinaryExpression ()
507- if bin .OperatorToken != nil && bin .OperatorToken .Kind == ast .KindEqualsToken && bin .Right == parent {
506+ if bin .OperatorToken != nil && bin .OperatorToken .Kind == ast .KindEqualsToken && utils . ESTreeRuntimeExpression ( bin .Right ) == parent {
508507 left := ast .SkipParentheses (bin .Left )
509508 if left .Kind == ast .KindIdentifier {
510509 return isFirstLetterCapitalized (left .AsIdentifier ().Text )
@@ -645,17 +644,9 @@ func isStatelessReactComponentCore(fn *ast.Node, pragma string, tc *checker.Chec
645644 }
646645
647646 // Branch 15 — isPropertyAssignment (MemberExpression LHS) but not
648- // module.exports: reject when rightmost property name is lowercase.
649- if isMEAssign && ! isModuleExportsAssign {
650- bin := parent .AsBinaryExpression ()
651- left := ast .SkipParentheses (bin .Left )
652- if left .Kind == ast .KindPropertyAccessExpression {
653- pa := left .AsPropertyAccessExpression ()
654- name := pa .Name ()
655- if name != nil && name .Kind == ast .KindIdentifier && ! isFirstLetterCapitalized (name .AsIdentifier ().Text ) {
656- return false
657- }
658- }
647+ // module.exports: compare property.name, not a computed string's value.
648+ if isMEAssign && ! isModuleExportsAssign && ! isFirstLetterCapitalized (IdentifierOrPrivateName (assignedProperty )) {
649+ return false
659650 }
660651
661652 // Branch 16 — Property parent + returns only null ⇒ undefined.
@@ -703,6 +694,8 @@ func functionReturnsOnlyNull(fn *ast.Node) bool {
703694 body = fn .AsMethodDeclaration ().Body
704695 case ast .KindGetAccessor :
705696 body = fn .AsGetAccessorDeclaration ().Body
697+ case ast .KindSetAccessor :
698+ body = fn .AsSetAccessorDeclaration ().Body
706699 }
707700 if body == nil {
708701 return false
@@ -759,7 +752,7 @@ func isExportDefaultAssignment(node *ast.Node) bool {
759752// `const Hello = (init(), arrow)` — whose comma Sequence sits inside parens —
760753// still reaches the VariableDeclaration ancestor.
761754func isInAllowedPositionForComponent (fn * ast.Node ) bool {
762- parent := skipParenParents (fn )
755+ parent := utils . ESTreeParent (fn )
763756 if parent == nil {
764757 return false
765758 }
@@ -779,28 +772,18 @@ func isInAllowedPositionForComponent(fn *ast.Node) bool {
779772 switch bin .OperatorToken .Kind {
780773 case ast .KindEqualsToken :
781774 // AssignmentExpression — always allowed when `fn` is the RHS.
782- return bin .Right == fn
775+ return utils . ESTreeRuntimeExpression ( bin .Right ) == fn
783776 case ast .KindCommaToken :
784777 // SequenceExpression — only the last operand inherits its parent's
785778 // allowed-ness.
786- if bin .Right == fn {
779+ if utils . ESTreeRuntimeExpression ( bin .Right ) == fn {
787780 return isInAllowedPositionForComponent (parent )
788781 }
789782 }
790783 }
791784 return false
792785}
793786
794- // skipParenParents walks up through ParenthesizedExpression wrappers and
795- // returns the first non-paren ancestor of `node`, or nil.
796- func skipParenParents (node * ast.Node ) * ast.Node {
797- p := node .Parent
798- for p != nil && p .Kind == ast .KindParenthesizedExpression {
799- p = p .Parent
800- }
801- return p
802- }
803-
804787// isPragmaComponentWrapperCall reports whether `call` is a React
805788// component-wrapping call — `<pragma>.memo(fn)` / `<pragma>.forwardRef(fn)` /
806789// bare `memo(fn)` / bare `forwardRef(fn)` — with `fn` as the first argument.
0 commit comments