@diegovelasquezweb/a11y-engine 0.11.31 → 0.11.33
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/package.json +1 -1
- package/src/fixes/apply-finding-fix.mjs +67 -14
package/package.json
CHANGED
|
@@ -90,7 +90,7 @@ function getFindings(input) {
|
|
|
90
90
|
|
|
91
91
|
function getIntelligenceForRule(ruleId) {
|
|
92
92
|
const rules = ASSETS.remediation.intelligence?.rules || {};
|
|
93
|
-
return isObject(rules[ruleId]) ? rules[ruleId] : {};
|
|
93
|
+
return isObject(rules[ruleId]) ? rules[ruleId] : isObject(rules[`cdp-${ruleId}`]) ? rules[`cdp-${ruleId}`] : {};
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
function listFilesRecursive(dir) {
|
|
@@ -167,15 +167,56 @@ function buildPatternAiInput({ finding, candidate }) {
|
|
|
167
167
|
};
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// Layout file name patterns for page-level fixes (e.g. bypass/skip-link).
|
|
171
|
+
// Ordered by priority: more specific names first.
|
|
172
|
+
const LAYOUT_FILE_PATTERNS = [
|
|
173
|
+
/^app[\/\\]layout\.[jt]sx?$/i,
|
|
174
|
+
/^src[\/\\]app[\/\\]layout\.[jt]sx?$/i,
|
|
175
|
+
/^pages[\/\\]_app\.[jt]sx?$/i,
|
|
176
|
+
/^pages[\/\\]_document\.[jt]sx?$/i,
|
|
177
|
+
/^src[\/\\]pages[\/\\]_app\.[jt]sx?$/i,
|
|
178
|
+
/^src[\/\\]pages[\/\\]_document\.[jt]sx?$/i,
|
|
179
|
+
/layouts[\/\\]default\.vue$/i,
|
|
180
|
+
/layouts[\/\\][^\/\\]+\.vue$/i,
|
|
181
|
+
/\+layout\.svelte$/i,
|
|
182
|
+
/app\.component\.html$/i,
|
|
183
|
+
/layouts[\/\\][^\/\\]+\.astro$/i,
|
|
184
|
+
/layout[\/\\]theme\.liquid$/i,
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
function getLayoutCandidates(projectDir, files) {
|
|
188
|
+
const byPattern = [];
|
|
189
|
+
for (const { abs, rel, content } of files) {
|
|
190
|
+
const normalRel = rel.replace(/\\/g, "/");
|
|
191
|
+
for (let i = 0; i < LAYOUT_FILE_PATTERNS.length; i++) {
|
|
192
|
+
if (LAYOUT_FILE_PATTERNS[i].test(normalRel)) {
|
|
193
|
+
byPattern.push({ abs, rel, content, score: LAYOUT_FILE_PATTERNS.length - i });
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return byPattern.sort((a, b) => b.score - a.score).slice(0, MAX_CANDIDATE_FILES);
|
|
199
|
+
}
|
|
200
|
+
|
|
170
201
|
function getCandidateFiles(projectDir, finding) {
|
|
171
|
-
const
|
|
202
|
+
const allFiles = listFilesRecursive(projectDir).map((abs) => {
|
|
203
|
+
const content = fs.readFileSync(abs, "utf8");
|
|
204
|
+
const rel = path.relative(projectDir, abs);
|
|
205
|
+
return { abs, rel, content };
|
|
206
|
+
});
|
|
207
|
+
|
|
172
208
|
const tokens = selectorTokens(finding.selector);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
209
|
+
|
|
210
|
+
if (tokens.length === 0) {
|
|
211
|
+
// Selector has no useful tokens (e.g. "html", "body") — target layout files directly.
|
|
212
|
+
const layoutCandidates = getLayoutCandidates(projectDir, allFiles);
|
|
213
|
+
if (layoutCandidates.length > 0) return layoutCandidates;
|
|
214
|
+
// Fallback: return all files with equal weight so Claude gets the full picture.
|
|
215
|
+
return allFiles.slice(0, MAX_CANDIDATE_FILES).map((f) => ({ ...f, score: 1 }));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const ranked = allFiles
|
|
219
|
+
.map((f) => ({ ...f, score: scoreFile(f.rel, f.content, tokens) }))
|
|
179
220
|
.filter((item) => item.score > 0)
|
|
180
221
|
.sort((a, b) => b.score - a.score)
|
|
181
222
|
.slice(0, MAX_CANDIDATE_FILES);
|
|
@@ -183,7 +224,11 @@ function getCandidateFiles(projectDir, finding) {
|
|
|
183
224
|
}
|
|
184
225
|
|
|
185
226
|
function buildExecution(ruleId, intelligenceRule, finding) {
|
|
186
|
-
const
|
|
227
|
+
const raw = finding.rule_id || ruleId || "";
|
|
228
|
+
const axeEquivalent = intelligenceRule.related_rules?.find((r) =>
|
|
229
|
+
r.reason?.toLowerCase().includes("axe"),
|
|
230
|
+
)?.id;
|
|
231
|
+
const ruleVerify = axeEquivalent || raw;
|
|
187
232
|
const route = normalizeRoute(finding.area);
|
|
188
233
|
return {
|
|
189
234
|
strategy: "ai-dom-patch",
|
|
@@ -212,11 +257,19 @@ function groupFindingsByFile(domFindings, projectDir) {
|
|
|
212
257
|
|
|
213
258
|
for (const finding of domFindings) {
|
|
214
259
|
const tokens = selectorTokens(finding.selector);
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
260
|
+
let ranked;
|
|
261
|
+
if (tokens.length === 0) {
|
|
262
|
+
const layoutCandidates = getLayoutCandidates(projectDir, allFiles);
|
|
263
|
+
ranked = layoutCandidates.length > 0
|
|
264
|
+
? layoutCandidates
|
|
265
|
+
: allFiles.slice(0, MAX_CANDIDATE_FILES).map((f) => ({ ...f, score: 1 }));
|
|
266
|
+
} else {
|
|
267
|
+
ranked = allFiles
|
|
268
|
+
.map((f) => ({ ...f, score: scoreFile(f.rel, f.content, tokens) }))
|
|
269
|
+
.filter((f) => f.score > 0)
|
|
270
|
+
.sort((a, b) => b.score - a.score)
|
|
271
|
+
.slice(0, MAX_CANDIDATE_FILES);
|
|
272
|
+
}
|
|
220
273
|
|
|
221
274
|
const key = ranked.length > 0 ? ranked[0].rel : `__no_candidates_${finding.id}`;
|
|
222
275
|
if (!initialGroups.has(key)) {
|