gbu-accessibility-package 3.8.8 → 3.9.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/lib/fixer.js CHANGED
@@ -1260,7 +1260,7 @@ class AccessibilityFixer {
1260
1260
  }
1261
1261
 
1262
1262
  async fixHtmlLang(directory = '.') {
1263
- console.log(chalk.blue('📝 Fixing HTML lang attributes...'));
1263
+ console.log(chalk.blue('📝 Đang sửa thuộc tính HTML lang...'));
1264
1264
 
1265
1265
  const htmlFiles = await this.findHtmlFiles(directory);
1266
1266
  const results = [];
@@ -1294,7 +1294,7 @@ class AccessibilityFixer {
1294
1294
  }
1295
1295
 
1296
1296
  async fixEmptyAltAttributes(directory = '.') {
1297
- console.log(chalk.blue('🖼️ Fixing empty alt attributes...'));
1297
+ console.log(chalk.blue('🖼️ Đang sửa thuộc tính alt rỗng...'));
1298
1298
 
1299
1299
  const htmlFiles = await this.findHtmlFiles(directory);
1300
1300
  const results = [];
@@ -1369,7 +1369,7 @@ class AccessibilityFixer {
1369
1369
  }
1370
1370
  }
1371
1371
 
1372
- console.log(chalk.blue(`\n📊 Summary: Found ${totalIssuesFound} alt attribute issues across ${results.length} files`));
1372
+ console.log(chalk.blue(`\n📊 Tóm tắt: Tìm thấy ${totalIssuesFound} vấn đề thuộc tính alt trong ${results.length} file`));
1373
1373
  if (this.config.enhancedAltMode) {
1374
1374
  console.log(chalk.gray(` 🔍 Enhanced analysis mode: Comprehensive quality checking enabled`));
1375
1375
  }
@@ -1406,7 +1406,7 @@ class AccessibilityFixer {
1406
1406
  }
1407
1407
 
1408
1408
  async fixRoleAttributes(directory = '.') {
1409
- console.log(chalk.blue('🎭 Fixing role attributes...'));
1409
+ console.log(chalk.blue('🎭 Đang sửa thuộc tính role...'));
1410
1410
 
1411
1411
  const htmlFiles = await this.findHtmlFiles(directory);
1412
1412
  const results = [];
@@ -1447,7 +1447,7 @@ class AccessibilityFixer {
1447
1447
  }
1448
1448
  }
1449
1449
 
1450
- console.log(chalk.blue(`\n📊 Summary: Found ${totalIssuesFound} role attribute issues across ${results.length} files`));
1450
+ console.log(chalk.blue(`\n📊 Tóm tắt: Tìm thấy ${totalIssuesFound} vấn đề thuộc tính role trong ${results.length} file`));
1451
1451
  return results;
1452
1452
  }
1453
1453
 
@@ -1483,7 +1483,7 @@ class AccessibilityFixer {
1483
1483
  }
1484
1484
  }
1485
1485
 
1486
- console.log(chalk.blue(`\n📊 Summary: Found ${totalIssuesFound} aria-label issues across ${results.length} files`));
1486
+ console.log(chalk.blue(`\n📊 Tóm tắt: Tìm thấy ${totalIssuesFound} vấn đề aria-label trong ${results.length} file`));
1487
1487
  return results;
1488
1488
  }
1489
1489
 
@@ -2275,7 +2275,7 @@ class AccessibilityFixer {
2275
2275
  }
2276
2276
 
