@@ -18,6 +18,32 @@ import { hasDefinedProp } from "./hasDefinedProp";
1818import { hasTextContentChild } from "./hasTextContentChild" ;
1919import { hasTriggerProp } from "./hasTriggerProp" ;
2020
21+ /**
22+ * Auto-fix strategy types for accessibility rules
23+ */
24+ export type AutoFixStrategy =
25+ | "aria-label-placeholder" // Add aria-label=""
26+ | "aria-label-suggestion" // Add aria-label="[Component description]"
27+ | "add-required-prop" // Add specific required prop
28+ | "custom" ; // Custom fix logic
29+
30+ /**
31+ * Auto-fix configuration for accessibility rules
32+ */
33+ export type AutoFixConfig = {
34+ /** The auto-fix strategy to use */
35+ strategy : AutoFixStrategy ;
36+ /** For add-required-prop: the prop name to add */
37+ propName ?: string ;
38+ /** For add-required-prop: the default prop value */
39+ propValue ?: string ;
40+ /** For aria-label-suggestion: the suggested label text */
41+ suggestedLabel ?: string ;
42+ /** For custom: custom fix function */
43+ // eslint-disable-next-line no-unused-vars
44+ customFix ?: ( opening : TSESTree . JSXOpeningElement ) => string ;
45+ } ;
46+
2147/**
2248 * Configuration options for a rule created via the `ruleFactory`
2349 */
@@ -52,6 +78,7 @@ export type LabeledControlConfig = {
5278 allowTextContentChild ?: boolean ; // Accept text children to provide the label e.g. <Button>Click me</Button>
5379 triggerProp ?: string ; // Only apply rule when this trigger prop is present (e.g., "dismissible", "disabled")
5480 customValidator ?: Function ; // Custom validation logic
81+ autoFix ?: AutoFixConfig ; // Auto-fix configuration for the rule
5582} ;
5683
5784/**
@@ -104,6 +131,41 @@ export function hasAccessibleLabel(
104131 return false ;
105132}
106133
134+ /**
135+ * Generate auto-fix for accessibility rules based on configuration
136+ */
137+ export function generateAutoFix ( opening : TSESTree . JSXOpeningElement , config : AutoFixConfig ) : TSESLint . ReportFixFunction | null {
138+ if ( ! config ) return null ;
139+
140+ return ( fixer : TSESLint . RuleFixer ) => {
141+ switch ( config . strategy ) {
142+ case "aria-label-placeholder" : {
143+ return fixer . insertTextAfter ( opening . name , ' aria-label=""' ) ;
144+ }
145+
146+ case "aria-label-suggestion" : {
147+ const label = config . suggestedLabel || "Provide accessible name" ;
148+ return fixer . insertTextAfter ( opening . name , ` aria-label="${ label } "` ) ;
149+ }
150+
151+ case "add-required-prop" : {
152+ if ( ! config . propName ) return null ;
153+ const value = config . propValue || '""' ;
154+ return fixer . insertTextAfter ( opening . name , ` ${ config . propName } =${ value } ` ) ;
155+ }
156+
157+ case "custom" : {
158+ if ( ! config . customFix ) return null ;
159+ const fixText = config . customFix ( opening ) ;
160+ return fixer . insertTextAfter ( opening . name , fixText ) ;
161+ }
162+
163+ default :
164+ return null ;
165+ }
166+ } ;
167+ }
168+
107169/**
108170 * Factory for a minimal, strongly-configurable ESLint rule that enforces
109171 * accessible labeling on a specific JSX element/component.
@@ -118,6 +180,7 @@ export function makeLabeledControlRule(config: LabeledControlConfig): TSESLint.R
118180 recommended : "strict" ,
119181 url : "https://www.w3.org/TR/html-aria/"
120182 } ,
183+ fixable : config . autoFix ? "code" : undefined ,
121184 schema : [ ]
122185 } ,
123186 defaultOptions : [ ] ,
@@ -142,8 +205,15 @@ export function makeLabeledControlRule(config: LabeledControlConfig): TSESLint.R
142205 : ( isValid = hasAccessibleLabel ( opening , node , context , config ) ) ;
143206
144207 if ( ! isValid ) {
208+ // Generate auto-fix if configuration is provided
209+ const autoFix = config . autoFix ? generateAutoFix ( opening , config . autoFix ) : undefined ;
210+
145211 // report on the opening tag for better location
146- context . report ( { node : opening , messageId : config . messageId } ) ;
212+ context . report ( {
213+ node : opening ,
214+ messageId : config . messageId ,
215+ fix : autoFix
216+ } ) ;
147217 }
148218 }
149219 } ;
0 commit comments