@@ -62,7 +62,7 @@ export function decorate(rule: Rule.RuleModule): Rule.RuleModule {
6262 return ;
6363 }
6464
65- const node = descriptor . node as estree . Node ;
65+ const node = descriptor . node ;
6666
6767 // Find the component's propTypes declaration
6868 const propTypesValue = findPropTypesDeclaration ( node , context ) ;
@@ -93,7 +93,7 @@ export function decorate(rule: Rule.RuleModule): Rule.RuleModule {
9393 */
9494function extractPropertyName ( descriptor : Rule . ReportDescriptor ) : string | null {
9595 if ( 'data' in descriptor && descriptor . data && typeof descriptor . data === 'object' ) {
96- const data = descriptor . data as Record < string , unknown > ;
96+ const data = descriptor . data ;
9797 if ( 'name' in data && typeof data . name === 'string' ) {
9898 return data . name ;
9999 }
@@ -113,44 +113,105 @@ function findPropTypesDeclaration(
113113
114114 for ( let i = ancestors . length - 1 ; i >= 0 ; i -- ) {
115115 const ancestor = ancestors [ i ] ;
116-
117- // Pattern 1: Class component with static propTypes
118- if ( ancestor . type === 'ClassBody' ) {
119- const propTypes = findStaticPropTypes ( ancestor ) ;
120- if ( propTypes ) {
121- return getUniqueWriteUsageOrNode ( context , propTypes , true ) ;
122- }
116+ const propTypes = findPropTypesForAncestor ( ancestor , ancestors , context ) ;
117+ if ( propTypes ) {
118+ return propTypes ;
123119 }
120+ }
124121
125- // Pattern 2: External assignment - Component.defaultProps = {...}
126- // We need to find Component.propTypes
127- if ( ancestor . type === 'AssignmentExpression' ) {
128- const left = ancestor . left ;
129- if (
130- left . type === 'MemberExpression' &&
131- left . object . type === 'Identifier' &&
132- left . property . type === 'Identifier' &&
133- left . property . name === 'defaultProps'
134- ) {
135- const componentName = left . object . name ;
136- const propTypes = findExternalPropTypes ( componentName , ancestors ) ;
137- if ( propTypes ) {
138- return getUniqueWriteUsageOrNode ( context , propTypes , true ) ;
139- }
140- }
141- }
122+ return null ;
123+ }
142124
143- // Pattern 3: Reported node is inside a constant used as defaultProps
144- // e.g., const DefaultProps = { prop: value }; class uses static defaultProps = DefaultProps;
145- if ( ancestor . type === 'VariableDeclarator' && ancestor . id . type === 'Identifier' ) {
146- const constantName = ancestor . id . name ;
147- const propTypes = findPropTypesForDefaultPropsConstant ( constantName , ancestors ) ;
148- if ( propTypes ) {
149- return getUniqueWriteUsageOrNode ( context , propTypes , true ) ;
150- }
151- }
125+ /**
126+ * Try to find propTypes based on the ancestor node type.
127+ */
128+ function findPropTypesForAncestor (
129+ ancestor : estree . Node ,
130+ ancestors : estree . Node [ ] ,
131+ context : Rule . RuleContext ,
132+ ) : estree . Node | null {
133+ // Pattern 1: Class component with static propTypes
134+ if ( ancestor . type === 'ClassBody' ) {
135+ return findPropTypesFromClassBody ( ancestor , context ) ;
136+ }
137+
138+ // Pattern 2: External assignment - Component.defaultProps = {...}
139+ if ( ancestor . type === 'AssignmentExpression' ) {
140+ return findPropTypesFromAssignment ( ancestor , ancestors , context ) ;
141+ }
142+
143+ // Pattern 3: Reported node is inside a constant used as defaultProps
144+ if ( ancestor . type === 'VariableDeclarator' && ancestor . id . type === 'Identifier' ) {
145+ return findPropTypesFromVariableDeclarator ( ancestor , ancestors , context ) ;
146+ }
147+
148+ return null ;
149+ }
150+
151+ /**
152+ * Find propTypes from a class body.
153+ */
154+ function findPropTypesFromClassBody (
155+ classBody : estree . ClassBody ,
156+ context : Rule . RuleContext ,
157+ ) : estree . Node | null {
158+ const propTypes = findStaticPropTypes ( classBody ) ;
159+ if ( propTypes ) {
160+ return getUniqueWriteUsageOrNode ( context , propTypes , true ) ;
161+ }
162+ return null ;
163+ }
164+
165+ /**
166+ * Find propTypes from an assignment expression (Component.defaultProps = {...}).
167+ */
168+ function findPropTypesFromAssignment (
169+ assignment : estree . AssignmentExpression ,
170+ ancestors : estree . Node [ ] ,
171+ context : Rule . RuleContext ,
172+ ) : estree . Node | null {
173+ const componentName = getDefaultPropsComponentName ( assignment . left ) ;
174+ if ( ! componentName ) {
175+ return null ;
176+ }
177+ const propTypes = findExternalPropTypes ( componentName , ancestors ) ;
178+ if ( propTypes ) {
179+ return getUniqueWriteUsageOrNode ( context , propTypes , true ) ;
180+ }
181+ return null ;
182+ }
183+
184+ /**
185+ * Extract component name from a defaultProps assignment target.
186+ */
187+ function getDefaultPropsComponentName ( left : estree . Pattern ) : string | null {
188+ if (
189+ left . type === 'MemberExpression' &&
190+ left . object . type === 'Identifier' &&
191+ left . property . type === 'Identifier' &&
192+ left . property . name === 'defaultProps'
193+ ) {
194+ return left . object . name ;
152195 }
196+ return null ;
197+ }
153198
199+ /**
200+ * Find propTypes from a variable declarator (constant used as defaultProps).
201+ */
202+ function findPropTypesFromVariableDeclarator (
203+ declarator : estree . VariableDeclarator ,
204+ ancestors : estree . Node [ ] ,
205+ context : Rule . RuleContext ,
206+ ) : estree . Node | null {
207+ if ( declarator . id . type !== 'Identifier' ) {
208+ return null ;
209+ }
210+ const constantName = declarator . id . name ;
211+ const propTypes = findPropTypesForDefaultPropsConstant ( constantName , ancestors ) ;
212+ if ( propTypes ) {
213+ return getUniqueWriteUsageOrNode ( context , propTypes , true ) ;
214+ }
154215 return null ;
155216}
156217
@@ -179,7 +240,7 @@ function findExternalPropTypes(
179240 componentName : string ,
180241 ancestors : estree . Node [ ] ,
181242) : estree . Node | null {
182- const program = ancestors . find ( n => n . type === 'Program' ) as estree . Program | undefined ;
243+ const program = ancestors . find ( ( n ) : n is estree . Program => n . type === 'Program' ) ;
183244 if ( ! program ) {
184245 return null ;
185246 }
@@ -247,7 +308,7 @@ function findPropTypesForDefaultPropsConstant(
247308 constantName : string ,
248309 ancestors : estree . Node [ ] ,
249310) : estree . Node | null {
250- const program = ancestors . find ( n => n . type === 'Program' ) as estree . Program | undefined ;
311+ const program = ancestors . find ( ( n ) : n is estree . Program => n . type === 'Program' ) ;
251312 if ( ! program ) {
252313 return null ;
253314 }
@@ -271,10 +332,8 @@ function findPropTypesInStatement(
271332) : estree . Node | null {
272333 // Check class declarations
273334 const classInfo = extractClassDeclaration ( statement ) ;
274- if ( classInfo ) {
275- if ( classUsesDefaultPropsConstant ( classInfo . body , defaultPropsConstantName ) ) {
276- return findStaticPropTypes ( classInfo . body ) ;
277- }
335+ if ( classInfo && classUsesDefaultPropsConstant ( classInfo . body , defaultPropsConstantName ) ) {
336+ return findStaticPropTypes ( classInfo . body ) ;
278337 }
279338
280339 // Check for function declarations that return classes (factory pattern)
@@ -286,15 +345,17 @@ function findPropTypesInStatement(
286345 }
287346
288347 // Check for exported functions
289- if ( statement . type === 'ExportNamedDeclaration' && statement . declaration ) {
290- if ( statement . declaration . type === 'FunctionDeclaration' && statement . declaration . body ) {
291- const propTypes = findPropTypesInFunctionBody (
292- statement . declaration . body ,
293- defaultPropsConstantName ,
294- ) ;
295- if ( propTypes ) {
296- return propTypes ;
297- }
348+ if (
349+ statement . type === 'ExportNamedDeclaration' &&
350+ statement . declaration ?. type === 'FunctionDeclaration' &&
351+ statement . declaration . body
352+ ) {
353+ const propTypes = findPropTypesInFunctionBody (
354+ statement . declaration . body ,
355+ defaultPropsConstantName ,
356+ ) ;
357+ if ( propTypes ) {
358+ return propTypes ;
298359 }
299360 }
300361
@@ -328,13 +389,12 @@ function findPropTypesInFunctionBody(
328389 defaultPropsConstantName : string ,
329390) : estree . Node | null {
330391 for ( const stmt of body . body ) {
331- if ( stmt . type === 'ReturnStatement' && stmt . argument ) {
332- if (
333- stmt . argument . type === 'ClassExpression' &&
334- classUsesDefaultPropsConstant ( stmt . argument . body , defaultPropsConstantName )
335- ) {
336- return findStaticPropTypes ( stmt . argument . body ) ;
337- }
392+ if (
393+ stmt . type === 'ReturnStatement' &&
394+ stmt . argument ?. type === 'ClassExpression' &&
395+ classUsesDefaultPropsConstant ( stmt . argument . body , defaultPropsConstantName )
396+ ) {
397+ return findStaticPropTypes ( stmt . argument . body ) ;
338398 }
339399 }
340400 return null ;
0 commit comments