2277
2277
  async cleanupDuplicateRoles(directory = '.') {
2278
- console.log(chalk.blue('🧹 Cleaning up duplicate role attributes...'));
2278
+ console.log(chalk.blue('🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
2279
2279
 
2280
2280
  const htmlFiles = await this.findHtmlFiles(directory);
2281
2281
  const results = [];
@@ -2307,7 +2307,7 @@ class AccessibilityFixer {
2307
2307
  }
2308
2308
  }
2309
2309
 
2310
- console.log(chalk.blue(`\n📊 Summary: Cleaned duplicate roles in ${totalFixedFiles} files`));
2310
+ console.log(chalk.blue(`\n📊 Tóm tắt: Đã dọn dẹp role trùng lặp trong ${totalFixedFiles} file`));
2311
2311
  return results;
2312
2312
  }
2313
2313
 
@@ -2341,7 +2341,7 @@ class AccessibilityFixer {
2341
2341
  }
2342
2342
 
2343
2343
  async fixAllAccessibilityIssues(directory = '.') {
2344
- console.log(chalk.blue('🚀 Starting comprehensive accessibility fixes...'));
2344
+ console.log(chalk.blue('🚀 Đang bắt đầu sửa lỗi accessibility toàn diện...'));
2345
2345
 
2346
2346
  const results = {
2347
2347
  lang: [],
@@ -2358,46 +2358,46 @@ class AccessibilityFixer {
2358
2358
 
2359
2359
  try {
2360
2360
  // Step 1: Fix lang attributes
2361
- console.log(chalk.yellow('\n📝 Step 1: HTML lang attributes...'));
2361
+ console.log(chalk.yellow('\n📝 Bước 1: Thuộc tính HTML lang...'));
2362
2362
  results.lang = await this.fixHtmlLang(directory);
2363
2363
 
2364
2364
  // Step 2: Fix alt attributes
2365
- console.log(chalk.yellow('\n🖼️ Step 2: Alt attributes...'));
2365
+ console.log(chalk.yellow('\n🖼️ Bước 2: Thuộc tính alt...'));
2366
2366
  results.alt = await this.fixEmptyAltAttributes(directory);
2367
2367
 
2368
2368
  // Step 3: Fix role attributes
2369
- console.log(chalk.yellow('\n🎭 Step 3: Role attributes...'));
2369
+ console.log(chalk.yellow('\n🎭 Bước 3: Thuộc tính role...'));
2370
2370
  results.roles = await this.fixRoleAttributes(directory);
2371
2371
 
2372
2372
  // Step 4: Fix form labels
2373
- console.log(chalk.yellow('\n📋 Step 4: Form labels...'));
2373
+ console.log(chalk.yellow('\n📋 Bước 4: Nhãn form...'));
2374
2374
  results.forms = await this.fixFormLabels(directory);
2375
2375
 
2376
2376
  // Step 5: Fix button names
2377
- console.log(chalk.yellow('\n🔘 Step 5: Button names...'));
2377
+ console.log(chalk.yellow('\n🔘 Bước 5: Tên button...'));
2378
2378
  results.buttons = await this.fixButtonNames(directory);
2379
2379
 
2380
2380
  // Step 6: Fix link names
2381
- console.log(chalk.yellow('\n🔗 Step 6: Link names...'));
2381
+ console.log(chalk.yellow('\n🔗 Bước 6: Tên link...'));
2382
2382
  results.links = await this.fixLinkNames(directory);
2383
2383
 
2384
2384
  // Step 7: Fix landmarks
2385
- console.log(chalk.yellow('\n🏛️ Step 7: Landmarks...'));
2385
+ console.log(chalk.yellow('\n🏛️ Bước 7: Landmark...'));
2386
2386
  results.landmarks = await this.fixLandmarks(directory);
2387
2387
 
2388
2388
  // Step 8: Analyze headings (no auto-fix)
2389
- console.log(chalk.yellow('\n📑 Step 8: Heading analysis...'));
2389
+ console.log(chalk.yellow('\n📑 Bước 8: Phân tích heading...'));
2390
2390
  results.headings = await this.analyzeHeadings(directory);
2391
2391
 
2392
2392
  // Step 9: Check broken links and missing resources (no auto-fix)
2393
- console.log(chalk.yellow('\n🔗 Step 9: External links check...'));
2393
+ console.log(chalk.yellow('\n🔗 Bước 9: Kiểm tra link bên ngoài...'));
2394
2394
  results.brokenLinks = await this.checkBrokenLinks(directory);
2395
2395
 
2396
- console.log(chalk.yellow('\n📁 Step 9b: Missing resources check...'));
2396
+ console.log(chalk.yellow('\n📁 Bước 9b: Kiểm tra tài nguyên thiếu...'));
2397
2397
  results.missingResources = await this.check404Resources(directory);
2398
2398
 
2399
2399
  // Step 10: Cleanup duplicate roles
2400
- console.log(chalk.yellow('\n🧹 Step 10: Cleanup duplicate roles...'));
2400
+ console.log(chalk.yellow('\n🧹 Bước 10: Dọn dẹp role trùng lặp...'));
2401
2401
  results.cleanup = await this.cleanupDuplicateRoles(directory);
2402
2402
 
2403
2403
  // Summary
@@ -2433,27 +2433,27 @@ class AccessibilityFixer {
2433
2433
  results.landmarks.reduce((sum, r) => sum + (r.issues || 0), 0) +
2434
2434
  results.cleanup.filter(r => r.status === 'fixed').length;
2435
2435
 
2436
- console.log(chalk.green('\n🎉 All accessibility fixes completed!'));
2437
- console.log(chalk.blue('📊 Final Summary:'));
2438
- console.log(chalk.white(` Total files scanned: ${totalFiles}`));
2439
- console.log(chalk.green(` Files fixed: ${totalFixed}`));
2440
- console.log(chalk.yellow(` Total issues resolved: ${totalIssues}`));
2436
+ console.log(chalk.green('\n🎉 Hoàn tất tất cả các sửa lỗi accessibility!'));
2437
+ console.log(chalk.blue('📊 Tóm tắt cuối cùng:'));
2438
+ console.log(chalk.white(` Tổng file đã quét: ${totalFiles}`));
2439
+ console.log(chalk.green(` File đã sửa: ${totalFixed}`));
2440
+ console.log(chalk.yellow(` Tổng vấn đề đã giải quyết: ${totalIssues}`));
2441
2441
 
2442
2442
  if (this.config.dryRun) {
2443
- console.log(chalk.cyan('\n💡 This was a dry run. Use without --dry-run to apply changes.'));
2443
+ console.log(chalk.cyan('\n💡 Đây chế độ xem trước. Sử dụng không có --dry-run để áp dụng thay đổi.'));
2444
2444
  }
2445
2445
 
2446
2446
  return results;
2447
2447
 
2448
2448
  } catch (error) {
2449
- console.error(chalk.red('❌ Error during comprehensive fix:'), error.message);
2449
+ console.error(chalk.red('❌ Lỗi trong quá trình sửa toàn diện:'), error.message);
2450
2450
  throw error;
2451
2451
  }
2452
2452
  }
2453
2453
 
2454
2454
  // Fix form labels
2455
2455
  async fixFormLabels(directory = '.') {
2456
- console.log(chalk.blue('📋 Fixing form labels...'));
2456
+ console.log(chalk.blue('📋 Đang sửa nhãn form...'));
2457
2457
 
2458
2458
  const htmlFiles = await this.findHtmlFiles(directory);
2459
2459
  const results = [];
@@ -2494,7 +2494,7 @@ class AccessibilityFixer {
2494
2494
  }
2495
2495
  }
2496
2496
 
2497
- console.log(chalk.blue(`\n📊 Summary: Found ${totalIssuesFound} form label issues across ${results.length} files`));
2497
+ console.log(chalk.blue(`\n📊 Tóm tắt: Tìm thấy ${totalIssuesFound} vấn đề nhãn form trong ${results.length} file`));
2498
2498
  return results;
2499
2499
  }
2500
2500
 
@@ -2608,7 +2608,7 @@ class AccessibilityFixer {
2608
2608
 
2609
2609
  // Fix button names
2610
2610
  async fixButtonNames(directory = '.') {
2611
- console.log(chalk.blue('🔘 Fixing button names...'));
2611
+ console.log(chalk.blue('🔘 Đang sửa tên button...'));
2612
2612
 
2613
2613
  const htmlFiles = await this.findHtmlFiles(directory);
2614
2614
  const results = [];
@@ -2649,7 +2649,7 @@ class AccessibilityFixer {
2649
2649
  }
2650
2650
  }
2651
2651
 
2652
- console.log(chalk.blue(`\n📊 Summary: Found ${totalIssuesFound} button name issues across ${results.length} files`));
2652
+ console.log(chalk.blue(`\n📊 Tóm tắt: Tìm thấy ${totalIssuesFound} vấn đề tên button trong ${results.length} file`));
2653
2653
  return results;
2654
2654
  }
2655
2655
 
@@ -2735,7 +2735,7 @@ class AccessibilityFixer {
2735
2735
 
2736
2736
  // Fix link names
2737
2737
  async fixLinkNames(directory = '.') {
2738
- console.log(chalk.blue('🔗 Fixing link names...'));
2738
+ console.log(chalk.blue('🔗 Đang sửa tên link...'));
2739
2739
 
2740
2740
  const htmlFiles = await this.findHtmlFiles(directory);
2741
2741
  const results = [];
@@ -2776,7 +2776,7 @@ class AccessibilityFixer {
2776
2776
  }
2777
2777
  }
2778
2778
 
2779
- console.log(chalk.blue(`\n📊 Summary: Found ${totalIssuesFound} link name issues across ${results.length} files`));
2779
+ console.log(chalk.blue(`\n�� Tóm tắt: Tìm thấy ${totalIssuesFound} vấn đề tên link trong ${results.length} file`));
2780
2780
  return results;
2781
2781
  }
2782
2782
 
@@ -2856,7 +2856,7 @@ class AccessibilityFixer {
2856
2856
 
2857
2857
  // Fix landmarks
2858
2858
  async fixLandmarks(directory = '.') {
2859
- console.log(chalk.blue('🏛️ Fixing landmarks...'));
2859
+ console.log(chalk.blue('🏛️ Đang sửa landmark...'));
2860
2860
 
2861
2861
  const htmlFiles = await this.findHtmlFiles(directory);
2862
2862
  const results = [];
@@ -2897,7 +2897,7 @@ class AccessibilityFixer {
2897
2897
  }
2898
2898
  }
2899
2899
 
2900
- console.log(chalk.blue(`\n📊 Summary: Found ${totalIssuesFound} landmark issues across ${results.length} files`));
2900
+ console.log(chalk.blue(`\n📊 Tóm tắt: Tìm thấy ${totalIssuesFound} vấn đề landmark trong ${results.length} file`));
2901
2901
  return results;
2902
2902
  }
2903
2903
 
@@ -2986,7 +2986,7 @@ class AccessibilityFixer {
2986
2986
 
2987
2987
  // Check for broken external links only
2988
2988
  async checkBrokenLinks(directory = '.') {
2989
- console.log(chalk.blue('🔗 Checking for broken external links...'));
2989
+ console.log(chalk.blue('🔗 Đang kiểm tra link bên ngoài bị lỗi...'));
2990
2990
 
2991
2991
  const htmlFiles = await this.findHtmlFiles(directory);
2992
2992
  const results = [];
@@ -3013,7 +3013,7 @@ class AccessibilityFixer {
3013
3013
  }
3014
3014
  }
3015
3015
 
3016
- console.log(chalk.blue(`\n📊 Summary: Analyzed external links in ${results.length} files`));
3016
+ console.log(chalk.blue(`\n📊 Tóm tắt: Đã phân tích link bên ngoài trong ${results.length} file`));
3017
3017
  console.log(chalk.gray('💡 Broken link issues require manual review and cannot be auto-fixed'));
3018
3018
  return results;
3019
3019
  }
@@ -3047,11 +3047,191 @@ class AccessibilityFixer {
3047
3047
  }
3048
3048
  }
3049
3049
 
3050
- console.log(chalk.blue(`\n📊 Summary: Analyzed local resources in ${results.length} files`));
3050
+ console.log(chalk.blue(`\n📊 Tóm tắt: Đã phân tích tài nguyên cục bộ trong ${results.length} file`));
3051
3051
  console.log(chalk.gray('💡 Missing resource issues require manual review and cannot be auto-fixed'));
3052
3052
  return results;
3053
3053
  }
3054
3054
 
3055
+ // Check Google Tag Manager installation
3056
+ async checkGoogleTagManager(directory = '.') {
3057
+ console.log(chalk.blue('🏷️ Đang kiểm tra cài đặt Google Tag Manager (GTM)...'));
3058
+
3059
+ const htmlFiles = await this.findHtmlFiles(directory);
3060
+ const results = [];
3061
+
3062
+ for (const file of htmlFiles) {
3063
+ try {
3064
+ const content = await fs.readFile(file, 'utf8');
3065
+ const gtmAnalysis = this.analyzeGTMInstallation(content, file);
3066
+
3067
+ if (gtmAnalysis.hasGTM || gtmAnalysis.issues.length > 0) {
3068
+ console.log(chalk.cyan(`\n📁 ${file}:`));
3069
+
3070
+ if (gtmAnalysis.hasGTM) {
3071
+ console.log(chalk.green(` ✅ GTM Container ID: ${gtmAnalysis.containerId}`));
3072
+
3073
+ if (gtmAnalysis.headScriptCorrect) {
3074
+ console.log(chalk.green(` ✅ Script trong head: Đã đặt đúng vị trí trước </head>`));
3075
+ } else if (gtmAnalysis.hasHeadScript) {
3076
+ console.log(chalk.yellow(` ⚠️ Script trong head: Tìm thấy nhưng có thể chưa đặt ở vị trí tối ưu`));
3077
+ } else {
3078
+ console.log(chalk.red(` ❌ Script trong head: Thiếu trong phần <head>`));
3079
+ }
3080
+
3081
+ if (gtmAnalysis.bodyNoscriptCorrect) {
3082
+ console.log(chalk.green(` ✅ Noscript trong body: Đã đặt đúng vị trí sau <body>`));
3083
+ } else if (gtmAnalysis.hasBodyNoscript) {
3084
+ console.log(chalk.yellow(` ⚠️ Noscript trong body: Tìm thấy nhưng có thể chưa đặt ở vị trí tối ưu`));
3085
+ } else {
3086
+ console.log(chalk.red(` ❌ Noscript trong body: Thiếu sau thẻ <body>`));
3087
+ }
3088
+ }
3089
+
3090
+ gtmAnalysis.issues.forEach(issue => {
3091
+ console.log(chalk.yellow(` ${issue.type}: ${issue.description}`));
3092
+ if (issue.suggestion) {
3093
+ console.log(chalk.gray(` 💡 ${issue.suggestion}`));
3094
+ }
3095
+ });
3096
+ }
3097
+
3098
+ results.push({
3099
+ file,
3100
+ status: 'analyzed',
3101
+ gtmAnalysis
3102
+ });
3103
+ } catch (error) {
3104
+ console.error(chalk.red(`❌ Lỗi khi xử lý ${file}: ${error.message}`));
3105
+ results.push({ file, status: 'error', error: error.message });
3106
+ }
3107
+ }
3108
+
3109
+ const filesWithGTM = results.filter(r => r.gtmAnalysis?.hasGTM).length;
3110
+ const filesWithIssues = results.filter(r => r.gtmAnalysis?.issues?.length > 0).length;
3111
+
3112
+ console.log(chalk.blue(`\n📊 Tóm tắt: Đã phân tích ${results.length} file`));
3113
+ console.log(chalk.green(` ✅ File có GTM: ${filesWithGTM}`));
3114
+ if (filesWithIssues > 0) {
3115
+ console.log(chalk.yellow(` ⚠️ File có vấn đề về GTM: ${filesWithIssues}`));
3116
+ }
3117
+ console.log(chalk.gray('💡 GTM cần có cả <script> trong <head> và <noscript> sau <body>'));
3118
+
3119
+ return results;
3120
+ }
3121
+
3122
+ analyzeGTMInstallation(content, filePath) {
3123
+ const result = {
3124
+ hasGTM: false,
3125
+ containerId: null,
3126
+ hasHeadScript: false,
3127
+ hasBodyNoscript: false,
3128
+ headScriptCorrect: false,
3129
+ bodyNoscriptCorrect: false,
3130
+ issues: []
3131
+ };
3132
+
3133
+ // Pattern to detect GTM container ID - more flexible to handle line breaks and spaces
3134
+ const gtmScriptPattern = /googletagmanager\.com\/gtm\.js[^'"]*['"]?\s*\+?\s*i\s*\+?\s*dl[^)]*\)\s*[,;]\s*['"]([^'"]*)['"]\s*,\s*['"]script['"]\s*,\s*['"]dataLayer['"]\s*,\s*['"]?(GTM-[A-Z0-9]+)['"]?\)/i;
3135
+ const gtmContainerPattern = /GTM-[A-Z0-9]+/g; // Simpler pattern to find any GTM container ID
3136
+ const gtmNoscriptPattern = /googletagmanager\.com\/ns\.html\?id=(GTM-[A-Z0-9]+)/i;
3137
+
3138
+ // Check for GTM script in head - look for container ID in the script section
3139
+ const scriptMatch = content.match(gtmScriptPattern);
3140
+ let containerId = null;
3141
+
3142
+ // Extract head content
3143
+ const headContent = content.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
3144
+
3145
+ if (headContent) {
3146
+ // Look for GTM container ID in head
3147
+ const headContainerMatches = headContent[1].match(gtmContainerPattern);
3148
+ if (headContainerMatches) {
3149
+ // Filter to only GTM container IDs (not other codes)
3150
+ const gtmIds = headContainerMatches.filter(id => id.startsWith('GTM-'));
3151
+ if (gtmIds.length > 0) {
3152
+ containerId = gtmIds[0]; // Use first found GTM ID
3153
+ result.hasGTM = true;
3154
+ result.containerId = containerId;
3155
+ result.hasHeadScript = true;
3156
+
3157
+ // Check if googletagmanager.com is mentioned in head (confirms it's GTM script)
3158
+ if (headContent[1].includes('googletagmanager.com')) {
3159
+ result.headScriptCorrect = true;
3160
+ }
3161
+ }
3162
+ }
3163
+ }
3164
+
3165
+ if (!result.hasHeadScript && scriptMatch) {
3166
+ // Fallback: script pattern matched but not in head
3167
+ result.hasGTM = true;
3168
+ result.containerId = scriptMatch[2] || scriptMatch[1];
3169
+ result.hasHeadScript = true;
3170
+ result.issues.push({
3171
+ type: '⚠️ Vị trí GTM Script',
3172
+ description: 'Tìm thấy GTM script nhưng không nằm trong phần <head>',
3173
+ suggestion: 'Di chuyển GTM script vào phần <head>, tốt nhất là đặt trước thẻ đóng </head>'
3174
+ });
3175
+ }
3176
+
3177
+ // Check for GTM noscript in body
3178
+ const noscriptMatch = content.match(gtmNoscriptPattern);
3179
+ if (noscriptMatch) {
3180
+ if (!result.hasGTM) {
3181
+ result.hasGTM = true;
3182
+ result.containerId = noscriptMatch[1];
3183
+ }
3184
+ result.hasBodyNoscript = true;
3185
+
3186
+ // Check if noscript is right after <body> tag
3187
+ const bodyStartPattern = /<body[^>]*>([\s\S]{0,500})/i;
3188
+ const bodyStartMatch = content.match(bodyStartPattern);
3189
+ if (bodyStartMatch && bodyStartMatch[1].includes(noscriptMatch[0])) {
3190
+ // Check if it's within first 200 chars after <body> (good practice)
3191
+ const bodyStartPart = bodyStartMatch[1].slice(0, 200);
3192
+ if (bodyStartPart.includes(noscriptMatch[0])) {
3193
+ result.bodyNoscriptCorrect = true;
3194
+ }
3195
+ } else {
3196
+ result.issues.push({
3197
+ type: '⚠️ Vị trí GTM Noscript',
3198
+ description: 'Tìm thấy GTM noscript nhưng không nằm ngay sau thẻ <body>',
3199
+ suggestion: 'Di chuyển GTM noscript về ngay sau thẻ mở <body>'
3200
+ });
3201
+ }
3202
+ }
3203
+
3204
+ // Check for mismatched container IDs
3205
+ if (containerId && noscriptMatch && containerId !== noscriptMatch[1]) {
3206
+ result.issues.push({
3207
+ type: '❌ Container ID không khớp',
3208
+ description: `Script sử dụng ${containerId} nhưng noscript sử dụng ${noscriptMatch[1]}`,
3209
+ suggestion: 'Đảm bảo cả hai đoạn mã GTM sử dụng cùng một container ID'
3210
+ });
3211
+ }
3212
+
3213
+ // Check for incomplete installation
3214
+ if (result.hasGTM) {
3215
+ if (!result.hasHeadScript) {
3216
+ result.issues.push({
3217
+ type: '❌ Thiếu GTM Script',
3218
+ description: 'Tìm thấy GTM noscript nhưng thiếu script chính trong <head>',
3219
+ suggestion: 'Thêm đoạn mã GTM script vào phần <head> trước thẻ </head>'
3220
+ });
3221
+ }
3222
+
3223
+ if (!result.hasBodyNoscript) {
3224
+ result.issues.push({
3225
+ type: '❌ Thiếu GTM Noscript',
3226
+ description: 'Tìm thấy GTM script nhưng thiếu noscript dự phòng trong <body>',
3227
+ suggestion: 'Thêm đoạn mã GTM noscript ngay sau thẻ mở <body>'
3228
+ });
3229
+ }
3230
+ }
3231
+
3232
+ return result;
3233
+ }
3234
+
3055
3235
  async analyzeBrokenLinks(content, filePath, mode = 'all') {
3056
3236
  const issues = [];
3057
3237
  const path = require('path');
@@ -3279,7 +3459,7 @@ class AccessibilityFixer {
3279
3459
 
3280
3460
  // Analyze headings (no auto-fix, only suggestions)
3281
3461
  async analyzeHeadings(directory = '.') {
3282
- console.log(chalk.blue('📑 Analyzing heading structure...'));
3462
+ console.log(chalk.blue('📑 Đang phân tích cấu trúc heading...'));
3283
3463
 
3284
3464
  const htmlFiles = await this.findHtmlFiles(directory);
3285
3465
  const results = [];
@@ -3306,7 +3486,7 @@ class AccessibilityFixer {
3306
3486
  }
3307
3487
  }
3308
3488
 
3309
- console.log(chalk.blue(`\n📊 Summary: Analyzed heading structure in ${results.length} files`));
3489
+ console.log(chalk.blue(`\n📊 Tóm tắt: Đã phân tích cấu trúc heading trong ${results.length} file`));
3310
3490
  console.log(chalk.gray('💡 Heading issues require manual review and cannot be auto-fixed'));
3311
3491
  return results;
3312
3492
  }
@@ -3353,7 +3533,7 @@ class AccessibilityFixer {
3353
3533
  }
3354
3534
  }
3355
3535
 
3356
- console.log(chalk.blue(`\n📊 Summary: Found ${totalIssuesFound} form label issues across ${results.length} files`));
3536
+ console.log(chalk.blue(`\n📊 Tóm tắt: Tìm thấy ${totalIssuesFound} vấn đề nhãn form trong ${results.length} file`));
3357
3537
  return results;
3358
3538
  }
3359
3539
 
@@ -3760,55 +3940,55 @@ class AccessibilityFixer {
3760
3940
 
3761
3941
  try {
3762
3942
  // Step 1: HTML lang attributes
3763
- console.log(chalk.blue('📝 Step 1: HTML lang attributes...'));
3943
+ console.log(chalk.blue('📝 Bước 1: Thuộc tính HTML lang...'));
3764
3944
  const langResults = await this.fixHtmlLang(directory);
3765
3945
  const langFixed = langResults.filter(r => r.status === 'fixed').length;
3766
3946
  results.steps.push({ step: 1, name: 'HTML lang attributes', fixed: langFixed });
3767
3947
 
3768
3948
  // Step 2: Alt attributes
3769
- console.log(chalk.blue('🖼️ Step 2: Alt attributes...'));
3949
+ console.log(chalk.blue('🖼️ Bước 2: Thuộc tính alt...'));
3770
3950
  const altResults = await this.fixEmptyAltAttributes(directory);
3771
3951
  const altFixed = altResults.filter(r => r.status === 'fixed').length;
3772
3952
  const totalAltIssues = altResults.reduce((sum, r) => sum + (r.issues || 0), 0);
3773
3953
  results.steps.push({ step: 2, name: 'Alt attributes', fixed: altFixed, issues: totalAltIssues });
3774
3954
 
3775
3955
  // Step 3: Role attributes
3776
- console.log(chalk.blue('🎭 Step 3: Role attributes...'));
3956
+ console.log(chalk.blue('🎭 Bước 3: Thuộc tính role...'));
3777
3957
  const roleResults = await this.fixRoleAttributes(directory);
3778
3958
  const roleFixed = roleResults.filter(r => r.status === 'fixed').length;
3779
3959
  const totalRoleIssues = roleResults.reduce((sum, r) => sum + (r.issues || 0), 0);
3780
3960
  results.steps.push({ step: 3, name: 'Role attributes', fixed: roleFixed, issues: totalRoleIssues });
3781
3961
 
3782
3962
  // Step 4: Form labels
3783
- console.log(chalk.blue('📋 Step 4: Form labels...'));
3963
+ console.log(chalk.blue('📋 Bước 4: Nhãn form...'));
3784
3964
  const formResults = await this.fixFormLabels(directory);
3785
3965
  const formFixed = formResults.filter(r => r.status === 'fixed').length;
3786
3966
  const totalFormIssues = formResults.reduce((sum, r) => sum + (r.issues || 0), 0);
3787
3967
  results.steps.push({ step: 4, name: 'Form labels', fixed: formFixed, issues: totalFormIssues });
3788
3968
 
3789
3969
  // Step 5: Button names
3790
- console.log(chalk.blue('🔘 Step 5: Button names...'));
3970
+ console.log(chalk.blue('🔘 Bước 5: Tên button...'));
3791
3971
  const buttonResults = await this.fixButtonNames(directory);
3792
3972
  const buttonFixed = buttonResults.filter(r => r.status === 'fixed').length;
3793
3973
  const totalButtonIssues = buttonResults.reduce((sum, r) => sum + (r.issues || 0), 0);
3794
3974
  results.steps.push({ step: 5, name: 'Button names', fixed: buttonFixed, issues: totalButtonIssues });
3795
3975
 
3796
3976
  // Step 6: Link names
3797
- console.log(chalk.blue('🔗 Step 6: Link names...'));
3977
+ console.log(chalk.blue('🔗 Bước 6: Tên link...'));
3798
3978
  const linkResults = await this.fixLinkNames(directory);
3799
3979
  const linkFixed = linkResults.filter(r => r.status === 'fixed').length;
3800
3980
  const totalLinkIssues = linkResults.reduce((sum, r) => sum + (r.issues || 0), 0);
3801
3981
  results.steps.push({ step: 6, name: 'Link names', fixed: linkFixed, issues: totalLinkIssues });
3802
3982
 
3803
3983
  // Step 7: Landmarks
3804
- console.log(chalk.blue('🏛️ Step 7: Landmarks...'));
3984
+ console.log(chalk.blue('🏛️ Bước 7: Landmark...'));
3805
3985
  const landmarkResults = await this.fixLandmarks(directory);
3806
3986
  const landmarkFixed = landmarkResults.filter(r => r.status === 'fixed').length;
3807
3987
  const totalLandmarkIssues = landmarkResults.reduce((sum, r) => sum + (r.issues || 0), 0);
3808
3988
  results.steps.push({ step: 7, name: 'Landmarks', fixed: landmarkFixed, issues: totalLandmarkIssues });
3809
3989
 
3810
3990
  // Step 8: Heading analysis
3811
- console.log(chalk.blue('📑 Step 8: Heading analysis...'));
3991
+ console.log(chalk.blue('📑 Bước 8: Phân tích heading...'));
3812
3992
  const headingResults = await this.analyzeHeadings(directory);
3813
3993
  const totalHeadingSuggestions = headingResults.reduce((sum, r) => sum + (r.issues || 0), 0);
3814
3994
  results.steps.push({ step: 8, name: 'Heading analysis', suggestions: totalHeadingSuggestions });
@@ -3820,7 +4000,7 @@ class AccessibilityFixer {
3820
4000
  const totalBrokenLinks = brokenLinksResults.reduce((sum, r) => sum + (r.issues || 0), 0);
3821
4001
  results.steps.push({ step: '9a', name: 'External links check', issues: totalBrokenLinks });
3822
4002
 
3823
- console.log(chalk.blue('📁 Step 9b: Missing resources check...'));
4003
+ console.log(chalk.blue('📁 Bước 9b: Kiểm tra tài nguyên thiếu...'));
3824
4004
  const missingResourcesResults = await this.check404Resources(directory);
3825
4005
  const totalMissingResources = missingResourcesResults.reduce((sum, r) => sum + (r.issues || 0), 0);
3826
4006
  results.steps.push({ step: '9b', name: 'Missing resources check', issues: totalMissingResources });
@@ -3828,7 +4008,7 @@ class AccessibilityFixer {
3828
4008
  console.log(chalk.gray('💡 Link and resource issues require manual review and cannot be auto-fixed'));
3829
4009
 
3830
4010
  // Step 10: Cleanup duplicate roles
3831
- console.log(chalk.blue('🧹 Step 10: Cleanup duplicate roles...'));
4011
+ console.log(chalk.blue('🧹 Bước 10: Dọn dẹp role trùng lặp...'));
3832
4012
  const cleanupResults = await this.cleanupDuplicateRoles(directory);
3833
4013
  const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
3834
4014
  results.steps.push({ step: 10, name: 'Cleanup duplicate roles', fixed: cleanupFixed });
@@ -3874,7 +4054,7 @@ class AccessibilityFixer {
3874
4054
  }
3875
4055
 
3876
4056
  async fixButtonNames(directory = '.') {
3877
- console.log(chalk.blue('🔘 Fixing button names...'));
4057
+ console.log(chalk.blue('🔘 Đang sửa tên button...'));
3878
4058
 
3879
4059
  const htmlFiles = await this.findHtmlFiles(directory);
3880
4060
  const results = [];
@@ -3915,7 +4095,7 @@ class AccessibilityFixer {
3915
4095
  }
3916
4096
  }
3917
4097
 
3918
- console.log(chalk.blue(`\n📊 Summary: Found ${totalIssuesFound} button name issues across ${results.length} files`));
4098
+ console.log(chalk.blue(`\n📊 Tóm tắt: Tìm thấy ${totalIssuesFound} vấn đề tên button trong ${results.length} file`));
3919
4099
  return results;
3920
4100
  }
3921
4101
 
@@ -3991,7 +4171,7 @@ class AccessibilityFixer {
3991
4171
  }
3992
4172
 
3993
4173
  async fixLinkNames(directory = '.') {
3994
- console.log(chalk.blue('🔗 Fixing link names...'));
4174
+ console.log(chalk.blue('🔗 Đang sửa tên link...'));
3995
4175
 
3996
4176
  const htmlFiles = await this.findHtmlFiles(directory);
3997
4177
  const results = [];
@@ -4032,7 +4212,7 @@ class AccessibilityFixer {
4032
4212
  }
4033
4213
  }
4034
4214
 
4035
- console.log(chalk.blue(`\n📊 Summary: Found ${totalIssuesFound} link name issues across ${results.length} files`));
4215
+ console.log(chalk.blue(`\n�� Tóm tắt: Tìm thấy ${totalIssuesFound} vấn đề tên link trong ${results.length} file`));
4036
4216
  return results;
4037
4217
  }
4038
4218
 
@@ -4108,7 +4288,7 @@ class AccessibilityFixer {
4108
4288
  }
4109
4289
 
4110
4290
  async fixLandmarks(directory = '.') {
4111
- console.log(chalk.blue('🏛️ Fixing landmarks...'));
4291
+ console.log(chalk.blue('🏛️ Đang sửa landmark...'));
4112
4292
 
4113
4293
  const htmlFiles = await this.findHtmlFiles(directory);
4114
4294
  const results = [];
@@ -4134,7 +4314,7 @@ class AccessibilityFixer {
4134
4314
  }
4135
4315
  }
4136
4316
 
4137
- console.log(chalk.blue(`\n📊 Summary: Found ${totalIssuesFound} landmark issues across ${results.length} files`));
4317
+ console.log(chalk.blue(`\n📊 Tóm tắt: Tìm thấy ${totalIssuesFound} vấn đề landmark trong ${results.length} file`));
4138
4318
  return results;
4139
4319
  }
4140
4320
 
@@ -4158,7 +4338,7 @@ class AccessibilityFixer {
4158
4338
  }
4159
4339
 
4160
4340
  async analyzeHeadings(directory = '.') {
4161
- console.log(chalk.blue('📑 Analyzing heading structure...'));
4341
+ console.log(chalk.blue('📑 Đang phân tích cấu trúc heading...'));
4162
4342
 
4163
4343
  const htmlFiles = await this.findHtmlFiles(directory);
4164
4344
  const results = [];
@@ -4187,7 +4367,7 @@ class AccessibilityFixer {
4187
4367
  }
4188
4368
  }
4189
4369
 
4190
- console.log(chalk.blue(`\n📊 Summary: Analyzed heading structure in ${results.length} files`));
4370
+ console.log(chalk.blue(`\n📊 Tóm tắt: Đã phân tích cấu trúc heading trong ${results.length} file`));
4191
4371
  console.log(chalk.gray('💡 Heading issues require manual review and cannot be auto-fixed'));
4192
4372
  return results;
4193
4373
  }
@@ -4195,7 +4375,7 @@ class AccessibilityFixer {
4195
4375
 
4196
4376
 
4197
4377
  async cleanupDuplicateRoles(directory = '.') {
4198
- console.log(chalk.blue('🧹 Cleaning up duplicate role attributes...'));
4378
+ console.log(chalk.blue('🧹 Đang dọn dẹp các thuộc tính role trùng lặp...'));
4199
4379
 
4200
4380
  const htmlFiles = await this.findHtmlFiles(directory);
4201
4381
  const results = [];
@@ -4227,7 +4407,7 @@ class AccessibilityFixer {
4227
4407
  }
4228
4408
  }
4229
4409
 
4230
- console.log(chalk.blue(`\n📊 Summary: Cleaned duplicate roles in ${totalIssuesFound} files`));
4410
+ console.log(chalk.blue(`\n📊 Tóm tắt: Đã dọn dẹp role trùng lặp trong ${totalIssuesFound} file`));
4231
4411
  return results;
4232
4412
  }
4233
4413
 
@@ -4241,7 +4421,7 @@ class AccessibilityFixer {
4241
4421
  }
4242
4422
 
4243
4423
  async fixNestedInteractiveControls(directory = '.') {
4244
- console.log(chalk.blue('🎯 Fixing nested interactive controls...'));
4424
+ console.log(chalk.blue('🎯 Đang sửa các control tương tác lồng nhau...'));
4245
4425
 
4246
4426
  const htmlFiles = await this.findHtmlFiles(directory);
4247
4427
  const results = [];
@@ -4285,7 +4465,7 @@ class AccessibilityFixer {
4285
4465
  }
4286
4466
  }
4287
4467
 
4288
- console.log(chalk.blue(`\n📊 Summary: Found ${totalIssuesFound} nested interactive control issues across ${results.length} files`));
4468
+ console.log(chalk.blue(`\n📊 Tóm tắt: Tìm thấy ${totalIssuesFound} vấn đề control tương tác lồng nhau trong ${results.length} file`));
4289
4469
  return results;
4290
4470
  }
4291
4471
 
@@ -4486,81 +4666,81 @@ class AccessibilityFixer {
4486
4666
 
4487
4667
  try {
4488
4668
  // Step 1: HTML lang attributes
4489
- console.log(chalk.blue('📝 Step 1: HTML lang attributes...'));
4669
+ console.log(chalk.blue('📝 Bước 1: Thuộc tính HTML lang...'));
4490
4670
  const langResults = await this.fixHtmlLang(directory);
4491
4671
  const langFixed = langResults.filter(r => r.status === 'fixed').length;
4492
4672
  results.steps.push({ step: 1, name: 'HTML lang attributes', fixed: langFixed });
4493
4673
 
4494
4674
  // Step 2: Alt attributes
4495
- console.log(chalk.blue('🖼️ Step 2: Alt attributes...'));
4675
+ console.log(chalk.blue('🖼️ Bước 2: Thuộc tính alt...'));
4496
4676
  const altResults = await this.fixEmptyAltAttributes(directory);
4497
4677
  const altFixed = altResults.filter(r => r.status === 'fixed').length;
4498
4678
  const totalAltIssues = altResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4499
4679
  results.steps.push({ step: 2, name: 'Alt attributes', fixed: altFixed, issues: totalAltIssues });
4500
4680
 
4501
4681
  // Step 3: Role attributes
4502
- console.log(chalk.blue('🎭 Step 3: Role attributes...'));
4682
+ console.log(chalk.blue('🎭 Bước 3: Thuộc tính role...'));
4503
4683
  const roleResults = await this.fixRoleAttributes(directory);
4504
4684
  const roleFixed = roleResults.filter(r => r.status === 'fixed').length;
4505
4685
  const totalRoleIssues = roleResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4506
4686
  results.steps.push({ step: 3, name: 'Role attributes', fixed: roleFixed, issues: totalRoleIssues });
4507
4687
 
4508
4688
  // Step 4: Aria-label attributes
4509
- console.log(chalk.blue('🏷️ Step 4: Aria-label attributes...'));
4689
+ console.log(chalk.blue('🏷️ Bước 4: Thuộc tính aria-label...'));
4510
4690
  const ariaResults = await this.fixAriaLabels(directory);
4511
4691
  const ariaFixed = ariaResults.filter(r => r.status === 'processed' && r.changes > 0).length;
4512
4692
  const totalAriaIssues = ariaResults.reduce((sum, r) => sum + (r.changes || 0), 0);
4513
4693
  results.steps.push({ step: 4, name: 'Aria-label attributes', fixed: ariaFixed, issues: totalAriaIssues });
4514
4694
 
4515
4695
  // Step 5: Form labels
4516
- console.log(chalk.blue('📋 Step 5: Form labels...'));
4696
+ console.log(chalk.blue('📋 Bước 5: Nhãn form...'));
4517
4697
  const formResults = await this.fixFormLabels(directory);
4518
4698
  const formFixed = formResults.filter(r => r.status === 'fixed').length;
4519
4699
  const totalFormIssues = formResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4520
4700
  results.steps.push({ step: 5, name: 'Form labels', fixed: formFixed, issues: totalFormIssues });
4521
4701
 
4522
4702
  // Step 6: Nested interactive controls (NEW!)
4523
- console.log(chalk.blue('🎯 Step 6: Nested interactive controls...'));
4703
+ console.log(chalk.blue('🎯 Bước 6: Các control tương tác lồng nhau...'));
4524
4704
  const nestedResults = await this.fixNestedInteractiveControls(directory);
4525
4705
  const nestedFixed = nestedResults.filter(r => r.status === 'fixed').length;
4526
4706
  const totalNestedIssues = nestedResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4527
4707
  results.steps.push({ step: 6, name: 'Nested interactive controls', fixed: nestedFixed, issues: totalNestedIssues });
4528
4708
 
4529
4709
  // Step 7: Button names
4530
- console.log(chalk.blue('🔘 Step 7: Button names...'));
4710
+ console.log(chalk.blue('🔘 Bước 7: Tên button...'));
4531
4711
  const buttonResults = await this.fixButtonNames(directory);
4532
4712
  const buttonFixed = buttonResults.filter(r => r.status === 'fixed').length;
4533
4713
  const totalButtonIssues = buttonResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4534
4714
  results.steps.push({ step: 7, name: 'Button names', fixed: buttonFixed, issues: totalButtonIssues });
4535
4715
 
4536
4716
  // Step 8: Link names
4537
- console.log(chalk.blue('🔗 Step 8: Link names...'));
4717
+ console.log(chalk.blue('🔗 Bước 8: Tên link...'));
4538
4718
  const linkResults = await this.fixLinkNames(directory);
4539
4719
  const linkFixed = linkResults.filter(r => r.status === 'fixed').length;
4540
4720
  const totalLinkIssues = linkResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4541
4721
  results.steps.push({ step: 8, name: 'Link names', fixed: linkFixed, issues: totalLinkIssues });
4542
4722
 
4543
4723
  // Step 9: Landmarks
4544
- console.log(chalk.blue('🏛️ Step 9: Landmarks...'));
4724
+ console.log(chalk.blue('🏛️ Bước 9: Landmark...'));
4545
4725
  const landmarkResults = await this.fixLandmarks(directory);
4546
4726
  const landmarkFixed = landmarkResults.filter(r => r.status === 'fixed').length;
4547
4727
  const totalLandmarkIssues = landmarkResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4548
4728
  results.steps.push({ step: 9, name: 'Landmarks', fixed: landmarkFixed, issues: totalLandmarkIssues });
4549
4729
 
4550
4730
  // Step 10: Heading analysis
4551
- console.log(chalk.blue('📑 Step 10: Heading analysis...'));
4731
+ console.log(chalk.blue('📑 Bước 10: Phân tích heading...'));
4552
4732
  const headingResults = await this.analyzeHeadings(directory);
4553
4733
  const totalHeadingSuggestions = headingResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4554
4734
  results.steps.push({ step: 10, name: 'Heading analysis', suggestions: totalHeadingSuggestions });
4555
4735
  console.log(chalk.gray('💡 Heading issues require manual review and cannot be auto-fixed'));
4556
4736
 
4557
4737
  // Step 11: Broken links and missing resources check
4558
- console.log(chalk.blue('🔗 Step 11a: External links check...'));
4738
+ console.log(chalk.blue('🔗 Bước 11a: Kiểm tra link bên ngoài...'));
4559
4739
  const brokenLinksResults = await this.checkBrokenLinks(directory);
4560
4740
  const totalBrokenLinks = brokenLinksResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4561
4741
  results.steps.push({ step: '11a', name: 'External links check', issues: totalBrokenLinks });
4562
4742
 
4563
- console.log(chalk.blue('📁 Step 11b: Missing resources check...'));
4743
+ console.log(chalk.blue('�� Bước 11b: Kiểm tra tài nguyên thiếu...'));
4564
4744
  const missingResourcesResults = await this.check404Resources(directory);
4565
4745
  const totalMissingResources = missingResourcesResults.reduce((sum, r) => sum + (r.issues || 0), 0);
4566
4746
  results.steps.push({ step: '11b', name: 'Missing resources check', issues: totalMissingResources });
@@ -4568,7 +4748,7 @@ class AccessibilityFixer {
4568
4748
  console.log(chalk.gray('💡 Link and resource issues require manual review and cannot be auto-fixed'));
4569
4749
 
4570
4750
  // Step 12: Cleanup duplicate roles
4571
- console.log(chalk.blue('🧹 Step 12: Cleanup duplicate roles...'));
4751
+ console.log(chalk.blue('🧹 Bước 12: Dọn dẹp role trùng lặp...'));
4572
4752
  const cleanupResults = await this.cleanupDuplicateRoles(directory);
4573
4753
  const cleanupFixed = cleanupResults.filter(r => r.status === 'fixed').length;
4574
4754
  results.steps.push({ step: 12, name: 'Cleanup duplicate roles', fixed: cleanupFixed });
@@ -5710,9 +5890,9 @@ class AccessibilityFixer {
5710
5890
 
5711
5891
  // Display results
5712
5892
  if (unusedFiles.length === 0) {
5713
- console.log(chalk.green('✅ No unused files found! All files are properly referenced.'));
5893
+ console.log(chalk.green('✅ Không tìm thấy file không sử dụng! Tất cả file đều được tham chiếu đúng cách.'));
5714
5894
  } else {
5715
- console.log(chalk.yellow(`\n📋 Found ${unusedFiles.length} potentially unused files:`));
5895
+ console.log(chalk.yellow(`\n📋 Tìm thấy ${unusedFiles.length} file thể không sử dụng:`));
5716
5896
 
5717
5897
  unusedFiles.forEach((file, index) => {
5718
5898
  const icon = this.getFileIcon(file.type);
@@ -5720,12 +5900,12 @@ class AccessibilityFixer {
5720
5900
  });
5721
5901
 
5722
5902
  const totalSize = unusedFiles.reduce((sum, file) => sum + file.size, 0);
5723
- console.log(chalk.blue(`\n📊 Total unused file size: ${this.formatFileSize(totalSize)}`));
5724
- console.log(chalk.gray('💡 Review these files before deleting - some may be used dynamically or required for deployment'));
5903
+ console.log(chalk.blue(`\n📊 Tổng kích thước file không sử dụng: ${this.formatFileSize(totalSize)}`));
5904
+ console.log(chalk.gray('💡 Xem xét kỹ các file này trước khi xóa - một số thể được sử dụng động hoặc cần thiết cho deployment'));
5725
5905
  }
5726
5906
 
5727
5907
  const endTime = Date.now();
5728
- console.log(chalk.gray(`⏱️ Analysis completed in ${endTime - startTime}ms`));
5908
+ console.log(chalk.gray(`⏱️ Phân tích hoàn tất trong ${endTime - startTime}ms`));
5729
5909
 
5730
5910
  return {
5731
5911
  unusedFiles: unusedFiles,
@@ -5841,7 +6021,7 @@ class AccessibilityFixer {
5841
6021
  return referencedFiles;
5842
6022
  }
5843
6023
 
5844
- // Find HTML and CSS/SCSS files to scan for references
6024
+ // Find HTML, CSS/SCSS, JS, and other source files to scan for references
5845
6025
  async findSourceFiles(directory) {
5846
6026
  const sourceFiles = [];
5847
6027
 
@@ -5858,9 +6038,11 @@ class AccessibilityFixer {
5858
6038
  await walk(filePath);
5859
6039
  }
5860
6040
  } else {
5861
- // Include HTML (main entry) and CSS/SCSS (for background images)
6041
+ // Include HTML, CSS/SCSS, JavaScript, TypeScript, Vue, PHP, JSON, XML, SVG
5862
6042
  const ext = path.extname(file).toLowerCase();
5863
- if (['.html', '.htm', '.css', '.scss', '.sass'].includes(ext)) {
6043
+ if (['.html', '.htm', '.css', '.scss', '.sass',
6044
+ '.js', '.jsx', '.ts', '.tsx', '.mjs',
6045
+ '.vue', '.php', '.json', '.xml', '.svg'].includes(ext)) {
5864
6046
  sourceFiles.push(filePath);
5865
6047
  }
5866
6048
  }
@@ -5874,7 +6056,7 @@ class AccessibilityFixer {
5874
6056
  return sourceFiles;
5875
6057
  }
5876
6058
 
5877
- // Enhanced reference extraction from HTML and CSS files
6059
+ // Enhanced reference extraction from HTML, CSS, JS, and other source files
5878
6060
  extractAllReferences(content, baseDir, sourceFile) {
5879
6061
  const references = [];
5880
6062
  const ext = path.extname(sourceFile).toLowerCase();
@@ -5888,6 +6070,31 @@ class AccessibilityFixer {
5888
6070
  // Extract from CSS/SCSS files - background images
5889
6071
  const cssRefs = this.extractCssReferences(content, baseDir);
5890
6072
  references.push(...cssRefs);
6073
+
6074
+ } else if (['.js', '.jsx', '.ts', '.tsx', '.mjs'].includes(ext)) {
6075
+ // Extract from JavaScript/TypeScript files - imports, requires, dynamic imports
6076
+ const jsRefs = this.extractJsReferences(content, baseDir);
6077
+ references.push(...jsRefs);
6078
+
6079
+ } else if (ext === '.vue') {
6080
+ // Extract from Vue files - template, script, and style sections
6081
+ const vueRefs = this.extractVueReferences(content, baseDir);
6082
+ references.push(...vueRefs);
6083
+
6084
+ } else if (ext === '.json') {
6085
+ // Extract from JSON files - config files, manifests
6086
+ const jsonRefs = this.extractJsonReferences(content, baseDir);
6087
+ references.push(...jsonRefs);
6088
+
6089
+ } else if (['.xml', '.svg'].includes(ext)) {
6090
+ // Extract from XML/SVG files - embedded references
6091
+ const xmlRefs = this.extractXmlReferences(content, baseDir);
6092
+ references.push(...xmlRefs);
6093
+
6094
+ } else if (ext === '.php') {
6095
+ // Extract from PHP files - mixed HTML/PHP content
6096
+ const phpRefs = this.extractPhpReferences(content, baseDir);
6097
+ references.push(...phpRefs);
5891
6098
  }
5892
6099
 
5893
6100
  return references;
@@ -5922,7 +6129,10 @@ class AccessibilityFixer {
5922
6129
  // Iframe
5923
6130
  /<iframe[^>]*src\s*=\s*["']([^"']+)["']/gi,
5924
6131
  // Meta (for icons)
5925
- /<meta[^>]*content\s*=\s*["']([^"']+\.(ico|png|jpg|jpeg|svg))["']/gi
6132
+ /<meta[^>]*content\s*=\s*["']([^"']+\.(ico|png|jpg|jpeg|svg))["']/gi,
6133
+ // Server Side Includes (SSI)
6134
+ /<!--#include\s+virtual\s*=\s*["']([^"']+)["']\s*-->/gi,
6135
+ /<!--#include\s+file\s*=\s*["']([^"']+)["']\s*-->/gi
5926
6136
  ];
5927
6137
 
5928
6138
  for (const pattern of patterns) {
@@ -5990,16 +6200,22 @@ class AccessibilityFixer {
5990
6200
  extractJsReferences(content, baseDir) {
5991
6201
  const references = [];
5992
6202
 
5993
- // JavaScript patterns for file references
6203
+ // JavaScript/TypeScript patterns for file references
5994
6204
  const patterns = [
5995
- // require() calls
5996
- /require\s*\(\s*["']([^"']+)["']\s*\)/gi,
5997
- // import statements
6205
+ // ES6 import statements
5998
6206
  /import\s+.*?from\s+["']([^"']+)["']/gi,
5999
- // fetch() calls with local files
6000
- /fetch\s*\(\s*["']([^"']*\.(html|css|js|json|xml))["']\s*\)/gi,
6207
+ // Dynamic imports
6208
+ /import\s*\(\s*["']([^"']+)["']\s*\)/gi,
6209
+ // CommonJS require
6210
+ /require\s*\(\s*["']([^"']+)["']\s*\)/gi,
6211
+ // fetch() API calls
6212
+ /fetch\s*\(\s*["']([^"']+)["']\s*\)/gi,
6001
6213
  // XMLHttpRequest
6002
- /\.open\s*\(\s*["'][^"']*["']\s*,\s*["']([^"']*\.(html|css|js|json|xml))["']/gi
6214
+ /\.open\s*\(\s*["'][^"']*["']\s*,\s*["']([^"']+)["']/gi,
6215
+ // String literals that look like paths
6216
+ /["']([^"']*\.(html|css|js|json|xml|jpg|jpeg|png|gif|svg|webp|ico))["']/gi,
6217
+ // Template literals with paths
6218
+ /`([^`]*\.(html|css|js|json|xml|jpg|jpeg|png|gif|svg|webp|ico))`/gi
6003
6219
  ];
6004
6220
 
6005
6221
  for (const pattern of patterns) {
@@ -6007,13 +6223,76 @@ class AccessibilityFixer {
6007
6223
  while ((match = pattern.exec(content)) !== null) {
6008
6224
  const url = match[1];
6009
6225
  if (this.isLocalFile(url)) {
6010
- // Store both original URL and normalized versions for matching
6011
- references.push(url);
6012
- if (url.startsWith('/')) {
6013
- references.push(url.substring(1)); // Remove leading slash
6226
+ this.addNormalizedUrl(references, url);
6227
+ }
6228
+ }
6229
+ }
6230
+
6231
+ return references;
6232
+ }
6233
+
6234
+ extractVueReferences(content, baseDir) {
6235
+ const references = [];
6236
+
6237
+ // Extract from template section (HTML-like)
6238
+ const templateMatch = content.match(/<template[^>]*>([\s\S]*?)<\/template>/i);
6239
+ if (templateMatch) {
6240
+ const htmlRefs = this.extractHtmlReferences(templateMatch[1], baseDir);
6241
+ references.push(...htmlRefs);
6242
+ }
6243
+
6244
+ // Extract from script section (JS-like)
6245
+ const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
6246
+ if (scriptMatch) {
6247
+ const jsRefs = this.extractJsReferences(scriptMatch[1], baseDir);
6248
+ references.push(...jsRefs);
6249
+ }
6250
+
6251
+ // Extract from style section (CSS-like)
6252
+ const styleMatch = content.match(/<style[^>]*>([\s\S]*?)<\/style>/i);
6253
+ if (styleMatch) {
6254
+ const cssRefs = this.extractCssReferences(styleMatch[1], baseDir);
6255
+ references.push(...cssRefs);
6256
+ }
6257
+
6258
+ return references;
6259
+ }
6260
+
6261
+ extractJsonReferences(content, baseDir) {
6262
+ const references = [];
6263
+
6264
+ try {
6265
+ // Parse JSON and look for string values that might be file paths
6266
+ const data = JSON.parse(content);
6267
+
6268
+ const findPaths = (obj) => {
6269
+ if (typeof obj === 'string') {
6270
+ // Check if string looks like a file path
6271
+ if (/\.(html|css|js|json|xml|jpg|jpeg|png|gif|svg|webp|ico)$/i.test(obj)) {
6272
+ if (this.isLocalFile(obj)) {
6273
+ this.addNormalizedUrl(references, obj);
6274
+ }
6014
6275
  }
6015
- if (!url.startsWith('./') && !url.startsWith('/')) {
6016
- references.push('./' + url); // Add leading ./
6276
+ } else if (Array.isArray(obj)) {
6277
+ obj.forEach(item => findPaths(item));
6278
+ } else if (obj && typeof obj === 'object') {
6279
+ Object.values(obj).forEach(value => findPaths(value));
6280
+ }
6281
+ };
6282
+
6283
+ findPaths(data);
6284
+ } catch (error) {
6285
+ // If JSON parsing fails, try regex patterns
6286
+ const patterns = [
6287
+ /["']([^"']*\.(html|css|js|json|xml|jpg|jpeg|png|gif|svg|webp|ico))["']/gi
6288
+ ];
6289
+
6290
+ for (const pattern of patterns) {
6291
+ let match;
6292
+ while ((match = pattern.exec(content)) !== null) {
6293
+ const url = match[1];
6294
+ if (this.isLocalFile(url)) {
6295
+ this.addNormalizedUrl(references, url);
6017
6296
  }
6018
6297
  }
6019
6298
  }
@@ -6022,6 +6301,68 @@ class AccessibilityFixer {
6022
6301
  return references;
6023
6302
  }
6024
6303
 
6304
+ extractXmlReferences(content, baseDir) {
6305
+ const references = [];
6306
+
6307
+ // XML/SVG patterns for file references
6308
+ const patterns = [
6309
+ // href attributes
6310
+ /href\s*=\s*["']([^"']+)["']/gi,
6311
+ // src attributes
6312
+ /src\s*=\s*["']([^"']+)["']/gi,
6313
+ // xlink:href (SVG)
6314
+ /xlink:href\s*=\s*["']([^"']+)["']/gi,
6315
+ // file attributes
6316
+ /file\s*=\s*["']([^"']+)["']/gi,
6317
+ // path attributes with file extensions
6318
+ /path\s*=\s*["']([^"']*\.(html|css|js|jpg|jpeg|png|gif|svg))["']/gi
6319
+ ];
6320
+
6321
+ for (const pattern of patterns) {
6322
+ let match;
6323
+ while ((match = pattern.exec(content)) !== null) {
6324
+ const url = match[1];
6325
+ if (this.isLocalFile(url)) {
6326
+ this.addNormalizedUrl(references, url);
6327
+ }
6328
+ }
6329
+ }
6330
+
6331
+ return references;
6332
+ }
6333
+
6334
+ extractPhpReferences(content, baseDir) {
6335
+ const references = [];
6336
+
6337
+ // PHP can contain HTML, so extract HTML references first
6338
+ const htmlRefs = this.extractHtmlReferences(content, baseDir);
6339
+ references.push(...htmlRefs);
6340
+
6341
+ // PHP-specific patterns
6342
+ const patterns = [
6343
+ // include/require statements
6344
+ /(?:include|require|include_once|require_once)\s*\(\s*["']([^"']+)["']\s*\)/gi,
6345
+ // file_get_contents
6346
+ /file_get_contents\s*\(\s*["']([^"']+)["']\s*\)/gi,
6347
+ // readfile
6348
+ /readfile\s*\(\s*["']([^"']+)["']\s*\)/gi,
6349
+ // fopen
6350
+ /fopen\s*\(\s*["']([^"']+)["']\s*,/gi
6351
+ ];
6352
+
6353
+ for (const pattern of patterns) {
6354
+ let match;
6355
+ while ((match = pattern.exec(content)) !== null) {
6356
+ const url = match[1];
6357
+ if (this.isLocalFile(url)) {
6358
+ this.addNormalizedUrl(references, url);
6359
+ }
6360
+ }
6361
+ }
6362
+
6363
+ return references;
6364
+ }
6365
+
6025
6366
  async findCssFiles(directory) {
6026
6367
  const files = [];
6027
6368