gbu-accessibility-package 3.8.0 → 3.8.2

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/lib/fixer.js CHANGED
@@ -1451,6 +1451,136 @@ class AccessibilityFixer {
1451
1451
  return results;
1452
1452
  }
1453
1453
 
1454
+ // Fix aria-labels for images and other elements
1455
+ async fixAriaLabels(directory = '.') {
1456
+ console.log(chalk.blue('šŸ·ļø Fixing aria-label attributes...'));
1457
+
1458
+ const htmlFiles = await this.findHtmlFiles(directory);
1459
+ const results = [];
1460
+ let totalIssuesFound = 0;
1461
+
1462
+ for (const file of htmlFiles) {
1463
+ try {
1464
+ const content = await fs.readFile(file, 'utf8');
1465
+ const fixed = this.fixAriaLabelsInContent(content);
1466
+
1467
+ if (fixed.content !== content) {
1468
+ if (!this.config.dryRun) {
1469
+ if (this.config.backupFiles) {
1470
+ await fs.writeFile(`${file}.backup`, content);
1471
+ }
1472
+ await fs.writeFile(file, fixed.content);
1473
+ }
1474
+
1475
+ console.log(chalk.green(`āœ… Fixed aria-label attributes in: ${file}`));
1476
+ totalIssuesFound += fixed.changes;
1477
+ }
1478
+
1479
+ results.push({ file, status: 'processed', changes: fixed.changes });
1480
+ } catch (error) {
1481
+ console.error(chalk.red(`āŒ Error processing ${file}: ${error.message}`));
1482
+ results.push({ file, status: 'error', error: error.message });
1483
+ }
1484
+ }
1485
+
1486
+ console.log(chalk.blue(`\nšŸ“Š Summary: Found ${totalIssuesFound} aria-label issues across ${results.length} files`));
1487
+ return results;
1488
+ }
1489
+
1490
+ fixAriaLabelsInContent(content) {
1491
+ let fixed = content;
1492
+ let changes = 0;
1493
+
1494
+ // Fix images - add aria-label from alt text
1495
+ fixed = fixed.replace(
1496
+ /<img([^>]*>)/gi,
1497
+ (match) => {
1498
+ // Check if aria-label already exists
1499
+ if (/aria-label\s*=/i.test(match)) {
1500
+ return match; // Return unchanged if aria-label already exists
1501
+ }
1502
+
1503
+ // Extract alt text to use for aria-label
1504
+ const altMatch = match.match(/alt\s*=\s*["']([^"']*)["']/i);
1505
+ if (altMatch && altMatch[1].trim()) {
1506
+ const altText = altMatch[1].trim();
1507
+ const updatedImg = match.replace(/(<img[^>]*?)(\s*>)/i, `$1 aria-label="${altText}"$2`);
1508
+ console.log(chalk.yellow(` šŸ·ļø Added aria-label="${altText}" to image element`));
1509
+ changes++;
1510
+ return updatedImg;
1511
+ }
1512
+
1513
+ return match;
1514
+ }
1515
+ );
1516
+
1517
+ // Fix buttons without aria-label but with text content
1518
+ fixed = fixed.replace(
1519
+ /<button([^>]*>)(.*?)<\/button>/gi,
1520
+ (match, attributes, content) => {
1521
+ // Skip if aria-label already exists
1522
+ if (/aria-label\s*=/i.test(attributes)) {
1523
+ return match;
1524
+ }
1525
+
1526
+ // Extract text content for aria-label
1527
+ const textContent = content.replace(/<[^>]*>/g, '').trim();
1528
+ if (textContent) {
1529
+ const updatedButton = match.replace(/(<button[^>]*?)(\s*>)/i, `$1 aria-label="${textContent}"$2`);
1530
+ console.log(chalk.yellow(` šŸ·ļø Added aria-label="${textContent}" to button element`));
1531
+ changes++;
1532
+ return updatedButton;
1533
+ }
1534
+
1535
+ return match;
1536
+ }
1537
+ );
1538
+
1539
+ // Fix links without aria-label but with text content
1540
+ fixed = fixed.replace(
1541
+ /<a([^>]*href[^>]*>)(.*?)<\/a>/gi,
1542
+ (match, attributes, content) => {
1543
+ // Skip if aria-label already exists
1544
+ if (/aria-label\s*=/i.test(attributes)) {
1545
+ return match;
1546
+ }
1547
+
1548
+ // Skip if it's just text content (not generic)
1549
+ const textContent = content.replace(/<[^>]*>/g, '').trim();
1550
+ const genericTexts = ['link', 'click here', 'read more', 'more info', 'here', 'this'];
1551
+
1552
+ if (textContent && genericTexts.some(generic =>
1553
+ textContent.toLowerCase().includes(generic.toLowerCase()))) {
1554
+ // Extract meaningful context or use href for generic links
1555
+ const hrefMatch = attributes.match(/href\s*=\s*["']([^"']*)["']/i);
1556
+ if (hrefMatch && hrefMatch[1]) {
1557
+ const href = hrefMatch[1];
1558
+ let ariaLabel = textContent;
1559
+
1560
+ // Improve generic link text
1561
+ if (href.includes('mailto:')) {
1562
+ ariaLabel = `Email: ${href.replace('mailto:', '')}`;
1563
+ } else if (href.includes('tel:')) {
1564
+ ariaLabel = `Phone: ${href.replace('tel:', '')}`;
1565
+ } else if (href.startsWith('http')) {
1566
+ const domain = new URL(href).hostname;
1567
+ ariaLabel = `Link to ${domain}`;
1568
+ }
1569
+
1570
+ const updatedLink = match.replace(/(<a[^>]*?)(\s*>)/i, `$1 aria-label="${ariaLabel}"$2`);
1571
+ console.log(chalk.yellow(` šŸ·ļø Added aria-label="${ariaLabel}" to link element`));
1572
+ changes++;
1573
+ return updatedLink;
1574
+ }
1575
+ }
1576
+
1577
+ return match;
1578
+ }
1579
+ );
1580
+
1581
+ return { content: fixed, changes };
1582
+ }
1583
+
1454
1584
  async addMainLandmarks(directory = '.') {
1455
1585
  console.log(chalk.yellow('šŸ—ļø Main landmark detection (manual review required)...'));
1456
1586
 
@@ -2014,33 +2144,18 @@ class AccessibilityFixer {
2014
2144
  // Fix picture elements with img children - move role from picture to img
2015
2145
  fixed = this.fixPictureImgRoles(fixed);
2016
2146
 
2017
- // Fix all images - add role="img" and aria-label
2147
+ // Fix all images - add role="img" only
2018
2148
  fixed = fixed.replace(
2019
2149
  /<img([^>]*>)/gi,
2020
2150
  (match) => {
2021
- let updatedImg = match;
2022
- let hasChanges = false;
2023
-
2024
2151
  // Check if role attribute already exists
2025
2152
  if (!/role\s*=/i.test(match)) {
2026
- updatedImg = updatedImg.replace(/(<img[^>]*?)(\s*>)/i, '$1 role="img"$2');
2153
+ const updatedImg = match.replace(/(<img[^>]*?)(\s*>)/i, '$1 role="img"$2');
2027
2154
  console.log(chalk.yellow(` šŸ–¼ļø Added role="img" to image element`));
2028
- hasChanges = true;
2029
- }
2030
-
2031
- // Check if aria-label already exists
2032
- if (!/aria-label\s*=/i.test(match)) {
2033
- // Extract alt text to use for aria-label
2034
- const altMatch = match.match(/alt\s*=\s*["']([^"']*)["']/i);
2035
- if (altMatch && altMatch[1].trim()) {
2036
- const altText = altMatch[1].trim();
2037
- updatedImg = updatedImg.replace(/(<img[^>]*?)(\s*>)/i, `$1 aria-label="${altText}"$2`);
2038
- console.log(chalk.yellow(` šŸ·ļø Added aria-label="${altText}" to image element`));
2039
- hasChanges = true;
2040
- }
2155
+ return updatedImg;
2041
2156
  }
2042
2157
 
2043
- return updatedImg;
2158
+ return match;
2044
2159
  }
2045
2160
  );
2046
2161
 
@@ -3106,13 +3221,14 @@ class AccessibilityFixer {
3106
3221
  async checkLocalFile(url, baseDir, resourceType, elementType) {
3107
3222
  const path = require('path');
3108
3223
 
3109
- // Handle relative URLs
3224
+ // Handle different URL types
3110
3225
  let filePath;
3111
3226
  if (url.startsWith('/')) {
3112
- // Absolute path from web root - we'll check relative to baseDir
3113
- filePath = path.join(baseDir, url.substring(1));
3227
+ // Absolute path from web root - find project root and resolve from there
3228
+ const projectRoot = this.findProjectRoot(baseDir);
3229
+ filePath = path.join(projectRoot, url.substring(1));
3114
3230
  } else {
3115
- // Relative path
3231
+ // Relative path - resolve relative to current HTML file directory
3116
3232
  filePath = path.resolve(baseDir, url);
3117
3233
  }
3118
3234
 
@@ -3126,11 +3242,41 @@ class AccessibilityFixer {
3126
3242
  suggestion: `Create the missing file or update the ${resourceType.toLowerCase()} path`,
3127
3243
  url: url,
3128
3244
  filePath: filePath,
3245
+ resolvedPath: filePath,
3129
3246
  resourceType: resourceType
3130
3247
  };
3131
3248
  }
3132
3249
  }
3133
3250
 
3251
+ // Helper method to find project root directory
3252
+ findProjectRoot(startDir) {
3253
+ const path = require('path');
3254
+ const fs = require('fs');
3255
+
3256
+ let currentDir = startDir;
3257
+ const root = path.parse(currentDir).root;
3258
+
3259
+ // Look for common project root indicators
3260
+ while (currentDir !== root) {
3261
+ // Check for package.json, .git, or other project indicators
3262
+ const packageJsonPath = path.join(currentDir, 'package.json');
3263
+ const gitPath = path.join(currentDir, '.git');
3264
+ const nodeModulesPath = path.join(currentDir, 'node_modules');
3265
+
3266
+ if (fs.existsSync(packageJsonPath) || fs.existsSync(gitPath) || fs.existsSync(nodeModulesPath)) {
3267
+ return currentDir;
3268
+ }
3269
+
3270
+ // Move up one directory
3271
+ const parentDir = path.dirname(currentDir);
3272
+ if (parentDir === currentDir) break; // Reached filesystem root
3273
+ currentDir = parentDir;
3274
+ }
3275
+
3276
+ // If no project root found, use the original baseDir (fallback)
3277
+ return startDir;
3278
+ }
3279
+
3134
3280
  // Analyze headings (no auto-fix, only suggestions)
3135
3281
  async analyzeHeadings(directory = '.') {
3136
3282
  console.log(chalk.blue('šŸ“‘ Analyzing heading structure...'));
@@ -4359,70 +4505,77 @@ class AccessibilityFixer {
4359
4505
  const totalRoleIssues = roleResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4360
4506
  results.steps.push({ step: 3, name: 'Role attributes', fixed: roleFixed, issues: totalRoleIssues });
4361
4507
 
4362
- // Step 4: Form labels
4363
- console.log(chalk.blue('šŸ“‹ Step 4: Form labels...'));
4508
+ // Step 4: Aria-label attributes
4509
+ console.log(chalk.blue('šŸ·ļø Step 4: Aria-label attributes...'));
4510
+ const ariaResults = await this.fixAriaLabels(directory);
4511
+ const ariaFixed = ariaResults.filter(r => r.status === 'processed' && r.changes > 0).length;
4512
+ const totalAriaIssues = ariaResults.reduce((sum, r) => sum + (r.changes || 0), 0);
4513
+ results.steps.push({ step: 4, name: 'Aria-label attributes', fixed: ariaFixed, issues: totalAriaIssues });
4514
+
4515
+ // Step 5: Form labels
4516
+ console.log(chalk.blue('šŸ“‹ Step 5: Form labels...'));
4364
4517
  const formResults = await this.fixFormLabels(directory);
4365
4518
  const formFixed = formResults.filter(r => r.status === 'fixed').length;
4366
4519
  const totalFormIssues = formResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4367
- results.steps.push({ step: 4, name: 'Form labels', fixed: formFixed, issues: totalFormIssues });
4520
+ results.steps.push({ step: 5, name: 'Form labels', fixed: formFixed, issues: totalFormIssues });
4368
4521
 
4369
- // Step 5: Nested interactive controls (NEW!)
4370
- console.log(chalk.blue('šŸŽÆ Step 5: Nested interactive controls...'));
4522
+ // Step 6: Nested interactive controls (NEW!)
4523
+ console.log(chalk.blue('šŸŽÆ Step 6: Nested interactive controls...'));
4371
4524
  const nestedResults = await this.fixNestedInteractiveControls(directory);
4372
4525
  const nestedFixed = nestedResults.filter(r => r.status === 'fixed').length;
4373
4526
  const totalNestedIssues = nestedResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4374
- results.steps.push({ step: 5, name: 'Nested interactive controls', fixed: nestedFixed, issues: totalNestedIssues });
4527
+ results.steps.push({ step: 6, name: 'Nested interactive controls', fixed: nestedFixed, issues: totalNestedIssues });
4375
4528
 
4376
- // Step 6: Button names
4377
- console.log(chalk.blue('šŸ”˜ Step 6: Button names...'));
4529
+ // Step 7: Button names
4530
+ console.log(chalk.blue('šŸ”˜ Step 7: Button names...'));
4378
4531
  const buttonResults = await this.fixButtonNames(directory);
4379
4532
  const buttonFixed = buttonResults.filter(r => r.status === 'fixed').length;
4380
4533
  const totalButtonIssues = buttonResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4381
- results.steps.push({ step: 6, name: 'Button names', fixed: buttonFixed, issues: totalButtonIssues });
4534
+ results.steps.push({ step: 7, name: 'Button names', fixed: buttonFixed, issues: totalButtonIssues });
4382
4535
 
4383
- // Step 7: Link names
4384
- console.log(chalk.blue('šŸ”— Step 7: Link names...'));
4536
+ // Step 8: Link names
4537
+ console.log(chalk.blue('šŸ”— Step 8: Link names...'));
4385
4538
  const linkResults = await this.fixLinkNames(directory);
4386
4539
  const linkFixed = linkResults.filter(r => r.status === 'fixed').length;
4387
4540
  const totalLinkIssues = linkResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4388
- results.steps.push({ step: 7, name: 'Link names', fixed: linkFixed, issues: totalLinkIssues });
4541
+ results.steps.push({ step: 8, name: 'Link names', fixed: linkFixed, issues: totalLinkIssues });
4389
4542
 
4390
- // Step 8: Landmarks
4391
- console.log(chalk.blue('šŸ›ļø Step 8: Landmarks...'));
4543
+ // Step 9: Landmarks
4544
+ console.log(chalk.blue('šŸ›ļø Step 9: Landmarks...'));
4392
4545
  const landmarkResults = await this.fixLandmarks(directory);
4393
4546
  const landmarkFixed = landmarkResults.filter(r => r.status === 'fixed').length;
4394
4547
  const totalLandmarkIssues = landmarkResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4395
- results.steps.push({ step: 8, name: 'Landmarks', fixed: landmarkFixed, issues: totalLandmarkIssues });
4548
+ results.steps.push({ step: 9, name: 'Landmarks', fixed: landmarkFixed, issues: totalLandmarkIssues });
4396
4549
 
4397
- // Step 9: Heading analysis
4398
- console.log(chalk.blue('šŸ“‘ Step 9: Heading analysis...'));
4550
+ // Step 10: Heading analysis
4551
+ console.log(chalk.blue('šŸ“‘ Step 10: Heading analysis...'));
4399
4552
  const headingResults = await this.analyzeHeadings(directory);
4400
4553
  const totalHeadingSuggestions = headingResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4401
- results.steps.push({ step: 9, name: 'Heading analysis', suggestions: totalHeadingSuggestions });
4554
+ results.steps.push({ step: 10, name: 'Heading analysis', suggestions: totalHeadingSuggestions });
4402
4555
  console.log(chalk.gray('šŸ’” Heading issues require manual review and cannot be auto-fixed'));
4403
4556
 
4404
- // Step 10: Broken links and missing resources check
4405
- console.log(chalk.blue('šŸ”— Step 10a: External links check...'));
4557
+ // Step 11: Broken links and missing resources check
4558
+ console.log(chalk.blue('šŸ”— Step 11a: External links check...'));
4406
4559
  const brokenLinksResults = await this.checkBrokenLinks(directory);
4407
4560
  const totalBrokenLinks = brokenLinksResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4408
- results.steps.push({ step: '10a', name: 'External links check', issues: totalBrokenLinks });
4561
+ results.steps.push({ step: '11a', name: 'External links check', issues: totalBrokenLinks });
4409
4562
 
4410
- console.log(chalk.blue('šŸ“ Step 10b: Missing resources check...'));
4563
+ console.log(chalk.blue('šŸ“ Step 11b: Missing resources check...'));
4411
4564
  const missingResourcesResults = await this.check404Resources(directory);
4412
4565
  const totalMissingResources = missingResourcesResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4413
- results.steps.push({ step: '10b', name: 'Missing resources check', issues: totalMissingResources });
4566
+ results.steps.push({ step: '11b', name: 'Missing resources check', issues: totalMissingResources });
4414
4567
 
4415
4568
  console.log(chalk.gray('šŸ’” Link and resource issues require manual review and cannot be auto-fixed'));
4416
4569
 
4417
- // Step 11: Cleanup duplicate roles
4418
- console.log(chalk.blue('🧹 Step 11: Cleanup duplicate roles...'));
4570
+ // Step 12: Cleanup duplicate roles
4571
+ console.log(chalk.blue('🧹 Step 12: Cleanup duplicate roles...'));
4419
4572
  const cleanupResults = await this.cleanupDuplicateRoles(directory);
4420
4573
  const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
4421
- results.steps.push({ step: 11, name: 'Cleanup duplicate roles', fixed: cleanupFixed });
4574
+ results.steps.push({ step: 12, name: 'Cleanup duplicate roles', fixed: cleanupFixed });
4422
4575
 
4423
4576
  // Calculate totals
4424
4577
  results.totalFiles = Math.max(
4425
- langResults.length, altResults.length, roleResults.length, formResults.length,
4578
+ langResults.length, altResults.length, roleResults.length, ariaResults.length, formResults.length,
4426
4579
  nestedResults.length, buttonResults.length, linkResults.length, landmarkResults.length,
4427
4580
  headingResults.length, brokenLinksResults.length, cleanupResults.length
4428
4581
  );
@@ -5500,49 +5653,117 @@ class AccessibilityFixer {
5500
5653
  return files;
5501
5654
  }
5502
5655
 
5503
- // Check for unused files in the project
5656
+ // Check for unused files in the project - enhanced for comprehensive project-wide scanning
5504
5657
  async checkUnusedFiles(directory = '.') {
5505
- console.log(chalk.blue('šŸ—‚ļø Checking for unused files...'));
5658
+ console.log(chalk.blue('šŸ—‚ļø Analyzing unused files across entire project...'));
5506
5659
 
5507
- const results = [];
5508
- const allFiles = await this.findAllProjectFiles(directory);
5509
- const referencedFiles = await this.findReferencedFiles(directory);
5660
+ const startTime = Date.now();
5510
5661
 
5511
- // Normalize paths for comparison
5512
- const normalizedReferenced = new Set();
5513
- referencedFiles.forEach(file => {
5514
- normalizedReferenced.add(path.resolve(file));
5515
- });
5662
+ // Find project root for comprehensive scanning
5663
+ const projectRoot = this.findProjectRoot(directory);
5664
+ console.log(chalk.gray(`šŸ“ Project root: ${path.relative(process.cwd(), projectRoot) || '.'}`));
5665
+
5666
+ // Get all project files from root (comprehensive scan)
5667
+ const allFiles = await this.findAllProjectFiles(projectRoot);
5668
+ console.log(chalk.gray(`šŸ“Š Found ${allFiles.length} total files in project`));
5669
+
5670
+ // Get referenced files from entire project (not just target directory)
5671
+ const referencedFiles = await this.findReferencedFiles(projectRoot);
5672
+ console.log(chalk.gray(`šŸ” Found ${referencedFiles.size} referenced files`));
5673
+
5674
+ // Find unused files
5675
+ const unusedFiles = [];
5516
5676
 
5517
5677
  for (const file of allFiles) {
5518
- const absolutePath = path.resolve(file);
5519
-
5520
- // Skip certain files that are typically not referenced directly
5678
+ // Skip certain directories and files that shouldn't be considered "unused"
5521
5679
  if (this.shouldSkipUnusedCheck(file)) {
5522
5680
  continue;
5523
5681
  }
5524
5682
 
5525
- if (!normalizedReferenced.has(absolutePath)) {
5526
- const relativePath = path.relative(directory, file);
5683
+ // Check if file is referenced anywhere in the project
5684
+ const relativePath = path.relative(projectRoot, file);
5685
+ const isReferenced = this.isFileReferenced(file, relativePath, referencedFiles, projectRoot);
5686
+
5687
+ if (!isReferenced) {
5688
+ const stats = await require('fs').promises.stat(file);
5689
+ const fileSize = this.formatFileSize(stats.size);
5527
5690
  const fileType = this.getFileType(file);
5528
5691
 
5529
- console.log(chalk.yellow(` šŸ—‘ļø Unused ${fileType}: ${relativePath}`));
5530
-
5531
- results.push({
5532
- type: `šŸ—‘ļø Unused ${fileType}`,
5533
- description: `File not referenced anywhere: ${relativePath}`,
5534
- suggestion: `Consider removing if truly unused: ${relativePath}`,
5535
- filePath: file,
5536
- fileType: fileType,
5537
- relativePath: relativePath
5692
+ unusedFiles.push({
5693
+ path: file,
5694
+ relativePath: relativePath,
5695
+ size: stats.size,
5696
+ formattedSize: fileSize,
5697
+ type: fileType,
5698
+ description: `File not referenced anywhere in project: ${relativePath}`,
5699
+ suggestion: `Consider removing if truly unused: ${relativePath}`
5538
5700
  });
5539
5701
  }
5540
5702
  }
5541
5703
 
5542
- console.log(chalk.blue(`\nšŸ“Š Summary: Found ${results.length} potentially unused files`));
5543
- console.log(chalk.gray('šŸ’” Review carefully before removing - some files may be referenced dynamically'));
5704
+ // Sort by size (largest first)
5705
+ unusedFiles.sort((a, b) => b.size - a.size);
5544
5706
 
5545
- return results;
5707
+ // Display results
5708
+ if (unusedFiles.length === 0) {
5709
+ console.log(chalk.green('āœ… No unused files found! All files are properly referenced.'));
5710
+ } else {
5711
+ console.log(chalk.yellow(`\nšŸ“‹ Found ${unusedFiles.length} potentially unused files:`));
5712
+
5713
+ unusedFiles.forEach((file, index) => {
5714
+ const icon = this.getFileIcon(file.type);
5715
+ console.log(chalk.yellow(` ${index + 1}. ${icon} ${file.relativePath} (${file.formattedSize})`));
5716
+ });
5717
+
5718
+ const totalSize = unusedFiles.reduce((sum, file) => sum + file.size, 0);
5719
+ console.log(chalk.blue(`\nšŸ“Š Total unused file size: ${this.formatFileSize(totalSize)}`));
5720
+ console.log(chalk.gray('šŸ’” Review these files before deleting - some may be used dynamically or required for deployment'));
5721
+ }
5722
+
5723
+ const endTime = Date.now();
5724
+ console.log(chalk.gray(`ā±ļø Analysis completed in ${endTime - startTime}ms`));
5725
+
5726
+ return {
5727
+ unusedFiles: unusedFiles,
5728
+ totalFiles: allFiles.length,
5729
+ referencedFiles: referencedFiles.size,
5730
+ unusedCount: unusedFiles.length,
5731
+ totalUnusedSize: unusedFiles.reduce((sum, file) => sum + file.size, 0)
5732
+ };
5733
+ }
5734
+
5735
+ // Enhanced file reference checking
5736
+ isFileReferenced(filePath, relativePath, referencedFiles, projectRoot) {
5737
+ // Check various possible reference formats
5738
+ const possibleRefs = [
5739
+ relativePath, // relative/to/file.ext
5740
+ '/' + relativePath, // /relative/to/file.ext
5741
+ './' + relativePath, // ./relative/to/file.ext
5742
+ '../' + relativePath, // ../relative/to/file.ext
5743
+ path.basename(filePath), // file.ext
5744
+ '/' + path.basename(filePath), // /file.ext
5745
+ relativePath.replace(/\\/g, '/'), // normalize windows paths
5746
+ '/' + relativePath.replace(/\\/g, '/'), // /normalized/path
5747
+ relativePath.replace(/^\.\//, ''), // remove leading ./
5748
+ relativePath.replace(/^\//, ''), // remove leading /
5749
+ ];
5750
+
5751
+ // Check if any reference format exists
5752
+ for (const ref of possibleRefs) {
5753
+ if (referencedFiles.has(ref)) {
5754
+ return true;
5755
+ }
5756
+ }
5757
+
5758
+ // Check for partial matches (for dynamic imports)
5759
+ const fileName = path.basename(filePath, path.extname(filePath));
5760
+ for (const ref of referencedFiles) {
5761
+ if (ref.includes(fileName) || ref.includes(relativePath)) {
5762
+ return true;
5763
+ }
5764
+ }
5765
+
5766
+ return false;
5546
5767
  }
5547
5768
 
5548
5769
  async findAllProjectFiles(directory) {
@@ -5577,46 +5798,103 @@ class AccessibilityFixer {
5577
5798
  return files;
5578
5799
  }
5579
5800
 
5801
+ // Enhanced reference finding across entire project
5580
5802
  async findReferencedFiles(directory) {
5581
- const referenced = new Set();
5582
- const htmlFiles = await this.findHtmlFiles(directory);
5583
- const cssFiles = await this.findCssFiles(directory);
5584
- const jsFiles = await this.findJsFiles(directory);
5803
+ const referencedFiles = new Set();
5585
5804
 
5586
- // Find references in HTML files
5587
- for (const htmlFile of htmlFiles) {
5588
- try {
5589
- const content = await fs.readFile(htmlFile, 'utf8');
5590
- const refs = this.extractFileReferences(content, path.dirname(htmlFile));
5591
- refs.forEach(ref => referenced.add(ref));
5592
- } catch (error) {
5593
- // Skip files we can't read
5594
- }
5595
- }
5805
+ // Find all source files that could contain references
5806
+ const sourceFiles = await this.findSourceFiles(directory);
5807
+ console.log(chalk.gray(`šŸ” Scanning ${sourceFiles.length} source files for references...`));
5596
5808
 
5597
- // Find references in CSS files
5598
- for (const cssFile of cssFiles) {
5809
+ for (const sourceFile of sourceFiles) {
5599
5810
  try {
5600
- const content = await fs.readFile(cssFile, 'utf8');
5601
- const refs = this.extractCssReferences(content, path.dirname(cssFile));
5602
- refs.forEach(ref => referenced.add(ref));
5811
+ const content = await fs.readFile(sourceFile, 'utf-8');
5812
+ const baseDir = path.dirname(sourceFile);
5813
+
5814
+ // Extract references based on file type
5815
+ const refs = this.extractAllReferences(content, baseDir, sourceFile);
5816
+ refs.forEach(ref => referencedFiles.add(ref));
5817
+
5603
5818
  } catch (error) {
5604
- // Skip files we can't read
5819
+ console.log(chalk.gray(`āš ļø Could not read ${sourceFile}: ${error.message}`));
5605
5820
  }
5606
5821
  }
5607
5822
 
5608
- // Find references in JS files
5609
- for (const jsFile of jsFiles) {
5823
+ return referencedFiles;
5824
+ }
5825
+
5826
+ // Find all source files that could contain references
5827
+ async findSourceFiles(directory) {
5828
+ const sourceFiles = [];
5829
+
5830
+ const walk = async (dir) => {
5610
5831
  try {
5611
- const content = await fs.readFile(jsFile, 'utf8');
5612
- const refs = this.extractJsReferences(content, path.dirname(jsFile));
5613
- refs.forEach(ref => referenced.add(ref));
5832
+ const files = await fs.readdir(dir);
5833
+
5834
+ for (const file of files) {
5835
+ const filePath = path.join(dir, file);
5836
+ const stat = await fs.stat(filePath);
5837
+
5838
+ if (stat.isDirectory()) {
5839
+ if (!this.shouldSkipDirectory(file)) {
5840
+ await walk(filePath);
5841
+ }
5842
+ } else {
5843
+ // Include files that could contain references
5844
+ const ext = path.extname(file).toLowerCase();
5845
+ if (['.html', '.htm', '.css', '.js', '.jsx', '.ts', '.tsx',
5846
+ '.vue', '.php', '.json', '.md', '.xml', '.svg'].includes(ext)) {
5847
+ sourceFiles.push(filePath);
5848
+ }
5849
+ }
5850
+ }
5614
5851
  } catch (error) {
5615
- // Skip files we can't read
5852
+ console.log(chalk.gray(`āš ļø Could not read directory ${dir}: ${error.message}`));
5616
5853
  }
5854
+ };
5855
+
5856
+ await walk(directory);
5857
+ return sourceFiles;
5858
+ }
5859
+
5860
+ // Enhanced reference extraction
5861
+ extractAllReferences(content, baseDir, sourceFile) {
5862
+ const references = new Set();
5863
+
5864
+ // Get file extension to determine extraction method
5865
+ const ext = path.extname(sourceFile).toLowerCase();
5866
+
5867
+ if (['.html', '.htm'].includes(ext)) {
5868
+ // HTML references
5869
+ const htmlRefs = this.extractFileReferences(content, baseDir);
5870
+ htmlRefs.forEach(ref => references.add(ref));
5871
+
5872
+ } else if (ext === '.css') {
5873
+ // CSS references
5874
+ const cssRefs = this.extractCssReferences(content, baseDir);
5875
+ cssRefs.forEach(ref => references.add(ref));
5876
+
5877
+ } else if (['.js', '.jsx', '.ts', '.tsx'].includes(ext)) {
5878
+ // JavaScript/TypeScript references
5879
+ const jsRefs = this.extractJsReferences(content, baseDir);
5880
+ jsRefs.forEach(ref => references.add(ref));
5881
+
5882
+ // Also extract import statements
5883
+ const importRefs = this.extractImportReferences(content, baseDir);
5884
+ importRefs.forEach(ref => references.add(ref));
5885
+
5886
+ } else if (ext === '.json') {
5887
+ // JSON references (like package.json, config files)
5888
+ const jsonRefs = this.extractJsonReferences(content, baseDir);
5889
+ jsonRefs.forEach(ref => references.add(ref));
5890
+
5891
+ } else {
5892
+ // Generic file references (for .md, .xml, etc.)
5893
+ const genericRefs = this.extractGenericReferences(content, baseDir);
5894
+ genericRefs.forEach(ref => references.add(ref));
5617
5895
  }
5618
5896
 
5619
- return Array.from(referenced);
5897
+ return Array.from(references);
5620
5898
  }
5621
5899
 
5622
5900
  extractFileReferences(content, baseDir) {
@@ -6461,6 +6739,128 @@ class AccessibilityFixer {
6461
6739
 
6462
6740
  return sortedBreakdown;
6463
6741
  }
6742
+
6743
+ // Extract import/require statements
6744
+ extractImportReferences(content, baseDir) {
6745
+ const references = [];
6746
+
6747
+ // ES6 imports and CommonJS requires
6748
+ const importPatterns = [
6749
+ /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g,
6750
+ /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
6751
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
6752
+ /import\s+['"]([^'"]+)['"]/g
6753
+ ];
6754
+
6755
+ importPatterns.forEach(pattern => {
6756
+ let match;
6757
+ while ((match = pattern.exec(content)) !== null) {
6758
+ const importPath = match[1];
6759
+
6760
+ // Skip npm packages (don't start with . or /)
6761
+ if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
6762
+ continue;
6763
+ }
6764
+
6765
+ // Resolve the import path
6766
+ const resolved = this.resolveImportPath(importPath, baseDir);
6767
+ if (resolved) {
6768
+ references.push(resolved);
6769
+ }
6770
+ }
6771
+ });
6772
+
6773
+ return references;
6774
+ }
6775
+
6776
+ // Extract JSON references
6777
+ extractJsonReferences(content, baseDir) {
6778
+ const references = [];
6779
+
6780
+ try {
6781
+ const json = JSON.parse(content);
6782
+
6783
+ // Extract file references from JSON values
6784
+ const extractFromObject = (obj) => {
6785
+ if (typeof obj === 'string') {
6786
+ // Check if it looks like a file path
6787
+ if (obj.includes('.') && (obj.includes('/') || obj.includes('\\'))) {
6788
+ const resolved = this.resolveFilePath(obj, baseDir);
6789
+ if (resolved) {
6790
+ references.push(resolved);
6791
+ }
6792
+ }
6793
+ } else if (typeof obj === 'object' && obj !== null) {
6794
+ Object.values(obj).forEach(value => {
6795
+ if (Array.isArray(value)) {
6796
+ value.forEach(extractFromObject);
6797
+ } else {
6798
+ extractFromObject(value);
6799
+ }
6800
+ });
6801
+ }
6802
+ };
6803
+
6804
+ extractFromObject(json);
6805
+ } catch (error) {
6806
+ // Invalid JSON, skip
6807
+ }
6808
+
6809
+ return references;
6810
+ }
6811
+
6812
+ // Extract generic file references
6813
+ extractGenericReferences(content, baseDir) {
6814
+ const references = [];
6815
+
6816
+ // Look for file-like patterns
6817
+ const patterns = [
6818
+ /['"]((?:\.{1,2}\/)?[^'"]*\.[a-zA-Z0-9]{1,5})['"]/g, // Quoted file paths
6819
+ /src\s*=\s*['"']([^'"']+)['"']/g, // src attributes
6820
+ /href\s*=\s*['"']([^'"']+)['"']/g, // href attributes
6821
+ /url\s*\(\s*['"']?([^'"')]+)['"']?\s*\)/g // CSS url()
6822
+ ];
6823
+
6824
+ patterns.forEach(pattern => {
6825
+ let match;
6826
+ while ((match = pattern.exec(content)) !== null) {
6827
+ const filePath = match[1];
6828
+
6829
+ if (this.isLocalFile(filePath)) {
6830
+ const resolved = this.resolveFilePath(filePath, baseDir);
6831
+ if (resolved) {
6832
+ references.push(resolved);
6833
+ }
6834
+ }
6835
+ }
6836
+ });
6837
+
6838
+ return references;
6839
+ }
6840
+
6841
+ // Resolve import paths (handle extensions and index files)
6842
+ resolveImportPath(importPath, baseDir) {
6843
+ const possiblePaths = [
6844
+ importPath,
6845
+ importPath + '.js',
6846
+ importPath + '.jsx',
6847
+ importPath + '.ts',
6848
+ importPath + '.tsx',
6849
+ importPath + '/index.js',
6850
+ importPath + '/index.jsx',
6851
+ importPath + '/index.ts',
6852
+ importPath + '/index.tsx'
6853
+ ];
6854
+
6855
+ for (const possiblePath of possiblePaths) {
6856
+ const resolved = this.resolveFilePath(possiblePath, baseDir);
6857
+ if (resolved) {
6858
+ return resolved;
6859
+ }
6860
+ }
6861
+
6862
+ return null;
6863
+ }
6464
6864
  }
6465
6865
 
6466
6866
  module.exports = AccessibilityFixer;