@binclusive/a11y 0.1.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/README.md +285 -0
- package/bin/a11y.mjs +36 -0
- package/bin/diff-scope.mjs +29 -0
- package/data/baseline-rules.json +892 -0
- package/package.json +68 -0
- package/src/agent-lane.ts +138 -0
- package/src/agents-block.ts +157 -0
- package/src/baseline/gen-baseline.ts +166 -0
- package/src/cli.ts +1026 -0
- package/src/collect-dom.ts +119 -0
- package/src/collect-liquid.ts +103 -0
- package/src/collect-swift.ts +227 -0
- package/src/collect-unity.ts +99 -0
- package/src/collect.ts +54 -0
- package/src/commands.ts +314 -0
- package/src/config-scan.ts +177 -0
- package/src/contract.ts +355 -0
- package/src/core.ts +546 -0
- package/src/detect-stack.ts +207 -0
- package/src/diff-scope-cli.ts +12 -0
- package/src/diff-scope.ts +150 -0
- package/src/emit-contract.ts +181 -0
- package/src/enforce.ts +1125 -0
- package/src/eslint-plugin-jsx-a11y.d.ts +11 -0
- package/src/evidence.ts +308 -0
- package/src/github-identity.ts +201 -0
- package/src/hook.ts +242 -0
- package/src/impact-gate.ts +107 -0
- package/src/imports-resolve.ts +248 -0
- package/src/index.ts +183 -0
- package/src/liquid-ast.ts +203 -0
- package/src/liquid-rules.ts +691 -0
- package/src/mcp.ts +363 -0
- package/src/module-scope.ts +137 -0
- package/src/phone-home.ts +470 -0
- package/src/pr-comment.ts +250 -0
- package/src/pr-summary-cli.ts +182 -0
- package/src/pr-summary.ts +291 -0
- package/src/registry.ts +605 -0
- package/src/reporter/contract.ts +154 -0
- package/src/reporter/finding.ts +87 -0
- package/src/reporter/github-adapter.ts +183 -0
- package/src/reporter/null-adapter.ts +51 -0
- package/src/reporter/registry.ts +22 -0
- package/src/reporter-cli.ts +48 -0
- package/src/resolve-components.ts +579 -0
- package/src/runner/budget.ts +90 -0
- package/src/runner/codegraph-lookup.test.ts +93 -0
- package/src/runner/codegraph-lookup.ts +197 -0
- package/src/runner/index.ts +86 -0
- package/src/runner/lookup.ts +69 -0
- package/src/runner/provider.ts +72 -0
- package/src/runner/providers/anthropic.ts +168 -0
- package/src/runner/providers/openai.ts +191 -0
- package/src/runner/reasoner.ts +73 -0
- package/src/runner/reasoning/index.ts +53 -0
- package/src/runner/reasoning/prompt.ts +200 -0
- package/src/runner/reasoning/react.ts +149 -0
- package/src/runner/reasoning/shopify.ts +214 -0
- package/src/runner/reasoning/skills-reasoner.ts +117 -0
- package/src/runner/reasoning/types.ts +99 -0
- package/src/runner/runner.ts +203 -0
- package/src/sarif.ts +148 -0
- package/src/source-identity.ts +0 -0
- package/src/source-trace.ts +814 -0
- package/src/suggest.ts +328 -0
- package/src/suppression-ranges.ts +354 -0
- package/src/suppressor-map.ts +189 -0
- package/src/suppressors.ts +284 -0
- package/src/tsconfig-aliases.ts +155 -0
- package/src/unity-ast.ts +331 -0
- package/src/unity-findings.ts +91 -0
- package/src/unity-guid-registry.ts +82 -0
- package/src/unity-label-resolve.ts +249 -0
- package/src/unity-rule-color-only.ts +127 -0
- package/src/unity-rule-missing-label.ts +156 -0
- package/src/unity-rules-baseline.ts +273 -0
- package/src/wcag-map.ts +35 -0
- package/src/wcag-tags.ts +35 -0
- package/src/workspace-resolve.ts +405 -0
package/src/sarif.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SARIF 2.1.0 renderer — GitHub code-scanning's finding interchange format.
|
|
3
|
+
* Harvested from the platform CLI's renderer (`@binclusive/cli`
|
|
4
|
+
* `src/output/sarif.ts`) and RETARGETED to the engine's rich LOCAL
|
|
5
|
+
* {@link EnrichedFinding}: a SARIF `physicalLocation` needs the `file`/`line` the
|
|
6
|
+
* metadata-only wire contract deliberately drops, so this is a LOCAL renderer
|
|
7
|
+
* that reads the source-anchored model directly — it never routes through the
|
|
8
|
+
* contract projection.
|
|
9
|
+
*
|
|
10
|
+
* Impact is the ONE contract enum: {@link impactToLevel} maps the four concrete
|
|
11
|
+
* axe impacts (plus `unknown`) onto SARIF's `error|warning|note`, and
|
|
12
|
+
* {@link evidenceImpact} supplies that value from the finding's resolved axe
|
|
13
|
+
* impact. So text and SARIF narrow through the same impact vocabulary.
|
|
14
|
+
*/
|
|
15
|
+
import { relative } from "node:path";
|
|
16
|
+
import type { Impact } from "@binclusive/a11y-contract";
|
|
17
|
+
import { hasSelector, toContractProvenance } from "./emit-contract";
|
|
18
|
+
import { evidenceHelpUrl, evidenceImpact, type EnrichedFinding } from "./evidence";
|
|
19
|
+
import { type LocationOptions, resolveLocations } from "./source-identity";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The one contract-impact -> SARIF-level mapping. Exhaustive over the closed enum.
|
|
23
|
+
* `critical`/`serious` are the actionable errors; `moderate` a warning; `minor`
|
|
24
|
+
* and `unknown` (not judged) are notes — SARIF's lowest, honest floor.
|
|
25
|
+
*/
|
|
26
|
+
export function impactToLevel(impact: Impact): "error" | "warning" | "note" {
|
|
27
|
+
switch (impact) {
|
|
28
|
+
case "critical":
|
|
29
|
+
case "serious":
|
|
30
|
+
return "error";
|
|
31
|
+
case "moderate":
|
|
32
|
+
return "warning";
|
|
33
|
+
case "minor":
|
|
34
|
+
case "unknown":
|
|
35
|
+
return "note";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// A SARIF physicalLocation anchors on an artifactLocation (the source file); a
|
|
40
|
+
// source-line region rides it when the finding has one. A rendered-DOM (axe)
|
|
41
|
+
// finding carries `file` = the page URL and `line` = 0, so it emits no region
|
|
42
|
+
// but maps its CSS `selector` to a logicalLocation instead.
|
|
43
|
+
interface SarifLocation {
|
|
44
|
+
physicalLocation: {
|
|
45
|
+
artifactLocation: { uri: string };
|
|
46
|
+
region?: { startLine: number };
|
|
47
|
+
};
|
|
48
|
+
logicalLocations?: Array<{ fullyQualifiedName: string; kind: "element" }>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// GitHub code-scanning resolves a SARIF `uri` relative to the repo root, so when
|
|
52
|
+
// a workspace `root` is given the source-file uri is relativized against it — a
|
|
53
|
+
// scan of a staged mirror dir then still yields repo-relative `src/Foo.tsx`
|
|
54
|
+
// paths that annotate the right line of the PR diff. A rendered-DOM (axe)
|
|
55
|
+
// finding's `file` is the page URL, not a workspace path, so it is left as-is.
|
|
56
|
+
function locationUri(file: string, root: string | undefined): string {
|
|
57
|
+
if (root === undefined || /^https?:\/\//i.test(file)) return file;
|
|
58
|
+
return relative(root, file);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function findingLocations(f: EnrichedFinding, root: string | undefined): SarifLocation[] {
|
|
62
|
+
const location: SarifLocation = {
|
|
63
|
+
physicalLocation: {
|
|
64
|
+
artifactLocation: { uri: locationUri(f.file, root) },
|
|
65
|
+
...(f.line > 0 ? { region: { startLine: f.line } } : {}),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
if (hasSelector(f.selector)) {
|
|
69
|
+
location.logicalLocations = [{ fullyQualifiedName: f.selector, kind: "element" }];
|
|
70
|
+
}
|
|
71
|
+
return [location];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Render a SARIF 2.1.0 log over the LOCAL findings. `runId` names the run in
|
|
76
|
+
* `automationDetails` (e.g. a PR number or scan id). Rules are the deduped set
|
|
77
|
+
* of fired rule ids; each result carries its impact level, source location,
|
|
78
|
+
* and a `properties.provenance` tag (`deterministic` | `agent`) so the two
|
|
79
|
+
* checker lanes stay distinguishable once both feed SARIF. `opts.root`, when
|
|
80
|
+
* given, relativizes source-file uris against the scanned root — the form
|
|
81
|
+
* GitHub code-scanning needs to anchor annotations on the PR diff.
|
|
82
|
+
*
|
|
83
|
+
* A source result also carries `partialFingerprints.primaryLocationLineHash` so
|
|
84
|
+
* code-scanning tracks the alert across commits as lines move (ADR 0042's "free
|
|
85
|
+
* consistency win"): the hash is the SAME source-identity fingerprint the wire
|
|
86
|
+
* contract emits — see the results map for why they can never disagree.
|
|
87
|
+
*/
|
|
88
|
+
export function formatSarif(
|
|
89
|
+
findings: readonly EnrichedFinding[],
|
|
90
|
+
runId: string,
|
|
91
|
+
opts: LocationOptions = {},
|
|
92
|
+
): string {
|
|
93
|
+
const ruleIds = [...new Set(findings.map((f) => f.ruleId))];
|
|
94
|
+
const ruleById = new Map(findings.map((f) => [f.ruleId, f]));
|
|
95
|
+
|
|
96
|
+
// Resolve every finding's identity through the ONE hash function the wire
|
|
97
|
+
// contract uses (`resolveLocations`), so SARIF and the emitted identity can
|
|
98
|
+
// never disagree on a line's hash. Page findings resolve to a `page` location
|
|
99
|
+
// (no line hash) and honestly get no `primaryLocationLineHash`.
|
|
100
|
+
const located = resolveLocations(findings, opts);
|
|
101
|
+
|
|
102
|
+
const sarif = {
|
|
103
|
+
$schema:
|
|
104
|
+
"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
|
|
105
|
+
version: "2.1.0" as const,
|
|
106
|
+
runs: [
|
|
107
|
+
{
|
|
108
|
+
tool: {
|
|
109
|
+
driver: {
|
|
110
|
+
name: "Binclusive",
|
|
111
|
+
informationUri: "https://binclusive.io",
|
|
112
|
+
rules: ruleIds.map((id) => {
|
|
113
|
+
const f = ruleById.get(id);
|
|
114
|
+
const helpUri = f ? evidenceHelpUrl(f) : null;
|
|
115
|
+
return {
|
|
116
|
+
id,
|
|
117
|
+
...(f ? { shortDescription: { text: f.message } } : {}),
|
|
118
|
+
...(helpUri ? { helpUri } : {}),
|
|
119
|
+
};
|
|
120
|
+
}),
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
results: findings.map((f) => {
|
|
124
|
+
const loc = located.get(f);
|
|
125
|
+
return {
|
|
126
|
+
ruleId: f.ruleId,
|
|
127
|
+
level: impactToLevel(evidenceImpact(f) ?? "unknown"),
|
|
128
|
+
message: { text: f.message },
|
|
129
|
+
locations: findingLocations(f, opts.root),
|
|
130
|
+
// `<lineHash>:<index>` — the content hash lets code-scanning re-match the
|
|
131
|
+
// alert when the line moves; `index` disambiguates identical lines within a
|
|
132
|
+
// file, exactly as the finding identity does. A page finding has no source
|
|
133
|
+
// line, so it carries no fingerprint (never fabricate one).
|
|
134
|
+
...(loc?.kind === "source"
|
|
135
|
+
? { partialFingerprints: { primaryLocationLineHash: `${loc.lineHash}:${loc.index}` } }
|
|
136
|
+
: {}),
|
|
137
|
+
properties: { provenance: toContractProvenance(f.provenance) },
|
|
138
|
+
};
|
|
139
|
+
}),
|
|
140
|
+
automationDetails: {
|
|
141
|
+
id: `binclusive-a11y/${runId}`,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return JSON.stringify(sarif, null, 2);
|
|
148
|
+
}
|
|
Binary file
|