@ploaness/governance 1.0.0 → 1.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/dist/a11y-hit-target.d.ts +35 -0
- package/dist/a11y-hit-target.d.ts.map +1 -0
- package/dist/a11y-hit-target.js +102 -0
- package/dist/a11y-hit-target.js.map +1 -0
- package/dist/a11y-incomplete.d.ts +18 -0
- package/dist/a11y-incomplete.d.ts.map +1 -0
- package/dist/a11y-incomplete.js +82 -0
- package/dist/a11y-incomplete.js.map +1 -0
- package/dist/access-boundary.d.ts +19 -5
- package/dist/access-boundary.d.ts.map +1 -1
- package/dist/access-boundary.js +58 -4
- package/dist/access-boundary.js.map +1 -1
- package/dist/admin-view-coverage.d.ts +25 -0
- package/dist/admin-view-coverage.d.ts.map +1 -0
- package/dist/admin-view-coverage.js +90 -0
- package/dist/admin-view-coverage.js.map +1 -0
- package/dist/axe-coverage.d.ts +41 -0
- package/dist/axe-coverage.d.ts.map +1 -0
- package/dist/axe-coverage.js +95 -0
- package/dist/axe-coverage.js.map +1 -0
- package/dist/banned-typography.d.ts.map +1 -1
- package/dist/banned-typography.js +26 -0
- package/dist/banned-typography.js.map +1 -1
- package/dist/build-diagnosis.d.ts +10 -0
- package/dist/build-diagnosis.d.ts.map +1 -0
- package/dist/build-diagnosis.js +38 -0
- package/dist/build-diagnosis.js.map +1 -0
- package/dist/document-references.d.ts.map +1 -1
- package/dist/document-references.js +7 -1
- package/dist/document-references.js.map +1 -1
- package/dist/environment-coherence.d.ts +87 -0
- package/dist/environment-coherence.d.ts.map +1 -0
- package/dist/environment-coherence.js +150 -0
- package/dist/environment-coherence.js.map +1 -0
- package/dist/generated-drift.d.ts +18 -0
- package/dist/generated-drift.d.ts.map +1 -0
- package/dist/generated-drift.js +35 -0
- package/dist/generated-drift.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/payload-access.d.ts.map +1 -1
- package/dist/payload-access.js +23 -49
- package/dist/payload-access.js.map +1 -1
- package/dist/payload-configs.d.ts +24 -0
- package/dist/payload-configs.d.ts.map +1 -0
- package/dist/payload-configs.js +77 -0
- package/dist/payload-configs.js.map +1 -0
- package/dist/payload-field-access.d.ts +4 -0
- package/dist/payload-field-access.d.ts.map +1 -0
- package/dist/payload-field-access.js +51 -0
- package/dist/payload-field-access.js.map +1 -0
- package/dist/payload-policy.d.ts.map +1 -1
- package/dist/payload-policy.js +111 -13
- package/dist/payload-policy.js.map +1 -1
- package/dist/payload-source.js +2 -2
- package/dist/payload-source.js.map +1 -1
- package/dist/relationship-cleanup.d.ts +18 -0
- package/dist/relationship-cleanup.d.ts.map +1 -0
- package/dist/relationship-cleanup.js +96 -0
- package/dist/relationship-cleanup.js.map +1 -0
- package/dist/route-coverage.d.ts +27 -0
- package/dist/route-coverage.d.ts.map +1 -0
- package/dist/route-coverage.js +57 -0
- package/dist/route-coverage.js.map +1 -0
- package/dist/route-map.d.ts +65 -0
- package/dist/route-map.d.ts.map +1 -0
- package/dist/route-map.js +183 -0
- package/dist/route-map.js.map +1 -0
- package/dist/secret-policy.js +4 -4
- package/dist/secret-policy.js.map +1 -1
- package/dist/settings.d.ts +10 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +5 -1
- package/dist/settings.js.map +1 -1
- package/dist/source-security.d.ts +4 -0
- package/dist/source-security.d.ts.map +1 -0
- package/dist/source-security.js +56 -0
- package/dist/source-security.js.map +1 -0
- package/dist/source-text.d.ts +14 -0
- package/dist/source-text.d.ts.map +1 -1
- package/dist/source-text.js +39 -0
- package/dist/source-text.js.map +1 -1
- package/dist/version-policy.d.ts.map +1 -1
- package/dist/version-policy.js +31 -0
- package/dist/version-policy.js.map +1 -1
- package/package.json +1 -1
- package/dist/url-safety.d.ts +0 -12
- package/dist/url-safety.d.ts.map +0 -1
- package/dist/url-safety.js +0 -58
- package/dist/url-safety.js.map +0 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** What a browser must report about a control before this module can classify it. */
|
|
2
|
+
export interface HitTargetProbe {
|
|
3
|
+
/** Border-box width in CSS pixels. */
|
|
4
|
+
readonly width: number;
|
|
5
|
+
/** Border-box height in CSS pixels. */
|
|
6
|
+
readonly height: number;
|
|
7
|
+
/** The computed `clip-path`, verbatim, or `none`. */
|
|
8
|
+
readonly clipPath: string;
|
|
9
|
+
/** The computed `clip`, verbatim, or `auto`. */
|
|
10
|
+
readonly clip: string;
|
|
11
|
+
/**
|
|
12
|
+
* Whether `elementFromPoint` at the box centre returned this element or a descendant of it.
|
|
13
|
+
*
|
|
14
|
+
* This is the same question Playwright's actionability check asks, and asking it here is what lets
|
|
15
|
+
* a clipped element and an obscured one be told apart rather than both reported as a timeout.
|
|
16
|
+
*/
|
|
17
|
+
readonly ownsCentrePoint: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** How a control can be reached, and therefore which passes of the sweep apply to it. */
|
|
20
|
+
export type HitTargetVerdict = 'hoverable' | 'visually-hidden' | 'obscured';
|
|
21
|
+
/** The verdict together with the sentence a report prints for it. */
|
|
22
|
+
export interface HitTargetClassification {
|
|
23
|
+
readonly verdict: HitTargetVerdict;
|
|
24
|
+
readonly reason: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Decide how a control can be reached, so the sweep hovers what it can and explains what it cannot.
|
|
28
|
+
*
|
|
29
|
+
* A control that owns its own centre point is hoverable whatever its styling, which is what keeps a
|
|
30
|
+
* decorative `clip-path` on a real button from being mistaken for a hiding technique.
|
|
31
|
+
* @param probe what the browser reports about the control.
|
|
32
|
+
* @returns the verdict and the sentence describing it.
|
|
33
|
+
*/
|
|
34
|
+
export declare const classifyHitTarget: (probe: HitTargetProbe) => HitTargetClassification;
|
|
35
|
+
//# sourceMappingURL=a11y-hit-target.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11y-hit-target.d.ts","sourceRoot":"","sources":["../src/a11y-hit-target.ts"],"names":[],"mappings":"AAsBA,qFAAqF;AACrF,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,uCAAuC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;CAClC;AAED,yFAAyF;AACzF,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,iBAAiB,GAAG,UAAU,CAAA;AAE3E,qEAAqE;AACrE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAA;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAyED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,cAAc,KAAG,uBAkBzD,CAAA"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Whether a control can be hovered, and what it means when it cannot.
|
|
2
|
+
//
|
|
3
|
+
// The shipped accessibility sweep hovers every control in the site chrome to measure hover contrast.
|
|
4
|
+
// A skip link hidden with `clip-path: inset(50%)` - the standard accessible pattern - has no hit
|
|
5
|
+
// target, because a clip removes an element from hit testing as well as from painting. Playwright
|
|
6
|
+
// therefore retried the hover until the whole test timed out, and reported `<header> intercepts
|
|
7
|
+
// pointer events`: a message that blames the ancestor the browser found instead, and sends the reader
|
|
8
|
+
// to weaken a skip link that was correct.
|
|
9
|
+
//
|
|
10
|
+
// The repair cannot be "catch the failure and move on". A VISIBLE control that intercepts pointer
|
|
11
|
+
// events is a real page defect, and swallowing both cases would trade a misleading failure for no
|
|
12
|
+
// failure at all. So this decides between them, and the two answers differ:
|
|
13
|
+
//
|
|
14
|
+
// - visually hidden: skip the hover pass and say why. Nothing is lost, because hover contrast is a
|
|
15
|
+
// measurement of painted pixels and this element paints none. The focus pass still runs, which is
|
|
16
|
+
// the state that actually matters for a skip link.
|
|
17
|
+
// - obscured: something is on top of a control that does paint. That is the defect the sweep is for,
|
|
18
|
+
// and it should be reported as such rather than as a timeout.
|
|
19
|
+
//
|
|
20
|
+
// The classification is deliberately conservative: anything it cannot place is hoverable, so an
|
|
21
|
+
// undecided case runs the check rather than skipping it.
|
|
22
|
+
// The legacy visually-hidden pattern draws a 1px box and clips it away, so its box has area and its
|
|
23
|
+
// paint does not. Anything at or under this in either dimension is that pattern rather than a control.
|
|
24
|
+
const HIDDEN_BOX_LIMIT = 1;
|
|
25
|
+
// An `inset()` is empty when its opposing insets meet or cross, which is arithmetic rather than a
|
|
26
|
+
// spelling: `inset(50%)` collapses both axes, and so do `inset(50% 0)` and `inset(60% 10% 60% 10%)`.
|
|
27
|
+
// Matching the literal `inset(50%)` would have covered the one form every visually-hidden helper class
|
|
28
|
+
// ships and quietly missed the rest, so the shorthand is expanded and the pairs are added instead.
|
|
29
|
+
const FULL_EXTENT = 100;
|
|
30
|
+
const INSET_SHORTHAND = /^inset\(([^)]*)\)$/;
|
|
31
|
+
// `inset()` may carry a `round <radius>` clause, which describes corners rather than extent.
|
|
32
|
+
const ROUND_CLAUSE = /\sround\s[\s\S]*$/;
|
|
33
|
+
const PERCENT = '%';
|
|
34
|
+
// A percentage is the only unit that can be reasoned about without knowing the element's size, and it
|
|
35
|
+
// is the unit the pattern is always written in. A pixel inset is left undecided, which classifies the
|
|
36
|
+
// control as reachable and runs the check.
|
|
37
|
+
const asPercentage = (token) => token.endsWith(PERCENT) ? Number(token.slice(0, -PERCENT.length)) : Number.NaN;
|
|
38
|
+
// CSS box shorthand: one value applies to all four sides, two to the vertical and horizontal pairs,
|
|
39
|
+
// three add a distinct bottom, four are top/right/bottom/left in order.
|
|
40
|
+
const expandBoxShorthand = (values) => {
|
|
41
|
+
const [first, second, third, fourth] = values;
|
|
42
|
+
if (first === undefined) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
const right = second ?? first;
|
|
46
|
+
return [first, right, third ?? first, fourth ?? right];
|
|
47
|
+
};
|
|
48
|
+
const isEmptyInset = (argument) => {
|
|
49
|
+
const values = expandBoxShorthand(argument
|
|
50
|
+
.replace(ROUND_CLAUSE, '')
|
|
51
|
+
.split(/[\s,]+/)
|
|
52
|
+
.filter((token) => token.length > 0)
|
|
53
|
+
.map((token) => asPercentage(token)));
|
|
54
|
+
const [top, right, bottom, left] = values;
|
|
55
|
+
if (top === undefined || right === undefined || bottom === undefined || left === undefined) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return top + bottom >= FULL_EXTENT || left + right >= FULL_EXTENT;
|
|
59
|
+
};
|
|
60
|
+
// A circle or ellipse of no radius clips everything away. Only an explicit zero counts: a keyword
|
|
61
|
+
// radius such as `closest-side` depends on the box and is left undecided.
|
|
62
|
+
const ZERO_RADIUS_SHAPE = /^(?:circle|ellipse)\(\s*0(?:px|%|em|rem)?[\s,)]/;
|
|
63
|
+
// `clip: rect(...)` with no extent, in the spellings browsers compute it to. The 1px form is the
|
|
64
|
+
// classic `.sr-only` rule, which pairs a 1px box with a rect that keeps none of it.
|
|
65
|
+
const EMPTY_CLIP = /^rect\(\s*[01](?:px)?[\s,]+[01](?:px)?[\s,]+[01](?:px)?[\s,]+[01](?:px)?\s*\)$/;
|
|
66
|
+
const isEmptyClipPath = (clipPath) => {
|
|
67
|
+
const normalised = clipPath.trim().toLowerCase();
|
|
68
|
+
const inset = INSET_SHORTHAND.exec(normalised);
|
|
69
|
+
return inset === null ? ZERO_RADIUS_SHAPE.test(normalised) : isEmptyInset(inset[1] ?? '');
|
|
70
|
+
};
|
|
71
|
+
const isEmptyClip = (clip) => EMPTY_CLIP.test(clip.trim().toLowerCase());
|
|
72
|
+
const hasNoPaintedArea = (probe) => probe.width <= 0 ||
|
|
73
|
+
probe.height <= 0 ||
|
|
74
|
+
(probe.width <= HIDDEN_BOX_LIMIT && probe.height <= HIDDEN_BOX_LIMIT) ||
|
|
75
|
+
isEmptyClipPath(probe.clipPath) ||
|
|
76
|
+
isEmptyClip(probe.clip);
|
|
77
|
+
/**
|
|
78
|
+
* Decide how a control can be reached, so the sweep hovers what it can and explains what it cannot.
|
|
79
|
+
*
|
|
80
|
+
* A control that owns its own centre point is hoverable whatever its styling, which is what keeps a
|
|
81
|
+
* decorative `clip-path` on a real button from being mistaken for a hiding technique.
|
|
82
|
+
* @param probe what the browser reports about the control.
|
|
83
|
+
* @returns the verdict and the sentence describing it.
|
|
84
|
+
*/
|
|
85
|
+
export const classifyHitTarget = (probe) => {
|
|
86
|
+
if (probe.ownsCentrePoint) {
|
|
87
|
+
return { verdict: 'hoverable', reason: 'the control is reachable at its own centre point' };
|
|
88
|
+
}
|
|
89
|
+
if (hasNoPaintedArea(probe)) {
|
|
90
|
+
return {
|
|
91
|
+
verdict: 'visually-hidden',
|
|
92
|
+
reason: 'the control is visually hidden (a zero-area box or a clip that removes it), so it has no ' +
|
|
93
|
+
'hit target and no painted pixels whose contrast could be measured under hover',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
verdict: 'obscured',
|
|
98
|
+
reason: 'the control paints but something else answers at its centre point, so a pointer cannot ' +
|
|
99
|
+
'reach it; this is a defect in the page rather than in the sweep',
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=a11y-hit-target.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11y-hit-target.js","sourceRoot":"","sources":["../src/a11y-hit-target.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The axe `incomplete` message keys that describe a defect rather than a question.
|
|
3
|
+
*
|
|
4
|
+
* Exported so a project reading a finding can see the whole of what is enforced, and so a spec can
|
|
5
|
+
* assert the enforced set rather than a single member of it.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFINITE_INCOMPLETE_KEYS: readonly string[];
|
|
8
|
+
/**
|
|
9
|
+
* Pick the entries of an axe `incomplete` bucket that are defects rather than open questions.
|
|
10
|
+
*
|
|
11
|
+
* Takes `unknown` rather than axe's own result type because this package declares no dependencies and
|
|
12
|
+
* must not acquire one to read a shape it only ever inspects. The caller passes `scan.incomplete`
|
|
13
|
+
* straight through.
|
|
14
|
+
* @param incomplete the `incomplete` array of an axe result.
|
|
15
|
+
* @returns one human-readable finding per definite defect, in the order axe reported them.
|
|
16
|
+
*/
|
|
17
|
+
export declare const findDefiniteIncomplete: (incomplete: unknown) => readonly string[];
|
|
18
|
+
//# sourceMappingURL=a11y-incomplete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11y-incomplete.d.ts","sourceRoot":"","sources":["../src/a11y-incomplete.ts"],"names":[],"mappings":"AAoBA;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAAmB,CAAA;AAuDzE;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAAI,YAAY,OAAO,KAAG,SAAS,MAAM,EAGpE,CAAA"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// What to do with the axe bucket the shipped sweep used to discard.
|
|
2
|
+
//
|
|
3
|
+
// axe returns three buckets, not two: `violations`, `passes`, and `incomplete` - the last meaning "a
|
|
4
|
+
// check ran and could not decide". The accessibility sweep asserted `violations` alone, which reads as
|
|
5
|
+
// the obvious thing to write and silently excluded the single worst defect the sweep exists to catch.
|
|
6
|
+
//
|
|
7
|
+
// An exactly equal foreground and background is filed as incomplete rather than as a violation,
|
|
8
|
+
// because axe reads identical colours as text hidden on purpose and refers the question to a human.
|
|
9
|
+
// So a control drawn in its own background colour - contrast ratio 1:1, invisible to every reader -
|
|
10
|
+
// passed a gate built to measure contrast. A project confirmed it by mutation: removing the colour fix
|
|
11
|
+
// from the page left the assertion green.
|
|
12
|
+
//
|
|
13
|
+
// The answer is narrow on purpose. A blanket "incomplete must be empty" would be wrong for a rule no
|
|
14
|
+
// project can edit: text over a photograph, a gradient, or a partly transparent layer lands in this
|
|
15
|
+
// bucket legitimately, and axe defers there because the question genuinely needs a human. `equalRatio`
|
|
16
|
+
// is the one key that needs none - two identical colours cannot be a judgement call - so it is the one
|
|
17
|
+
// key that fails. The list is a floor to widen when another key turns out to be as unambiguous, not a
|
|
18
|
+
// ceiling.
|
|
19
|
+
import { asRecord, asText, isArray } from './json-shapes.js';
|
|
20
|
+
/**
|
|
21
|
+
* The axe `incomplete` message keys that describe a defect rather than a question.
|
|
22
|
+
*
|
|
23
|
+
* Exported so a project reading a finding can see the whole of what is enforced, and so a spec can
|
|
24
|
+
* assert the enforced set rather than a single member of it.
|
|
25
|
+
*/
|
|
26
|
+
export const DEFINITE_INCOMPLETE_KEYS = ['equalRatio'];
|
|
27
|
+
// A node's checks live under three keys, and which one carries the result depends on how the rule was
|
|
28
|
+
// composed rather than on anything the reader chose. Reading only `any` would miss a rule expressed as
|
|
29
|
+
// `all`, which is a silence of exactly the kind this module exists to remove.
|
|
30
|
+
const CHECK_KEYS = ['any', 'all', 'none'];
|
|
31
|
+
// A node names itself by CSS selector, and axe nests the selector when the node is inside an iframe.
|
|
32
|
+
// Joining rather than taking the first keeps a finding pointing at the element a reader has to open.
|
|
33
|
+
const TARGET_SEPARATOR = ' >>> ';
|
|
34
|
+
const targetOf = (node) => {
|
|
35
|
+
const target = asRecord(node)['target'];
|
|
36
|
+
if (!isArray(target)) {
|
|
37
|
+
return '(unknown element)';
|
|
38
|
+
}
|
|
39
|
+
const parts = target.map((part) => asText(part));
|
|
40
|
+
return parts.length === 0 ? '(unknown element)' : parts.join(TARGET_SEPARATOR);
|
|
41
|
+
};
|
|
42
|
+
const checksOf = (node) => CHECK_KEYS.flatMap((key) => {
|
|
43
|
+
const checks = asRecord(node)[key];
|
|
44
|
+
return isArray(checks) ? checks : [];
|
|
45
|
+
});
|
|
46
|
+
// The message axe itself wrote, rather than one restated here. A locale-aware runner returns the
|
|
47
|
+
// translated sentence, and a finding that quoted an English copy would disagree with the report the
|
|
48
|
+
// same run printed.
|
|
49
|
+
const describeCheck = (ruleId, node, check) => {
|
|
50
|
+
const message = asText(asRecord(check)['message']);
|
|
51
|
+
const stated = message.length > 0 ? message : 'axe reported no message';
|
|
52
|
+
return `${ruleId} on ${targetOf(node)}: ${stated}`;
|
|
53
|
+
};
|
|
54
|
+
// axe names the reason it could not decide under `data.messageKey`, which is the only field here that
|
|
55
|
+
// is a stable identifier rather than prose: the message beside it is localised.
|
|
56
|
+
const messageKeyOf = (check) => {
|
|
57
|
+
const data = asRecord(asRecord(check)['data']);
|
|
58
|
+
return asText(data['messageKey']);
|
|
59
|
+
};
|
|
60
|
+
const definiteChecks = (ruleId, node) => checksOf(node)
|
|
61
|
+
.filter((check) => DEFINITE_INCOMPLETE_KEYS.includes(messageKeyOf(check)))
|
|
62
|
+
.map((check) => describeCheck(ruleId, node, check));
|
|
63
|
+
const definiteNodes = (result) => {
|
|
64
|
+
const nodes = asRecord(result)['nodes'];
|
|
65
|
+
const ruleId = asText(asRecord(result)['id']);
|
|
66
|
+
return isArray(nodes)
|
|
67
|
+
? nodes.flatMap((node) => definiteChecks(ruleId, node))
|
|
68
|
+
: [];
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Pick the entries of an axe `incomplete` bucket that are defects rather than open questions.
|
|
72
|
+
*
|
|
73
|
+
* Takes `unknown` rather than axe's own result type because this package declares no dependencies and
|
|
74
|
+
* must not acquire one to read a shape it only ever inspects. The caller passes `scan.incomplete`
|
|
75
|
+
* straight through.
|
|
76
|
+
* @param incomplete the `incomplete` array of an axe result.
|
|
77
|
+
* @returns one human-readable finding per definite defect, in the order axe reported them.
|
|
78
|
+
*/
|
|
79
|
+
export const findDefiniteIncomplete = (incomplete) => isArray(incomplete)
|
|
80
|
+
? incomplete.flatMap((result) => definiteNodes(result))
|
|
81
|
+
: [];
|
|
82
|
+
//# sourceMappingURL=a11y-incomplete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11y-incomplete.js","sourceRoot":"","sources":["../src/a11y-incomplete.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,18 +7,32 @@ export type ReportedPermission = boolean | {
|
|
|
7
7
|
readonly permission?: boolean;
|
|
8
8
|
readonly where?: unknown;
|
|
9
9
|
};
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
/**
|
|
11
|
+
* A `fields` map as Payload sends it, or the bare `true` its sanitisation collapses a fully permitted
|
|
12
|
+
* one to - which says every field without naming one, and so cannot be enumerated.
|
|
13
|
+
*/
|
|
14
|
+
export type ReportedFields = boolean | Readonly<Record<string, ReportedEntity>>;
|
|
15
|
+
/**
|
|
16
|
+
* One entity's operations and, beside them, the per-field map Payload reports for it.
|
|
17
|
+
*
|
|
18
|
+
* A field is reported in exactly the shape an entity is - its own operations, and its own `fields` when
|
|
19
|
+
* it is an array or a group - so one type describes both and the walk over them is the same walk.
|
|
20
|
+
*/
|
|
21
|
+
export interface ReportedEntity {
|
|
22
|
+
readonly fields?: ReportedFields;
|
|
23
|
+
readonly [operation: string]: ReportedFields | ReportedPermission | undefined;
|
|
24
|
+
}
|
|
25
|
+
/** The body of `/api/access`, read for the operations and the fields beneath them. */
|
|
13
26
|
export interface AccessReport {
|
|
14
27
|
readonly canAccessAdmin?: boolean;
|
|
15
28
|
readonly collections?: Readonly<Record<string, ReportedEntity>>;
|
|
16
29
|
readonly globals?: Readonly<Record<string, ReportedEntity>>;
|
|
17
30
|
}
|
|
18
|
-
/** One permission the running application actually grants. */
|
|
31
|
+
/** One permission the running application actually grants, on an entity or on one field of it. */
|
|
19
32
|
export interface Granted {
|
|
20
33
|
readonly entity: string;
|
|
21
34
|
readonly operation: string;
|
|
35
|
+
readonly field?: string;
|
|
22
36
|
}
|
|
23
37
|
/**
|
|
24
38
|
* A write is never a default a project should be able to reach by accident: Payload grants none of these
|
|
@@ -27,7 +41,7 @@ export interface Granted {
|
|
|
27
41
|
*/
|
|
28
42
|
export declare const JUDGED_OPERATIONS: readonly string[];
|
|
29
43
|
/** Whether Payload reported this operation as permitted, in either of the two shapes it sends. */
|
|
30
|
-
export declare const isPermitted: (permission: ReportedPermission | undefined) => boolean;
|
|
44
|
+
export declare const isPermitted: (permission: ReportedFields | ReportedPermission | undefined) => boolean;
|
|
31
45
|
/** Every permission an access report grants, across collections and globals alike. */
|
|
32
46
|
export declare const grantedPermissions: (report: AccessReport) => readonly Granted[];
|
|
33
47
|
/** How one grant is named, both in a report and in the `publicAccess` entry that would declare it. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-boundary.d.ts","sourceRoot":"","sources":["../src/access-boundary.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"access-boundary.d.ts","sourceRoot":"","sources":["../src/access-boundary.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAEjD;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAC1B,OAAO,GACP;IAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAE/D;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;AAE/E;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAA;IAChC,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,kBAAkB,GAAG,SAAS,CAAA;CAC9E;AAED,sFAAsF;AACtF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;CAC5D;AAED,kGAAkG;AAClG,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAA2C,CAAA;AAO1F,kGAAkG;AAClG,eAAO,MAAM,WAAW,GACtB,YAAY,cAAc,GAAG,kBAAkB,GAAG,SAAS,KAC1D,OASF,CAAA;AA0DD,sFAAsF;AACtF,eAAO,MAAM,kBAAkB,GAAI,QAAQ,YAAY,KAAG,SAAS,OAAO,EAGzE,CAAA;AAED,sGAAsG;AACtG,eAAO,MAAM,aAAa,GAAI,SAAS,OAAO,KAAG,MAG8B,CAAA;AAiB/E;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,YAAY,EACpB,UAAU,SAAS,YAAY,EAAE,KAChC,SAAS,MAAM,EAG4C,CAAA"}
|
package/dist/access-boundary.js
CHANGED
|
@@ -4,17 +4,71 @@
|
|
|
4
4
|
* a public site legitimately grants, which is what `publicAccess` exists to record.
|
|
5
5
|
*/
|
|
6
6
|
export const JUDGED_OPERATIONS = ['create', 'read', 'update', 'delete'];
|
|
7
|
+
/** The path standing in for a `fields` map Payload collapsed, which grants every field at once. */
|
|
8
|
+
const EVERY_FIELD = '*';
|
|
9
|
+
const PATH_SEPARATOR = '.';
|
|
7
10
|
/** Whether Payload reported this operation as permitted, in either of the two shapes it sends. */
|
|
8
|
-
export const isPermitted = (permission) =>
|
|
9
|
-
|
|
11
|
+
export const isPermitted = (permission) => {
|
|
12
|
+
if (permission === undefined || typeof permission === 'boolean') {
|
|
13
|
+
return permission === true;
|
|
14
|
+
}
|
|
15
|
+
// Both remaining shapes carry an index signature or an optional `permission`, so both are readable
|
|
16
|
+
// through one accessor rather than through a guard that would have to tell them apart by a key a
|
|
17
|
+
// field could legitimately be named.
|
|
18
|
+
const constrained = permission;
|
|
19
|
+
return constrained.permission === true;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Every field path one operation reaches, joined as Payload nests them.
|
|
23
|
+
*
|
|
24
|
+
* Recursive because an array or group field carries a `fields` map of its own, so a leaf is named by
|
|
25
|
+
* the path that reaches it (`playerFleet.shipKind`) rather than by its own name alone, which would
|
|
26
|
+
* collide between two arrays sharing a field name.
|
|
27
|
+
*/
|
|
28
|
+
const fieldPathsFor = (fields, operation, prefix) => {
|
|
29
|
+
if (fields === undefined) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
if (typeof fields === 'boolean') {
|
|
33
|
+
return fields ? [`${prefix}${EVERY_FIELD}`] : [];
|
|
34
|
+
}
|
|
35
|
+
return Object.entries(fields).flatMap(([name, reported]) => [
|
|
36
|
+
...(isPermitted(reported[operation]) ? [`${prefix}${name}`] : []),
|
|
37
|
+
...fieldPathsFor(reported.fields, operation, `${prefix}${name}${PATH_SEPARATOR}`),
|
|
38
|
+
]);
|
|
39
|
+
};
|
|
40
|
+
const grantsForOperation = (entity, permissions, operation) => [
|
|
41
|
+
{ entity, operation },
|
|
42
|
+
...fieldPathsFor(permissions.fields, operation, '').map((field) => ({ entity, operation, field })),
|
|
43
|
+
];
|
|
44
|
+
// The field map is walked only for an operation the ENTITY grants, and that is a correctness rule
|
|
45
|
+
// rather than a saving. Payload reports a field's verdict whether or not the operation carrying it is
|
|
46
|
+
// reachable: an auth collection that denies `read` to a stranger still lists fields carrying
|
|
47
|
+
// `read: true`, because the field rule genuinely says yes and the collection rule is what stops the
|
|
48
|
+
// caller. Judging those would name permissions nobody holds, and a project cannot close a finding that
|
|
49
|
+
// describes no exposure - so it would close it by declaring it, which teaches exactly the wrong habit.
|
|
50
|
+
const grantsForEntity = (entity, permissions) => JUDGED_OPERATIONS.filter((operation) => isPermitted(permissions[operation])).flatMap((operation) => grantsForOperation(entity, permissions, operation));
|
|
51
|
+
const grantsIn = (entities) => Object.entries(entities).flatMap(([entity, permissions]) => grantsForEntity(entity, permissions));
|
|
10
52
|
/** Every permission an access report grants, across collections and globals alike. */
|
|
11
53
|
export const grantedPermissions = (report) => [
|
|
12
54
|
...grantsIn(report.collections ?? {}),
|
|
13
55
|
...grantsIn(report.globals ?? {}),
|
|
14
56
|
];
|
|
15
57
|
/** How one grant is named, both in a report and in the `publicAccess` entry that would declare it. */
|
|
16
|
-
export const describeGrant = (granted) =>
|
|
17
|
-
|
|
58
|
+
export const describeGrant = (granted) => granted.field === undefined
|
|
59
|
+
? `${granted.entity}.${granted.operation}`
|
|
60
|
+
: `${granted.entity}.${granted.operation}${PATH_SEPARATOR}${granted.field}`;
|
|
61
|
+
// A field is matched by name and never by a wildcard the project could write for itself. An entry
|
|
62
|
+
// listing `*` covers only the grant Payload itself collapsed to `*`, where every field is open and the
|
|
63
|
+
// declaration is therefore exact rather than blanket. Nothing here lets one declaration stand for a
|
|
64
|
+
// field added later, which is the whole reason the field half is judged at all.
|
|
65
|
+
const coversField = (entry, field) => (entry.fields ?? []).includes(field);
|
|
66
|
+
const isDeclared = (granted, declared) => declared.some((entry) => {
|
|
67
|
+
if (entry.entity !== granted.entity || entry.operation !== granted.operation) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return granted.field === undefined ? true : coversField(entry, granted.field);
|
|
71
|
+
});
|
|
18
72
|
/**
|
|
19
73
|
* The grants the running application makes to a stranger that the project has not recorded. Empty is
|
|
20
74
|
* the only passing answer: a project that declares nothing is judged most strictly, and a declaration
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-boundary.js","sourceRoot":"","sources":["../src/access-boundary.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"access-boundary.js","sourceRoot":"","sources":["../src/access-boundary.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type SpecSource } from './axe-coverage.js';
|
|
2
|
+
import type { PayloadViolation } from './payload-source.js';
|
|
3
|
+
/** A custom admin view a Payload configuration declares, and where it declares it. */
|
|
4
|
+
export interface DeclaredAdminView {
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly line: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The custom admin views a Payload configuration declares.
|
|
10
|
+
*
|
|
11
|
+
* Both `admin.components.views` and a collection's own are found, because both produce a route the
|
|
12
|
+
* pinned sweep will not reach and both are the project's markup.
|
|
13
|
+
* @param source the file's text.
|
|
14
|
+
* @returns one entry per declared view path, in the order the file declares them.
|
|
15
|
+
*/
|
|
16
|
+
export declare const findDeclaredAdminViews: (source: string) => readonly DeclaredAdminView[];
|
|
17
|
+
/**
|
|
18
|
+
* The declared views that no specification scans for accessibility.
|
|
19
|
+
* @param views the views a configuration declares.
|
|
20
|
+
* @param specs the project's test files, which is where a scan would be written.
|
|
21
|
+
* @param everyFile every file a spec may import a scan from, the specs included.
|
|
22
|
+
* @returns one violation per view nothing looks at.
|
|
23
|
+
*/
|
|
24
|
+
export declare const findUnscannedAdminViews: (views: readonly DeclaredAdminView[], specs: readonly SpecSource[], everyFile: readonly SpecSource[]) => readonly PayloadViolation[];
|
|
25
|
+
//# sourceMappingURL=admin-view-coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-view-coverage.d.ts","sourceRoot":"","sources":["../src/admin-view-coverage.ts"],"names":[],"mappings":"AAiBA,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAG3D,sFAAsF;AACtF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAsCD;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAAI,QAAQ,MAAM,KAAG,SAAS,iBAAiB,EAejF,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAClC,OAAO,SAAS,iBAAiB,EAAE,EACnC,OAAO,SAAS,UAAU,EAAE,EAC5B,WAAW,SAAS,UAAU,EAAE,KAC/B,SAAS,gBAAgB,EA6BxB,CAAA"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Custom admin views, and the accessibility hole they sit in.
|
|
2
|
+
//
|
|
3
|
+
// The pinned sweep skips `/admin` unconditionally, and correctly: the panel is Payload's markup rather
|
|
4
|
+
// than the project's, and the crawl carries no credential to sign in with. A CUSTOM admin view is the
|
|
5
|
+
// one thing inside that exemption the project wrote itself, and nothing else in the harness will ever
|
|
6
|
+
// look at it. `jsx-a11y` reads the source, so it cannot see contrast, focus order, or a landmark whose
|
|
7
|
+
// role depends on where the framework's template put the markup - and that last one is not
|
|
8
|
+
// hypothetical: a bare `header` in a custom view becomes a second `banner` beside the panel's own,
|
|
9
|
+
// which is invisible until something renders it.
|
|
10
|
+
//
|
|
11
|
+
// ploaness cannot do the scanning. It cannot sign in, it does not know which container belongs to the
|
|
12
|
+
// project rather than to Payload, and a scan of the whole panel would report defects in components
|
|
13
|
+
// nobody working in that project can repair. What it can do is refuse to let the view go unlooked at.
|
|
14
|
+
// The configuration says a custom view exists; a spec has to say something scans it.
|
|
15
|
+
//
|
|
16
|
+
// This is the same shape as the anonymous-grant rule: the framework will happily leave a decision
|
|
17
|
+
// unmade, so the project is required to have made it somewhere a check can see.
|
|
18
|
+
import { containsRoute, reachesAxe } from './axe-coverage.js';
|
|
19
|
+
import { balancedArguments, lineOf, stripComments } from './source-text.js';
|
|
20
|
+
// `components` then `views`, each read as the block it opens rather than as text found anywhere in the
|
|
21
|
+
// file. Nesting the two is what tells Payload's own vocabulary apart from a project's: a `views` key
|
|
22
|
+
// that is not inside a `components` block is not a custom admin view.
|
|
23
|
+
const BLOCK_PATTERNS = [
|
|
24
|
+
String.raw `\bcomponents\s*:\s*\{`,
|
|
25
|
+
String.raw `\bviews\s*:\s*\{`,
|
|
26
|
+
];
|
|
27
|
+
const VIEW_PATH = /\bpath\s*:\s*['"](\/[^'"]*)['"]/gu;
|
|
28
|
+
// The brace-delimited body opened at `at`, carrying the offset it starts at so that a match inside it
|
|
29
|
+
// can be reported at its line in the whole file rather than at its line in the fragment. The scan is
|
|
30
|
+
// `balancedArguments`, which skips string literals - a brace inside a quoted string would otherwise
|
|
31
|
+
// close the block early and hide every view declared after it.
|
|
32
|
+
const blockAt = (source, at) => {
|
|
33
|
+
const text = balancedArguments(source, at);
|
|
34
|
+
return text === undefined ? undefined : { text, start: at + 1 };
|
|
35
|
+
};
|
|
36
|
+
const blocksMatching = (blocks, pattern) => blocks.flatMap((block) => [...block.text.matchAll(new RegExp(pattern, 'gu'))].flatMap((match) => {
|
|
37
|
+
const opened = blockAt(block.text, match.index + match[0].length - 1);
|
|
38
|
+
return opened === undefined
|
|
39
|
+
? []
|
|
40
|
+
: [{ text: opened.text, start: block.start + opened.start }];
|
|
41
|
+
}));
|
|
42
|
+
/**
|
|
43
|
+
* The custom admin views a Payload configuration declares.
|
|
44
|
+
*
|
|
45
|
+
* Both `admin.components.views` and a collection's own are found, because both produce a route the
|
|
46
|
+
* pinned sweep will not reach and both are the project's markup.
|
|
47
|
+
* @param source the file's text.
|
|
48
|
+
* @returns one entry per declared view path, in the order the file declares them.
|
|
49
|
+
*/
|
|
50
|
+
export const findDeclaredAdminViews = (source) => {
|
|
51
|
+
const code = stripComments(source);
|
|
52
|
+
const views = BLOCK_PATTERNS.reduce((blocks, pattern) => blocksMatching(blocks, pattern), [{ text: code, start: 0 }]);
|
|
53
|
+
return views.flatMap((block) => [...block.text.matchAll(VIEW_PATH)].map((match) => ({
|
|
54
|
+
path: match[1] ?? '',
|
|
55
|
+
line: lineOf(code, block.start + match.index),
|
|
56
|
+
})));
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* The declared views that no specification scans for accessibility.
|
|
60
|
+
* @param views the views a configuration declares.
|
|
61
|
+
* @param specs the project's test files, which is where a scan would be written.
|
|
62
|
+
* @param everyFile every file a spec may import a scan from, the specs included.
|
|
63
|
+
* @returns one violation per view nothing looks at.
|
|
64
|
+
*/
|
|
65
|
+
export const findUnscannedAdminViews = (views, specs, everyFile) => views.flatMap((view) => {
|
|
66
|
+
const driving = specs.filter((spec) => containsRoute(spec.source, view.path));
|
|
67
|
+
if (driving.length === 0) {
|
|
68
|
+
return [
|
|
69
|
+
{
|
|
70
|
+
line: view.line,
|
|
71
|
+
rule: 'admin-view-undriven',
|
|
72
|
+
reason: `no test drives the custom admin view at "${view.path}". It is this project's own ` +
|
|
73
|
+
'markup inside the panel the accessibility sweep skips, so nothing else will ever look ' +
|
|
74
|
+
'at it: give it an end-to-end spec that scans it with axe, scoped to the view container',
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
return driving.some((spec) => reachesAxe(spec, everyFile))
|
|
79
|
+
? []
|
|
80
|
+
: [
|
|
81
|
+
{
|
|
82
|
+
line: view.line,
|
|
83
|
+
rule: 'admin-view-unscanned',
|
|
84
|
+
reason: `the custom admin view at "${view.path}" is driven by a test but nothing scans it ` +
|
|
85
|
+
'with axe. The pinned sweep skips the admin panel, so this view has no accessibility ' +
|
|
86
|
+
'coverage at all: add a scan scoped to the view container, not to the whole panel',
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
});
|
|
90
|
+
//# sourceMappingURL=admin-view-coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-view-coverage.js","sourceRoot":"","sources":["../src/admin-view-coverage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** A file the coverage question is answered from: its repository path and its text. */
|
|
2
|
+
export interface SpecSource {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly source: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Whether a specification names one route.
|
|
8
|
+
*
|
|
9
|
+
* A route is a prefix of every longer route beneath it, so a bare `includes` would let a spec for
|
|
10
|
+
* `/calendar-archive` answer for `/calendar`. The character after the match has to end the route.
|
|
11
|
+
* @param source the specification's text.
|
|
12
|
+
* @param route the route being looked for.
|
|
13
|
+
* @returns true when the text drives that route and not merely one whose name starts the same way.
|
|
14
|
+
*/
|
|
15
|
+
export declare const containsRoute: (source: string, route: string) => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Whether a specification BUILDS a URL beneath one route prefix.
|
|
18
|
+
*
|
|
19
|
+
* The evidence a dynamic route needs, and deliberately narrower than `containsRoute`. A spec cannot
|
|
20
|
+
* contain `/play/[id]`, because that is a file path rather than an address: what it contains is
|
|
21
|
+
* `/play/${gameId}`. Asking merely for the prefix would let a spec that visits `/play` answer for
|
|
22
|
+
* `/play/[id]`, which is the false pass this whole family of rules exists to avoid - so what is
|
|
23
|
+
* required is the prefix, a separator, and an interpolation, which together mean the spec computed an
|
|
24
|
+
* address rather than typed one.
|
|
25
|
+
* @param source the specification's text.
|
|
26
|
+
* @param prefix the static part of the route, before its first dynamic segment.
|
|
27
|
+
* @returns true when the text builds a URL below that prefix.
|
|
28
|
+
*/
|
|
29
|
+
export declare const containsBuiltRoute: (source: string, prefix: string) => boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Whether a specification scans with axe, directly or through a helper it imports.
|
|
32
|
+
*
|
|
33
|
+
* One hop, deliberately. A spec that scans through a helper is the ordinary arrangement and has to be
|
|
34
|
+
* recognised; a chain deeper than that is a project hiding its own scan from itself, and following it
|
|
35
|
+
* would trade a rule anyone can predict for one nobody can.
|
|
36
|
+
* @param spec the specification being judged.
|
|
37
|
+
* @param everyFile every file it may import a scan from, the specifications included.
|
|
38
|
+
* @returns true when running that spec reaches axe.
|
|
39
|
+
*/
|
|
40
|
+
export declare const reachesAxe: (spec: SpecSource, everyFile: readonly SpecSource[]) => boolean;
|
|
41
|
+
//# sourceMappingURL=axe-coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"axe-coverage.d.ts","sourceRoot":"","sources":["../src/axe-coverage.ts"],"names":[],"mappings":"AAgBA,uFAAuF;AACvF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AA0CD;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GAAI,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,OACmB,CAAA;AAEjF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,MAAM,EAAE,QAAQ,MAAM,KAAG,OACD,CAAA;AA4BnE;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,UAAU,EAAE,WAAW,SAAS,UAAU,EAAE,KAAG,OAI7E,CAAA"}
|