gbu-accessibility-package 3.3.0 โ†’ 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -23,6 +23,7 @@ const options = {
23
23
  langOnly: false,
24
24
  roleOnly: false,
25
25
  formsOnly: false,
26
+ nestedOnly: false,
26
27
  buttonsOnly: false,
27
28
  linksOnly: false,
28
29
  landmarksOnly: false,
@@ -80,6 +81,9 @@ for (let i = 0; i < args.length; i++) {
80
81
  case '--forms-only':
81
82
  options.formsOnly = true;
82
83
  break;
84
+ case '--nested-only':
85
+ options.nestedOnly = true;
86
+ break;
83
87
  case '--buttons-only':
84
88
  options.buttonsOnly = true;
85
89
  break;
@@ -227,7 +231,7 @@ async function main() {
227
231
  try {
228
232
  // Handle different modes - All modes now include cleanup
229
233
  if (options.cleanupOnly || options.altOnly || options.langOnly || options.roleOnly ||
230
- options.formsOnly || options.buttonsOnly || options.linksOnly || options.landmarksOnly ||
234
+ options.formsOnly || options.nestedOnly || options.buttonsOnly || options.linksOnly || options.landmarksOnly ||
231
235
  options.headingsOnly || options.brokenLinksOnly) {
232
236
  // Individual modes - handle each separately, then run cleanup
233
237
  } else {
@@ -333,6 +337,24 @@ async function main() {
333
337
  showCompletionMessage(options, 'Form label fixes + cleanup');
334
338
  return;
335
339
 
340
+ } else if (options.nestedOnly) {
341
+ // Fix nested interactive controls + cleanup
342
+ console.log(chalk.blue('๐ŸŽฏ Running nested interactive controls fixes + cleanup...'));
343
+ const nestedResults = await fixer.fixNestedInteractiveControls(options.directory);
344
+ const nestedFixed = nestedResults.filter(r => r.status === 'fixed').length;
345
+ const totalNestedIssues = nestedResults.reduce((sum, r) => sum + (r.issues || 0), 0);
346
+
347
+ console.log(chalk.green(`\nโœ… Fixed nested interactive controls in ${nestedFixed} files (${totalNestedIssues} issues)`));
348
+
349
+ // Run cleanup
350
+ console.log(chalk.blue('\n๐Ÿงน Running cleanup for duplicate role attributes...'));
351
+ const cleanupResults = await fixer.cleanupDuplicateRoles(options.directory);
352
+ const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
353
+ console.log(chalk.green(`โœ… Cleaned duplicate roles in ${cleanupFixed} files`));
354
+
355
+ showCompletionMessage(options, 'Nested interactive controls fixes + cleanup');
356
+ return;
357
+
336
358
  } else if (options.buttonsOnly) {
337
359
  // Fix button names + cleanup
338
360
  console.log(chalk.blue('๐Ÿ”˜ Running button name fixes + cleanup...'));
@@ -0,0 +1,92 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ja">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Nested Interactive Controls Test - Accessibility Issues</title>
7
+ </head>
8
+ <body>
9
+ <h1>Nested Interactive Controls Test Cases</h1>
10
+
11
+ <!-- Test Case 1: div[role="button"] containing links (like in the axe error) -->
12
+ <div class="card-buttons" role="button">
13
+ <a href="https://business.mobile.rakuten.co.jp/solution/service/rakuten-ai-for-business/?scid=we_solution09_2504" class="btn btn-secondary" target="_blank" role="link">่ฉณ็ดฐใ‚’่ฆ‹ใ‚‹</a>
14
+ <a href="https://business.mobile.rakuten.co.jp/solution/ai/inquiry/?l=id=solution_ai_inquiry1&amp;scid=we_solution10_2504" class="btn btn-primary" target="_blank" role="link">ใŠๅ•ใ„ๅˆใ‚ใ›</a>
15
+ </div>
16
+
17
+ <!-- Test Case 2: Button containing links -->
18
+ <button type="button" onclick="handleClick()">
19
+ <a href="/page1">Link inside button</a>
20
+ <span>Click me</span>
21
+ </button>
22
+
23
+ <!-- Test Case 3: Link containing button -->
24
+ <a href="/page2">
25
+ <button type="button">Button inside link</button>
26
+ </a>
27
+
28
+ <!-- Test Case 4: div[role="button"] containing input -->
29
+ <div role="button" tabindex="0" onclick="submit()">
30
+ <input type="text" placeholder="Search...">
31
+ <span>Submit</span>
32
+ </div>
33
+
34
+ <!-- Test Case 5: Link containing select -->
35
+ <a href="/settings">
36
+ <select name="language">
37
+ <option value="ja">Japanese</option>
38
+ <option value="en">English</option>
39
+ </select>
40
+ Settings
41
+ </a>
42
+
43
+ <!-- Test Case 6: Button containing textarea -->
44
+ <button type="submit">
45
+ <textarea name="comment" placeholder="Enter comment"></textarea>
46
+ <span>Send</span>
47
+ </button>
48
+
49
+ <!-- Test Case 7: Multiple levels of nesting -->
50
+ <div role="button" tabindex="0">
51
+ <div class="container">
52
+ <a href="/nested">
53
+ <button type="button">Deeply nested</button>
54
+ </a>
55
+ </div>
56
+ </div>
57
+
58
+ <!-- Test Case 8: div[role="button"] with tabindex containing interactive elements -->
59
+ <div role="button" tabindex="0" onclick="handleAction()">
60
+ <input type="checkbox" id="agree">
61
+ <label for="agree">I agree</label>
62
+ <a href="/terms">Terms</a>
63
+ </div>
64
+
65
+ <!-- Test Case 9: Link containing details/summary -->
66
+ <a href="/info">
67
+ <details>
68
+ <summary>More info</summary>
69
+ <p>Details content</p>
70
+ </details>
71
+ </a>
72
+
73
+ <!-- Test Case 10: Form elements nested in buttons -->
74
+ <button type="button" class="form-button">
75
+ <input type="radio" name="choice" value="1">
76
+ <input type="radio" name="choice" value="2">
77
+ <span>Choose option</span>
78
+ </button>
79
+
80
+ <!-- Test Case 11: Correct structure (should not be flagged) -->
81
+ <div class="card-buttons">
82
+ <a href="/page1" class="btn btn-secondary">่ฉณ็ดฐใ‚’่ฆ‹ใ‚‹</a>
83
+ <a href="/page2" class="btn btn-primary">ใŠๅ•ใ„ๅˆใ‚ใ›</a>
84
+ </div>
85
+
86
+ <!-- Test Case 12: Another correct structure -->
87
+ <button type="button" onclick="handleClick()">
88
+ <span>Click me</span>
89
+ <i class="icon"></i>
90
+ </button>
91
+ </body>
92
+ </html>
package/lib/fixer.js CHANGED
@@ -4093,6 +4093,367 @@ class AccessibilityFixer {
4093
4093
  return fixed;
4094
4094
  }
4095
4095
 
4096
+ async fixNestedInteractiveControls(directory = '.') {
4097
+ console.log(chalk.blue('๐ŸŽฏ Fixing nested interactive controls...'));
4098
+
4099
+ const htmlFiles = await this.findHtmlFiles(directory);
4100
+ const results = [];
4101
+ let totalIssuesFound = 0;
4102
+
4103
+ for (const file of htmlFiles) {
4104
+ try {
4105
+ const content = await fs.readFile(file, 'utf8');
4106
+ const issues = this.analyzeNestedInteractiveControls(content);
4107
+
4108
+ if (issues.length > 0) {
4109
+ console.log(chalk.cyan(`\n๐Ÿ“ ${file}:`));
4110
+ issues.forEach(issue => {
4111
+ console.log(chalk.yellow(` ${issue.type}: ${issue.description}`));
4112
+ if (issue.suggestion) {
4113
+ console.log(chalk.gray(` ๐Ÿ’ก ${issue.suggestion}`));
4114
+ }
4115
+ totalIssuesFound++;
4116
+ });
4117
+ }
4118
+
4119
+ const fixed = this.fixNestedInteractiveControlsInContent(content);
4120
+
4121
+ if (fixed !== content) {
4122
+ if (this.config.backupFiles) {
4123
+ await fs.writeFile(`${file}.backup`, content);
4124
+ }
4125
+
4126
+ if (!this.config.dryRun) {
4127
+ await fs.writeFile(file, fixed);
4128
+ }
4129
+
4130
+ console.log(chalk.green(`โœ… Fixed nested interactive controls in: ${file}`));
4131
+ results.push({ file, status: 'fixed', issues: issues.length });
4132
+ } else {
4133
+ results.push({ file, status: 'no-change', issues: issues.length });
4134
+ }
4135
+ } catch (error) {
4136
+ console.error(chalk.red(`โŒ Error processing ${file}: ${error.message}`));
4137
+ results.push({ file, status: 'error', error: error.message });
4138
+ }
4139
+ }
4140
+
4141
+ console.log(chalk.blue(`\n๐Ÿ“Š Summary: Found ${totalIssuesFound} nested interactive control issues across ${results.length} files`));
4142
+ return results;
4143
+ }
4144
+
4145
+ analyzeNestedInteractiveControls(content) {
4146
+ const issues = [];
4147
+
4148
+ // Define interactive elements and their roles
4149
+ const interactiveElements = [
4150
+ { tag: 'button', role: 'button' },
4151
+ { tag: 'a', role: 'link', requiresHref: true },
4152
+ { tag: 'input', role: 'textbox|button|checkbox|radio|slider|spinbutton' },
4153
+ { tag: 'textarea', role: 'textbox' },
4154
+ { tag: 'select', role: 'combobox|listbox' },
4155
+ { tag: 'details', role: 'group' },
4156
+ { tag: 'summary', role: 'button' }
4157
+ ];
4158
+
4159
+ // Also check for elements with interactive roles
4160
+ const interactiveRoles = [
4161
+ 'button', 'link', 'textbox', 'checkbox', 'radio', 'slider',
4162
+ 'spinbutton', 'combobox', 'listbox', 'menuitem', 'tab',
4163
+ 'treeitem', 'gridcell', 'option'
4164
+ ];
4165
+
4166
+ // Find all interactive elements
4167
+ const interactiveSelectors = [];
4168
+
4169
+ // Add tag-based selectors
4170
+ interactiveElements.forEach(element => {
4171
+ if (element.requiresHref) {
4172
+ interactiveSelectors.push(`<${element.tag}[^>]*href[^>]*>`);
4173
+ } else {
4174
+ interactiveSelectors.push(`<${element.tag}[^>]*>`);
4175
+ }
4176
+ });
4177
+
4178
+ // Add role-based selectors
4179
+ interactiveRoles.forEach(role => {
4180
+ interactiveSelectors.push(`<[^>]*role\\s*=\\s*["']${role}["'][^>]*>`);
4181
+ });
4182
+
4183
+ // Create combined regex pattern
4184
+ const interactivePattern = new RegExp(interactiveSelectors.join('|'), 'gi');
4185
+
4186
+ // Find all interactive elements with their positions
4187
+ const interactiveMatches = [];
4188
+ let match;
4189
+
4190
+ while ((match = interactivePattern.exec(content)) !== null) {
4191
+ const element = match[0];
4192
+ const startPos = match.index;
4193
+ const endPos = this.findElementEndPosition(content, element, startPos);
4194
+
4195
+ if (endPos > startPos) {
4196
+ interactiveMatches.push({
4197
+ element: element,
4198
+ startPos: startPos,
4199
+ endPos: endPos,
4200
+ fullElement: content.substring(startPos, endPos)
4201
+ });
4202
+ }
4203
+ }
4204
+
4205
+ // Check for nesting
4206
+ for (let i = 0; i < interactiveMatches.length; i++) {
4207
+ const parent = interactiveMatches[i];
4208
+
4209
+ for (let j = 0; j < interactiveMatches.length; j++) {
4210
+ if (i === j) continue;
4211
+
4212
+ const child = interactiveMatches[j];
4213
+
4214
+ // Check if child is nested inside parent
4215
+ if (child.startPos > parent.startPos && child.endPos < parent.endPos) {
4216
+ const parentType = this.getInteractiveElementType(parent.element);
4217
+ const childType = this.getInteractiveElementType(child.element);
4218
+
4219
+ issues.push({
4220
+ type: '๐ŸŽฏ Nested interactive controls',
4221
+ description: `${childType} is nested inside ${parentType}`,
4222
+ parentElement: parent.element.substring(0, 100) + '...',
4223
+ childElement: child.element.substring(0, 100) + '...',
4224
+ suggestion: `Remove interactive role from parent or child, or restructure HTML to avoid nesting`
4225
+ });
4226
+ }
4227
+ }
4228
+ }
4229
+
4230
+ return issues;
4231
+ }
4232
+
4233
+ findElementEndPosition(content, startTag, startPos) {
4234
+ // Extract tag name from start tag
4235
+ const tagMatch = startTag.match(/<(\w+)/);
4236
+ if (!tagMatch) return startPos + startTag.length;
4237
+
4238
+ const tagName = tagMatch[1].toLowerCase();
4239
+
4240
+ // Self-closing tags
4241
+ if (startTag.endsWith('/>') || ['input', 'img', 'br', 'hr', 'meta', 'link'].includes(tagName)) {
4242
+ return startPos + startTag.length;
4243
+ }
4244
+
4245
+ // Find matching closing tag
4246
+ const closeTagPattern = new RegExp(`</${tagName}>`, 'i');
4247
+ const remainingContent = content.substring(startPos + startTag.length);
4248
+ const closeMatch = remainingContent.match(closeTagPattern);
4249
+
4250
+ if (closeMatch) {
4251
+ return startPos + startTag.length + closeMatch.index + closeMatch[0].length;
4252
+ }
4253
+
4254
+ // If no closing tag found, assume it ends at the start tag
4255
+ return startPos + startTag.length;
4256
+ }
4257
+
4258
+ getInteractiveElementType(element) {
4259
+ // Extract tag name
4260
+ const tagMatch = element.match(/<(\w+)/);
4261
+ const tagName = tagMatch ? tagMatch[1].toLowerCase() : 'element';
4262
+
4263
+ // Extract role if present
4264
+ const roleMatch = element.match(/role\s*=\s*["']([^"']+)["']/i);
4265
+ const role = roleMatch ? roleMatch[1] : null;
4266
+
4267
+ if (role) {
4268
+ return `${tagName}[role="${role}"]`;
4269
+ }
4270
+
4271
+ // Special cases
4272
+ if (tagName === 'a' && /href\s*=/i.test(element)) {
4273
+ return 'link';
4274
+ }
4275
+
4276
+ if (tagName === 'input') {
4277
+ const typeMatch = element.match(/type\s*=\s*["']([^"']+)["']/i);
4278
+ const inputType = typeMatch ? typeMatch[1] : 'text';
4279
+ return `input[type="${inputType}"]`;
4280
+ }
4281
+
4282
+ return tagName;
4283
+ }
4284
+
4285
+ fixNestedInteractiveControlsInContent(content) {
4286
+ let fixed = content;
4287
+
4288
+ // Strategy 1: Remove role attributes from parent containers that have interactive children
4289
+ const issues = this.analyzeNestedInteractiveControls(content);
4290
+
4291
+ issues.forEach(issue => {
4292
+ // Try to fix by removing role from parent element
4293
+ const parentRoleMatch = issue.parentElement.match(/role\s*=\s*["'][^"']*["']/i);
4294
+ if (parentRoleMatch) {
4295
+ const parentWithoutRole = issue.parentElement.replace(/\s*role\s*=\s*["'][^"']*["']/i, '');
4296
+ fixed = fixed.replace(issue.parentElement, parentWithoutRole);
4297
+ console.log(chalk.yellow(` ๐ŸŽฏ Removed role attribute from parent element to fix nesting`));
4298
+ }
4299
+ });
4300
+
4301
+ // Strategy 2: Convert div[role="button"] containing links to regular div
4302
+ fixed = fixed.replace(/<div([^>]*role\s*=\s*["']button["'][^>]*)>([\s\S]*?)<\/div>/gi, (match, attributes, content) => {
4303
+ // Check if content contains interactive elements
4304
+ const hasInteractiveChildren = /<(?:a\s[^>]*href|button|input|select|textarea)[^>]*>/i.test(content);
4305
+
4306
+ if (hasInteractiveChildren) {
4307
+ // Remove role="button" and any button-related attributes
4308
+ const cleanAttributes = attributes
4309
+ .replace(/\s*role\s*=\s*["']button["']/i, '')
4310
+ .replace(/\s*tabindex\s*=\s*["'][^"']*["']/i, '')
4311
+ .replace(/\s*onclick\s*=\s*["'][^"']*["']/i, '');
4312
+
4313
+ console.log(chalk.yellow(` ๐ŸŽฏ Converted div[role="button"] to regular div due to interactive children`));
4314
+ return `<div${cleanAttributes}>${content}</div>`;
4315
+ }
4316
+
4317
+ return match;
4318
+ });
4319
+
4320
+ // Strategy 3: Remove tabindex from parent containers with interactive children
4321
+ fixed = fixed.replace(/(<[^>]+)(\s+tabindex\s*=\s*["'][^"']*["'])([^>]*>[\s\S]*?<(?:a\s[^>]*href|button|input|select|textarea)[^>]*>[\s\S]*?<\/[^>]+>)/gi, (match, beforeTabindex, tabindexAttr, afterTabindex) => {
4322
+ console.log(chalk.yellow(` ๐ŸŽฏ Removed tabindex from parent element with interactive children`));
4323
+ return beforeTabindex + afterTabindex;
4324
+ });
4325
+
4326
+ return fixed;
4327
+ }
4328
+
4329
+ async fixAllAccessibilityIssues(directory = '.') {
4330
+ console.log(chalk.blue('๐Ÿš€ Starting comprehensive accessibility fixes...'));
4331
+ console.log('');
4332
+
4333
+ const results = {
4334
+ totalFiles: 0,
4335
+ fixedFiles: 0,
4336
+ totalIssues: 0,
4337
+ steps: []
4338
+ };
4339
+
4340
+ try {
4341
+ // Step 1: HTML lang attributes
4342
+ console.log(chalk.blue('๐Ÿ“ Step 1: HTML lang attributes...'));
4343
+ const langResults = await this.fixHtmlLang(directory);
4344
+ const langFixed = langResults.filter(r => r.status === 'fixed').length;
4345
+ results.steps.push({ step: 1, name: 'HTML lang attributes', fixed: langFixed });
4346
+
4347
+ // Step 2: Alt attributes
4348
+ console.log(chalk.blue('๐Ÿ–ผ๏ธ Step 2: Alt attributes...'));
4349
+ const altResults = await this.fixEmptyAltAttributes(directory);
4350
+ const altFixed = altResults.filter(r => r.status === 'fixed').length;
4351
+ const totalAltIssues = altResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4352
+ results.steps.push({ step: 2, name: 'Alt attributes', fixed: altFixed, issues: totalAltIssues });
4353
+
4354
+ // Step 3: Role attributes
4355
+ console.log(chalk.blue('๐ŸŽญ Step 3: Role attributes...'));
4356
+ const roleResults = await this.fixRoleAttributes(directory);
4357
+ const roleFixed = roleResults.filter(r => r.status === 'fixed').length;
4358
+ const totalRoleIssues = roleResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4359
+ results.steps.push({ step: 3, name: 'Role attributes', fixed: roleFixed, issues: totalRoleIssues });
4360
+
4361
+ // Step 4: Form labels
4362
+ console.log(chalk.blue('๐Ÿ“‹ Step 4: Form labels...'));
4363
+ const formResults = await this.fixFormLabels(directory);
4364
+ const formFixed = formResults.filter(r => r.status === 'fixed').length;
4365
+ const totalFormIssues = formResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4366
+ results.steps.push({ step: 4, name: 'Form labels', fixed: formFixed, issues: totalFormIssues });
4367
+
4368
+ // Step 5: Nested interactive controls (NEW!)
4369
+ console.log(chalk.blue('๐ŸŽฏ Step 5: Nested interactive controls...'));
4370
+ const nestedResults = await this.fixNestedInteractiveControls(directory);
4371
+ const nestedFixed = nestedResults.filter(r => r.status === 'fixed').length;
4372
+ const totalNestedIssues = nestedResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4373
+ results.steps.push({ step: 5, name: 'Nested interactive controls', fixed: nestedFixed, issues: totalNestedIssues });
4374
+
4375
+ // Step 6: Button names
4376
+ console.log(chalk.blue('๐Ÿ”˜ Step 6: Button names...'));
4377
+ const buttonResults = await this.fixButtonNames(directory);
4378
+ const buttonFixed = buttonResults.filter(r => r.status === 'fixed').length;
4379
+ const totalButtonIssues = buttonResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4380
+ results.steps.push({ step: 6, name: 'Button names', fixed: buttonFixed, issues: totalButtonIssues });
4381
+
4382
+ // Step 7: Link names
4383
+ console.log(chalk.blue('๐Ÿ”— Step 7: Link names...'));
4384
+ const linkResults = await this.fixLinkNames(directory);
4385
+ const linkFixed = linkResults.filter(r => r.status === 'fixed').length;
4386
+ const totalLinkIssues = linkResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4387
+ results.steps.push({ step: 7, name: 'Link names', fixed: linkFixed, issues: totalLinkIssues });
4388
+
4389
+ // Step 8: Landmarks
4390
+ console.log(chalk.blue('๐Ÿ›๏ธ Step 8: Landmarks...'));
4391
+ const landmarkResults = await this.fixLandmarks(directory);
4392
+ const landmarkFixed = landmarkResults.filter(r => r.status === 'fixed').length;
4393
+ const totalLandmarkIssues = landmarkResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4394
+ results.steps.push({ step: 8, name: 'Landmarks', fixed: landmarkFixed, issues: totalLandmarkIssues });
4395
+
4396
+ // Step 9: Heading analysis
4397
+ console.log(chalk.blue('๐Ÿ“‘ Step 9: Heading analysis...'));
4398
+ const headingResults = await this.analyzeHeadings(directory);
4399
+ const totalHeadingSuggestions = headingResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4400
+ results.steps.push({ step: 9, name: 'Heading analysis', suggestions: totalHeadingSuggestions });
4401
+ console.log(chalk.gray('๐Ÿ’ก Heading issues require manual review and cannot be auto-fixed'));
4402
+
4403
+ // Step 10: Broken links check
4404
+ console.log(chalk.blue('๐Ÿ”— Step 10: Broken links check...'));
4405
+ const brokenLinksResults = await this.checkBrokenLinks(directory);
4406
+ const totalBrokenLinks = brokenLinksResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4407
+ results.steps.push({ step: 10, name: 'Broken links check', issues: totalBrokenLinks });
4408
+ console.log(chalk.gray('๐Ÿ’ก Broken link issues require manual review and cannot be auto-fixed'));
4409
+
4410
+ // Step 11: Cleanup duplicate roles
4411
+ console.log(chalk.blue('๐Ÿงน Step 11: Cleanup duplicate roles...'));
4412
+ const cleanupResults = await this.cleanupDuplicateRoles(directory);
4413
+ const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
4414
+ results.steps.push({ step: 11, name: 'Cleanup duplicate roles', fixed: cleanupFixed });
4415
+
4416
+ // Calculate totals
4417
+ results.totalFiles = Math.max(
4418
+ langResults.length, altResults.length, roleResults.length, formResults.length,
4419
+ nestedResults.length, buttonResults.length, linkResults.length, landmarkResults.length,
4420
+ headingResults.length, brokenLinksResults.length, cleanupResults.length
4421
+ );
4422
+
4423
+ results.fixedFiles = new Set([
4424
+ ...langResults.filter(r => r.status === 'fixed').map(r => r.file),
4425
+ ...altResults.filter(r => r.status === 'fixed').map(r => r.file),
4426
+ ...roleResults.filter(r => r.status === 'fixed').map(r => r.file),
4427
+ ...formResults.filter(r => r.status === 'fixed').map(r => r.file),
4428
+ ...nestedResults.filter(r => r.status === 'fixed').map(r => r.file),
4429
+ ...buttonResults.filter(r => r.status === 'fixed').map(r => r.file),
4430
+ ...linkResults.filter(r => r.status === 'fixed').map(r => r.file),
4431
+ ...landmarkResults.filter(r => r.status === 'fixed').map(r => r.file),
4432
+ ...cleanupResults.filter(r => r.status === 'fixed').map(r => r.file)
4433
+ ]).size;
4434
+
4435
+ results.totalIssues = totalAltIssues + totalRoleIssues + totalFormIssues + totalNestedIssues +
4436
+ totalButtonIssues + totalLinkIssues + totalLandmarkIssues;
4437
+
4438
+ // Final summary
4439
+ console.log(chalk.green('\n๐ŸŽ‰ All accessibility fixes completed!'));
4440
+ console.log(chalk.blue('๐Ÿ“Š Final Summary:'));
4441
+ console.log(chalk.blue(` Total files scanned: ${results.totalFiles}`));
4442
+ console.log(chalk.blue(` Files fixed: ${results.fixedFiles}`));
4443
+ console.log(chalk.blue(` Total issues resolved: ${results.totalIssues}`));
4444
+
4445
+ if (this.config.dryRun) {
4446
+ console.log(chalk.yellow('\n๐Ÿ’ก This was a dry run. Use without --dry-run to apply changes.'));
4447
+ }
4448
+
4449
+ return results;
4450
+
4451
+ } catch (error) {
4452
+ console.error(chalk.red(`โŒ Error during comprehensive fixes: ${error.message}`));
4453
+ throw error;
4454
+ }
4455
+ }
4456
+
4096
4457
  async findHtmlFiles(directory) {
4097
4458
  const files = [];
4098
4459
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbu-accessibility-package",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Comprehensive accessibility fixes for HTML files. Smart context-aware alt text generation, form labels, button names, link names, landmarks, heading analysis, and WCAG-compliant role attributes. Covers major axe DevTools issues with individual fix modes.",
5
5
  "main": "index.js",
6
6
  "bin": {