@@ -164,6 +164,42 @@ MyComponent.propTypes = {
164164 } ) ;
165165 } ) ;
166166
167+ it ( 'should not report props accessed via computed bracket notation' , ( ) => {
168+ const ruleTester = new NoTypeCheckingRuleTester ( ) ;
169+
170+ ruleTester . run ( 'no-unused-prop-types' , rule , {
171+ valid : [
172+ {
173+ // FP: props[key] computed access
174+ code : `
175+ function AnimationComponent(props) {
176+ const key = 'offsetX';
177+ return <div value={props[key]} />;
178+ }
179+ AnimationComponent.propTypes = {
180+ offsetX: PropTypes.number,
181+ };
182+ ` ,
183+ } ,
184+ {
185+ // FP: this.props[key] computed access
186+ code : `
187+ class VictoryAxis extends React.Component {
188+ render() {
189+ const key = 'offsetX';
190+ return <div>{this.props[key]}</div>;
191+ }
192+ }
193+ VictoryAxis.propTypes = {
194+ offsetX: PropTypes.number,
195+ };
196+ ` ,
197+ } ,
198+ ] ,
199+ invalid : [ ] ,
200+ } ) ;
201+ } ) ;
202+
167203 it ( 'should exercise TypeScript type-checking paths (Strategy C in react helpers)' , ( ) => {
168204 const ruleTester = new RuleTester ( {
169205 parserOptions : {
@@ -220,6 +256,31 @@ interface MyComponentProps {
220256function MyComponent(props: MyComponentProps) {
221257 return <SomeComponent {...props} />;
222258}
259+ ` ,
260+ filename : fixtureFile ,
261+ } ,
262+ {
263+ // FP: TypeScript class component with this.props[key] — Strategy C matches
264+ // VictoryAxisProps to VictoryAxis via matchesClassProps, hasPropsCall finds computed MemberExpression.
265+ code : `
266+ declare const React: any;
267+ interface VictoryAxisProps {
268+ offsetX?: number;
269+ offsetY?: number;
270+ }
271+ class VictoryAxis extends React.Component<VictoryAxisProps> {
272+ props: VictoryAxisProps;
273+ static animationWhitelist: Array<keyof VictoryAxisProps> = ['offsetX', 'offsetY'];
274+ animate() {
275+ return VictoryAxis.animationWhitelist.reduce((acc: Record<string, unknown>, key) => {
276+ acc[key] = this.props[key];
277+ return acc;
278+ }, {});
279+ }
280+ render() {
281+ return <div>{this.animate()}</div>;
282+ }
283+ }
223284` ,
224285 filename : fixtureFile ,
225286 } ,
@@ -350,6 +411,34 @@ Wrapper.propTypes = { onClick: PropTypes.func };
350411 } ) ;
351412 } ) ;
352413
414+ it ( 'upstream rule should report dynamic bracket notation FP pattern (sentinel: remove decorator check if this fails)' , ( ) => {
415+ // Confirms that the upstream eslint-plugin-react no-unused-prop-types rule DOES raise
416+ // issues when props are accessed via computed bracket notation (props[key], this.props[key]).
417+ // If this test starts failing, the upstream rule has been fixed and the computed
418+ // MemberExpression handling in the S6767 decorator can be removed.
419+ const upstreamRule = rules [ 'no-unused-prop-types' ] ;
420+ const ruleTester = new NoTypeCheckingRuleTester ( ) ;
421+
422+ ruleTester . run ( 'no-unused-prop-types (upstream, bracket notation)' , upstreamRule , {
423+ valid : [ ] ,
424+ invalid : [
425+ {
426+ // upstream does not track props[key] computed bracket access
427+ code : `
428+ function AnimationComponent(props) {
429+ const key = 'offsetX';
430+ return <div value={props[key]} />;
431+ }
432+ AnimationComponent.propTypes = {
433+ offsetX: PropTypes.number,
434+ };
435+ ` ,
436+ errors : 1 ,
437+ } ,
438+ ] ,
439+ } ) ;
440+ } ) ;
441+
353442 it ( 'upstream rule should NOT report state interface properties when TypeScript type info is available' , ( ) => {
354443 // Confirms that the upstream rule uses TypeScript type information to distinguish
355444 // state types from props types. AnchorState (used as the second type parameter of
0 commit comments