@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/contract.ts
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `binclusive.json` — the per-repo accessibility contract.
|
|
3
|
+
*
|
|
4
|
+
* This is the customer's committed source of truth: the detected stack, which
|
|
5
|
+
* WCAG SC the team blocks vs. warns on, and the team's own learned rules. The
|
|
6
|
+
* AI tools (and the generated AGENTS.md / CLAUDE.md block) consume it; nothing
|
|
7
|
+
* here is ever sent off the machine, and NO secret/API key ever lands in this
|
|
8
|
+
* file — it is committed to the customer's git.
|
|
9
|
+
*
|
|
10
|
+
* Disk reads are boundary-parsed: the file is loaded as `unknown` and narrowed
|
|
11
|
+
* by {@link parseContract}, which fails LOUD on a malformed file rather than
|
|
12
|
+
* smuggling `any` inward (same discipline as `corpus.ts`).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** The only schema version we emit/accept today. Bump on a breaking shape change. */
|
|
16
|
+
export const CONTRACT_VERSION = 1 as const;
|
|
17
|
+
|
|
18
|
+
/** Which Next.js router the repo uses, or `null` when not a Next app. */
|
|
19
|
+
export type Router = "app" | "pages" | null;
|
|
20
|
+
|
|
21
|
+
/** Source language of the repo, decided by tsconfig presence. */
|
|
22
|
+
export type Language = "ts" | "js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The detected stack. Every field is a best-effort signal from
|
|
26
|
+
* `package.json` + on-disk layout; see `detectStack` for what each draws on.
|
|
27
|
+
* `designSystem` is `"custom"` when no dominant component-source module wins.
|
|
28
|
+
*/
|
|
29
|
+
export interface Stack {
|
|
30
|
+
readonly framework: string;
|
|
31
|
+
readonly router: Router;
|
|
32
|
+
readonly designSystem: string;
|
|
33
|
+
readonly language: Language;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Enforcement policy, keyed by WCAG SC string. `block` SC fail the build;
|
|
38
|
+
* `warn` SC surface but don't. An SC should appear in at most one list — the
|
|
39
|
+
* parser de-dupes `warn` against `block` so `block` always wins.
|
|
40
|
+
*/
|
|
41
|
+
export interface Enforcement {
|
|
42
|
+
readonly block: readonly string[];
|
|
43
|
+
readonly warn: readonly string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A team-authored rule, appended via `learn`. `id` is a slug derived from the
|
|
48
|
+
* rule text; `addedAt` is an ISO timestamp; `fix`/`source` are optional context.
|
|
49
|
+
*/
|
|
50
|
+
export interface LearnedRule {
|
|
51
|
+
readonly id: string;
|
|
52
|
+
readonly rule: string;
|
|
53
|
+
readonly wcag: readonly string[];
|
|
54
|
+
readonly fix: string | null;
|
|
55
|
+
readonly source: string;
|
|
56
|
+
readonly addedAt: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The customer's escape-hatch declarations — what auto-detection cannot reach.
|
|
61
|
+
*
|
|
62
|
+
* Auto-detect handles the deterministically-knowable (registry + source-trace);
|
|
63
|
+
* everything here is what the customer DECLARES because the checker is blind to
|
|
64
|
+
* it. Every field is OPTIONAL: a zero-config repo carries none of this and still
|
|
65
|
+
* gets the full auto-detected scan. Each field is parsed leniently — a single
|
|
66
|
+
* malformed entry is dropped (with a warning), the rest still load. A bad line
|
|
67
|
+
* never hard-fails the whole config; that would punish the escape hatch.
|
|
68
|
+
*/
|
|
69
|
+
export interface Declarations {
|
|
70
|
+
/**
|
|
71
|
+
* Manual wrapper->host map: a component name jsx-a11y should treat as a host
|
|
72
|
+
* primitive, e.g. `{ "Button": "button", "FancyLink": "a" }`. Fills gaps the
|
|
73
|
+
* tracer can't (host hidden behind library indirection) and OVERRIDES the
|
|
74
|
+
* derived map on conflict — the customer's word wins over inference.
|
|
75
|
+
*
|
|
76
|
+
* A compound/namespaced member is declared by the dotted name as it appears in
|
|
77
|
+
* JSX: `{ "Dialog.Close": "button", "Tabs.Tab": "button" }` maps exactly that
|
|
78
|
+
* member. A bare leaf (`{ "Close": "button" }`) also resolves, but matches
|
|
79
|
+
* EVERY `*.Close` member — the dotted form is the precise way to scope it to
|
|
80
|
+
* one wrapper. (See `resolveComponents`' declared-lookup step for the lookup
|
|
81
|
+
* order: full dotted name first, then leaf fallback.)
|
|
82
|
+
*/
|
|
83
|
+
readonly components: Readonly<Record<string, string>>;
|
|
84
|
+
/**
|
|
85
|
+
* Component names whose children are injected at RUNTIME (the customer's own
|
|
86
|
+
* `<Trans>`-like helpers). Fed into the suppression pass so content-family
|
|
87
|
+
* findings on these elements are dropped, exactly like the built-in `<Trans>`.
|
|
88
|
+
*/
|
|
89
|
+
readonly injectsChildren: readonly string[];
|
|
90
|
+
/**
|
|
91
|
+
* File globs and/or jsx-a11y rule ids to skip. A glob match drops the whole
|
|
92
|
+
* file from the scan; a rule-id match (with or without the `jsx-a11y/` prefix)
|
|
93
|
+
* drops every finding for that rule.
|
|
94
|
+
*/
|
|
95
|
+
readonly ignore: readonly string[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The whole `binclusive.json` document. */
|
|
99
|
+
export interface Contract {
|
|
100
|
+
readonly version: typeof CONTRACT_VERSION;
|
|
101
|
+
readonly stack: Stack;
|
|
102
|
+
readonly enforcement: Enforcement;
|
|
103
|
+
readonly learned: readonly LearnedRule[];
|
|
104
|
+
/** Customer escape-hatch declarations; always present, defaults are empty. */
|
|
105
|
+
readonly declarations: Declarations;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Thrown when `binclusive.json` is present but malformed. Fails loud. */
|
|
109
|
+
export class ContractParseError extends Error {
|
|
110
|
+
constructor(message: string) {
|
|
111
|
+
super(`binclusive.json is malformed: ${message}`);
|
|
112
|
+
this.name = "ContractParseError";
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isObject(v: unknown): v is Record<string, unknown> {
|
|
117
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Narrow an array of unknown to `string[]`, rejecting any non-string element. */
|
|
121
|
+
function parseStringArray(v: unknown, field: string): string[] {
|
|
122
|
+
if (!Array.isArray(v)) throw new ContractParseError(`${field} must be an array`);
|
|
123
|
+
const out: string[] = [];
|
|
124
|
+
for (const el of v) {
|
|
125
|
+
if (typeof el !== "string") throw new ContractParseError(`${field}[] must be strings`);
|
|
126
|
+
out.push(el);
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function parseRouter(v: unknown): Router {
|
|
132
|
+
if (v === null || v === "app" || v === "pages") return v;
|
|
133
|
+
throw new ContractParseError(`stack.router must be "app", "pages", or null`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parseLanguage(v: unknown): Language {
|
|
137
|
+
if (v === "ts" || v === "js") return v;
|
|
138
|
+
throw new ContractParseError(`stack.language must be "ts" or "js"`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parseStack(v: unknown): Stack {
|
|
142
|
+
if (!isObject(v)) throw new ContractParseError("stack must be an object");
|
|
143
|
+
const { framework, router, designSystem, language } = v;
|
|
144
|
+
if (typeof framework !== "string")
|
|
145
|
+
throw new ContractParseError("stack.framework must be a string");
|
|
146
|
+
if (typeof designSystem !== "string")
|
|
147
|
+
throw new ContractParseError("stack.designSystem must be a string");
|
|
148
|
+
return {
|
|
149
|
+
framework,
|
|
150
|
+
router: parseRouter(router),
|
|
151
|
+
designSystem,
|
|
152
|
+
language: parseLanguage(language),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function parseEnforcement(v: unknown): Enforcement {
|
|
157
|
+
if (!isObject(v)) throw new ContractParseError("enforcement must be an object");
|
|
158
|
+
const block = parseStringArray(v.block, "enforcement.block");
|
|
159
|
+
const warnRaw = parseStringArray(v.warn, "enforcement.warn");
|
|
160
|
+
// block wins: an SC can't be both blocked and warned.
|
|
161
|
+
const blockSet = new Set(block);
|
|
162
|
+
const warn = warnRaw.filter((sc) => !blockSet.has(sc));
|
|
163
|
+
return { block, warn };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The empty escape-hatch block: what a zero-config contract carries. */
|
|
167
|
+
export function emptyDeclarations(): Declarations {
|
|
168
|
+
return { components: {}, injectsChildren: [], ignore: [] };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Valid intrinsic tag token: a single lowercase tag like `button` or `a`. */
|
|
172
|
+
const VALID_HOST_RE = /^[a-z][a-z0-9-]*$/;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Validate a raw `components` map (before it is filtered by the parser).
|
|
176
|
+
* Returns one human-readable diagnostic string per invalid host entry; an
|
|
177
|
+
* empty array means all hosts are valid. A valid host is a single lowercase
|
|
178
|
+
* intrinsic tag token (`/^[a-z][a-z0-9-]*$/` — no `|`, no spaces, no upper).
|
|
179
|
+
*
|
|
180
|
+
* Pure helper — does NOT mutate the map and does NOT print anything. Used by
|
|
181
|
+
* tests to assert the diagnostic messages without invoking the parser.
|
|
182
|
+
* In production the same checks run inside `parseComponentsLenient` and print
|
|
183
|
+
* via `warnContract` (stderr) during contract loading.
|
|
184
|
+
*/
|
|
185
|
+
export function validateDeclaredHosts(
|
|
186
|
+
components: Readonly<Record<string, string>>,
|
|
187
|
+
): string[] {
|
|
188
|
+
const diagnostics: string[] = [];
|
|
189
|
+
for (const [name, host] of Object.entries(components)) {
|
|
190
|
+
if (VALID_HOST_RE.test(host)) continue;
|
|
191
|
+
if (host.includes("|")) {
|
|
192
|
+
diagnostics.push(
|
|
193
|
+
`binclusive.json: "${name}" host "${host}" is the un-edited declare hint — pick ONE host, e.g. "${name}": "button".`,
|
|
194
|
+
);
|
|
195
|
+
} else {
|
|
196
|
+
diagnostics.push(
|
|
197
|
+
`binclusive.json: "${name}" host "${host}" is not a valid intrinsic tag (must be a single lowercase tag like "button" or "a") — entry ignored.`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return diagnostics;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Emit a non-fatal warning for a malformed OPTIONAL field. Optional fields are
|
|
206
|
+
* the escape hatch — a bad entry degrades gracefully (skip it, keep the rest)
|
|
207
|
+
* rather than crashing the scan, so the warning is the only signal. Routed
|
|
208
|
+
* through one helper so the channel (stderr) is consistent and testable.
|
|
209
|
+
*/
|
|
210
|
+
function warnContract(message: string): void {
|
|
211
|
+
console.warn(`binclusive.json: ${message} — ignored.`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Parse the optional `components` map leniently: keep only `string -> string`
|
|
216
|
+
* entries where the value is a valid intrinsic tag token. Drops (with a
|
|
217
|
+
* warning) any entry whose value isn't a string, is empty, or isn't a single
|
|
218
|
+
* lowercase tag. A non-object value for the whole field is dropped entirely.
|
|
219
|
+
* Absent → `{}`.
|
|
220
|
+
*/
|
|
221
|
+
function parseComponentsLenient(v: unknown): Record<string, string> {
|
|
222
|
+
if (v === undefined) return {};
|
|
223
|
+
if (!isObject(v)) {
|
|
224
|
+
warnContract("components must be an object of name->host strings");
|
|
225
|
+
return {};
|
|
226
|
+
}
|
|
227
|
+
const out: Record<string, string> = {};
|
|
228
|
+
for (const [name, host] of Object.entries(v)) {
|
|
229
|
+
if (typeof host !== "string" || host === "") {
|
|
230
|
+
warnContract(`components["${name}"] must be a non-empty host string`);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (!VALID_HOST_RE.test(host)) {
|
|
234
|
+
if (host.includes("|")) {
|
|
235
|
+
warnContract(
|
|
236
|
+
`"${name}" host "${host}" is the un-edited declare hint — pick ONE host, e.g. "${name}": "button"`,
|
|
237
|
+
);
|
|
238
|
+
} else {
|
|
239
|
+
warnContract(
|
|
240
|
+
`"${name}" host "${host}" is not a valid intrinsic tag (must be a single lowercase tag like "button" or "a")`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
out[name] = host;
|
|
246
|
+
}
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Parse an optional `string[]` leniently: keep the string elements, drop
|
|
252
|
+
* (with a warning) any non-string, and drop the whole field if it isn't an
|
|
253
|
+
* array. Absent → `[]`. Used by both `injectsChildren` and `ignore`.
|
|
254
|
+
*/
|
|
255
|
+
function parseStringArrayLenient(v: unknown, field: string): string[] {
|
|
256
|
+
if (v === undefined) return [];
|
|
257
|
+
if (!Array.isArray(v)) {
|
|
258
|
+
warnContract(`${field} must be an array of strings`);
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
const out: string[] = [];
|
|
262
|
+
for (const el of v) {
|
|
263
|
+
if (typeof el !== "string" || el === "") {
|
|
264
|
+
warnContract(`${field}[] entries must be non-empty strings`);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
out.push(el);
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Parse the optional escape-hatch declarations. Every sub-field degrades
|
|
274
|
+
* gracefully — a malformed `components`/`injectsChildren`/`ignore` is reduced to
|
|
275
|
+
* its valid entries (or empty), never thrown. The block is always returned
|
|
276
|
+
* (empty when absent) so the rest of the pipeline never branches on presence.
|
|
277
|
+
*/
|
|
278
|
+
function parseDeclarations(raw: Record<string, unknown>): Declarations {
|
|
279
|
+
return {
|
|
280
|
+
components: parseComponentsLenient(raw.components),
|
|
281
|
+
injectsChildren: parseStringArrayLenient(raw.injectsChildren, "injectsChildren"),
|
|
282
|
+
ignore: parseStringArrayLenient(raw.ignore, "ignore"),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function parseLearnedRule(v: unknown, idx: number): LearnedRule {
|
|
287
|
+
if (!isObject(v)) throw new ContractParseError(`learned[${idx}] must be an object`);
|
|
288
|
+
const { id, rule, wcag, fix, source, addedAt } = v;
|
|
289
|
+
if (typeof id !== "string") throw new ContractParseError(`learned[${idx}].id must be a string`);
|
|
290
|
+
if (typeof rule !== "string")
|
|
291
|
+
throw new ContractParseError(`learned[${idx}].rule must be a string`);
|
|
292
|
+
if (typeof source !== "string")
|
|
293
|
+
throw new ContractParseError(`learned[${idx}].source must be a string`);
|
|
294
|
+
if (typeof addedAt !== "string")
|
|
295
|
+
throw new ContractParseError(`learned[${idx}].addedAt must be a string`);
|
|
296
|
+
if (fix !== null && typeof fix !== "string")
|
|
297
|
+
throw new ContractParseError(`learned[${idx}].fix must be a string or null`);
|
|
298
|
+
return {
|
|
299
|
+
id,
|
|
300
|
+
rule,
|
|
301
|
+
wcag: parseStringArray(wcag, `learned[${idx}].wcag`),
|
|
302
|
+
fix: fix ?? null,
|
|
303
|
+
source,
|
|
304
|
+
addedAt,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Parse a `binclusive.json` document loaded as `unknown` from disk. Throws
|
|
310
|
+
* {@link ContractParseError} on any structural violation. The version is
|
|
311
|
+
* checked exactly: an unknown version is a hard error, not a silent upgrade.
|
|
312
|
+
*/
|
|
313
|
+
export function parseContract(raw: unknown): Contract {
|
|
314
|
+
if (!isObject(raw)) throw new ContractParseError("top level must be an object");
|
|
315
|
+
if (raw.version !== CONTRACT_VERSION)
|
|
316
|
+
throw new ContractParseError(`version must be ${CONTRACT_VERSION}, got ${String(raw.version)}`);
|
|
317
|
+
const learnedRaw = raw.learned;
|
|
318
|
+
if (!Array.isArray(learnedRaw)) throw new ContractParseError("learned must be an array");
|
|
319
|
+
return {
|
|
320
|
+
version: CONTRACT_VERSION,
|
|
321
|
+
stack: parseStack(raw.stack),
|
|
322
|
+
enforcement: parseEnforcement(raw.enforcement),
|
|
323
|
+
learned: learnedRaw.map(parseLearnedRule),
|
|
324
|
+
declarations: parseDeclarations(raw),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Serialize a contract to the canonical on-disk string (2-space, trailing
|
|
330
|
+
* newline). The escape-hatch declarations are written FLAT at the top level
|
|
331
|
+
* (`components` / `injectsChildren` / `ignore`) — the customer edits them by
|
|
332
|
+
* hand, so they read as first-class fields, not a nested `declarations` blob.
|
|
333
|
+
* Each is emitted ONLY when non-empty, so a zero-config contract stays minimal
|
|
334
|
+
* and a re-`init` never injects empty escape-hatch keys into a clean file.
|
|
335
|
+
*/
|
|
336
|
+
export function serializeContract(contract: Contract): string {
|
|
337
|
+
const { declarations, ...rest } = contract;
|
|
338
|
+
const doc: Record<string, unknown> = { ...rest };
|
|
339
|
+
if (Object.keys(declarations.components).length > 0) doc.components = declarations.components;
|
|
340
|
+
if (declarations.injectsChildren.length > 0) doc.injectsChildren = declarations.injectsChildren;
|
|
341
|
+
if (declarations.ignore.length > 0) doc.ignore = declarations.ignore;
|
|
342
|
+
return `${JSON.stringify(doc, null, 2)}\n`;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* The default enforcement policy for a fresh contract: the corpus's
|
|
347
|
+
* very-common SC block the build; the rest of the mapped SC warn. Passed the
|
|
348
|
+
* full set of SC the checker knows about, split by whether each is very-common.
|
|
349
|
+
*/
|
|
350
|
+
export function defaultEnforcement(
|
|
351
|
+
veryCommon: readonly string[],
|
|
352
|
+
rest: readonly string[],
|
|
353
|
+
): Enforcement {
|
|
354
|
+
return { block: [...veryCommon], warn: [...rest] };
|
|
355
|
+
}
|