@diegovelasquezweb/a11y-engine 0.11.51 → 0.11.53
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 +47 -14
package/package.json
CHANGED
|
@@ -465,16 +465,32 @@ function detectStylingSystem(aiInput) {
|
|
|
465
465
|
return "inline";
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
+
export function buildStyleInstruction(stylingSystem) {
|
|
469
|
+
if (stylingSystem === "tailwind") {
|
|
470
|
+
return (
|
|
471
|
+
"This project uses Tailwind CSS. Apply style fixes as Tailwind utility classes in the HTML/JSX file — do not write raw CSS. " +
|
|
472
|
+
"For inline style attributes that suppress focus (e.g. style=\"outline: none\"): remove outline: none from the style attribute and add focus-visible: Tailwind utility classes (e.g. focus-visible:outline-2 focus-visible:outline-offset-2) directly on the element instead."
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
if (stylingSystem === "css") {
|
|
476
|
+
return (
|
|
477
|
+
"CSS/SCSS files are available in the files array. Prefer fixing visual issues (touch targets, color contrast) in the CSS/SCSS file using proper selectors. " +
|
|
478
|
+
"For violations in a CSS/SCSS file: add or update the rule there directly. " +
|
|
479
|
+
"For violations in an inline HTML style attribute (e.g. style=\"outline: none\"): make TWO changes — " +
|
|
480
|
+
"(1) remove outline: none / outline: 0 from the HTML style attribute, " +
|
|
481
|
+
"(2) add a :focus-visible rule in the CSS/SCSS file targeting the element."
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
return (
|
|
485
|
+
"No CSS file was provided. Remove outline: none or outline: 0 from the style attribute entirely and rely on the browser default focus indicator. " +
|
|
486
|
+
"Do not add new CSS files."
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
468
490
|
async function callClaudeForPatch({ apiKey, model, aiInput, remediationPath }) {
|
|
469
491
|
const remediationContext = extractRemediationContext(remediationPath);
|
|
470
492
|
const stylingSystem = detectStylingSystem(aiInput);
|
|
471
|
-
|
|
472
|
-
const styleInstruction =
|
|
473
|
-
stylingSystem === "tailwind"
|
|
474
|
-
? "This project uses Tailwind CSS. Apply style fixes as Tailwind utility classes in the HTML/JSX file — do not write raw CSS."
|
|
475
|
-
: stylingSystem === "css"
|
|
476
|
-
? "CSS/SCSS files are available in the files array. Prefer fixing visual issues (touch targets, color contrast, focus outlines) in the CSS/SCSS file using proper selectors rather than inline styles."
|
|
477
|
-
: "No CSS file was provided. Apply style fixes using inline style attributes in the HTML file.";
|
|
493
|
+
const styleInstruction = buildStyleInstruction(stylingSystem);
|
|
478
494
|
|
|
479
495
|
const system = [
|
|
480
496
|
"You are an accessibility fix engine.",
|
|
@@ -696,19 +712,36 @@ export async function applyFindingFix(input) {
|
|
|
696
712
|
|
|
697
713
|
const validation = validateAiPatchOutput(patchOutput, projectDir, candidateSet);
|
|
698
714
|
if (!validation.ok) {
|
|
699
|
-
// When Claude returns no changes
|
|
700
|
-
// batch) already resolved this issue.
|
|
701
|
-
//
|
|
715
|
+
// When Claude returns no changes OR a no-op patch (search===replace), it may be
|
|
716
|
+
// because a prior fix (e.g. the DOM batch) already resolved this issue.
|
|
717
|
+
// Verify by checking if the pattern's context_reject_regex now matches the
|
|
718
|
+
// current file content around the target element.
|
|
702
719
|
// If it does, the element is already accessible — count as resolved.
|
|
703
|
-
|
|
720
|
+
const isNoChanges = validation.reason === "AI patch output has no changes";
|
|
721
|
+
const isNoop = validation.reason.startsWith("AI generated a no-op patch for ");
|
|
722
|
+
if (isNoChanges || isNoop) {
|
|
704
723
|
const patternId = finding.pattern_id || finding.patternId || "";
|
|
705
724
|
const patternDef = (ASSETS.remediation.codePatterns?.patterns || [])
|
|
706
725
|
.find((p) => p.id === patternId);
|
|
707
726
|
const rejectRegex = patternDef?.context_reject_regex;
|
|
708
727
|
if (rejectRegex) {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
728
|
+
// For no-op patches, read the CURRENT file content — the DOM batch may have
|
|
729
|
+
// already resolved this finding by modifying the file after the scan.
|
|
730
|
+
let context;
|
|
731
|
+
if (isNoop) {
|
|
732
|
+
try {
|
|
733
|
+
const currentContent = fs.readFileSync(candidate.abs, "utf8");
|
|
734
|
+
const fileLines = currentContent.split("\n");
|
|
735
|
+
const lineIdx = Math.max(0, (aiInput.finding.line || 1) - 1);
|
|
736
|
+
const start = Math.max(0, lineIdx - 4);
|
|
737
|
+
const end = Math.min(fileLines.length, lineIdx + 5);
|
|
738
|
+
context = fileLines.slice(start, end).join("\n");
|
|
739
|
+
} catch {
|
|
740
|
+
context = [aiInput.finding.surroundingLines, aiInput.finding.matchLine].filter(Boolean).join("\n");
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
context = [aiInput.finding.surroundingLines, aiInput.finding.matchLine].filter(Boolean).join("\n");
|
|
744
|
+
}
|
|
712
745
|
try {
|
|
713
746
|
if (new RegExp(rejectRegex, "i").test(context)) {
|
|
714
747
|
return buildResult({
|