@adia-ai/adia-ui-forge 0.8.60 → 0.8.61
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +40 -0
- package/package.json +1 -1
- package/plugin.yaml +1 -1
- package/scripts/lint-rules.generated.mjs +463 -137
- package/skills/a2ui-maintenance/references/eval-diagnostics.md +2 -2
- package/skills/a2ui-maintenance/references/pipeline-overview.md +3 -1
- package/skills/a2ui-maintenance/references/strategy-engines.md +12 -0
- package/skills/demo-audit/references/admin-shell-anatomy.md +18 -12
- package/skills/package-release/scripts/dispatch-publish.mjs +8 -0
- package/skills/package-release/scripts/gate-roster.mjs +4 -4
- package/skills/package-release/scripts/release-pack.mjs +62 -24
- package/skills/package-release/scripts/stale-copy-warning.mjs +215 -0
- package/skills/package-release/scripts/tag-lockstep.mjs +8 -0
- package/skills/primitive-authoring/references/INDEX.md +1 -0
- package/skills/primitive-authoring/references/examples-structure.md +52 -0
- package/skills/primitive-authoring/references/shell-patterns.md +12 -11
- package/skills/primitive-authoring/references/token-contract.md +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// GENERATED, do not hand-edit. Sources:
|
|
2
|
-
// scripts/lint/engine/primitives.mjs, scripts/lint/engine/run.mjs
|
|
2
|
+
// scripts/lint/engine/primitives.mjs, scripts/lint/engine/run.mjs, scripts/lint/engine/baseline.mjs
|
|
3
3
|
// scripts/lint/rules/consumer/genui-doc.mjs
|
|
4
4
|
// scripts/lint/rules/consumer/hardcoded-open.mjs
|
|
5
5
|
// scripts/lint/rules/consumer/llm-key-in-client.mjs
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
// scripts/lint/rules/generated/composition/admin-content.mjs
|
|
16
16
|
// scripts/lint/rules/generated/composition/admin-entity-item.mjs
|
|
17
17
|
// scripts/lint/rules/generated/composition/admin-roster.mjs
|
|
18
|
-
// scripts/lint/rules/generated/composition/admin-scroll.mjs
|
|
19
18
|
// scripts/lint/rules/generated/composition/admin-settings.mjs
|
|
20
19
|
// scripts/lint/rules/generated/composition/admin-shell.mjs
|
|
21
20
|
// scripts/lint/rules/generated/composition/admin-sidebar.mjs
|
|
@@ -219,8 +218,24 @@ function lineOf(text, offset) {
|
|
|
219
218
|
return n;
|
|
220
219
|
}
|
|
221
220
|
|
|
222
|
-
/**
|
|
223
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Suppression comment recognized on the line itself or the line before a finding.
|
|
223
|
+
*
|
|
224
|
+
* gh#3715: the reason separator accepts an em dash, a colon, or a
|
|
225
|
+
* space-padded hyphen. It used to accept ONLY the em dash, which collided
|
|
226
|
+
* head-on with `check:em-dash-added-lines` (gh#3767): that gate forbids an
|
|
227
|
+
* em dash on any added line, so obeying it made a reasoned suppression
|
|
228
|
+
* impossible to write. Worse, the failure was silent rather than loud. A
|
|
229
|
+
* hyphen did not merely lose the reason, it made the WHOLE directive fail
|
|
230
|
+
* to match, so the suppression quietly did nothing and the finding stayed
|
|
231
|
+
* live with no diagnostic anywhere.
|
|
232
|
+
*
|
|
233
|
+
* The hyphen form requires whitespace on both sides. Rule ids contain
|
|
234
|
+
* hyphens themselves (SHELL-RESIZE), and the id class is greedy, so a bare
|
|
235
|
+
* `-` with no padding would be ambiguous with the id it follows.
|
|
236
|
+
*/
|
|
237
|
+
const SUPPRESS_RE =
|
|
238
|
+
/\/\*\s*adia-lint-disable\s+([A-Z0-9-]+)(?:(?:\s*[\u2014:]\s*|\s+-\s+)(.*?))?\s*\*\//;
|
|
224
239
|
|
|
225
240
|
/** Given all raw lines and a finding's line number, is it suppressed, and was a reason given? */
|
|
226
241
|
function suppressionFor(rawLines, lineNo, ruleId) {
|
|
@@ -251,6 +266,47 @@ function allSuppressions(rawLines) {
|
|
|
251
266
|
return out;
|
|
252
267
|
}
|
|
253
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Loose match for anything that READS as a suppression-directive comment:
|
|
271
|
+
* opens `/* adia-lint-disable`, closes on the same line, regardless of
|
|
272
|
+
* whether its body satisfies SUPPRESS_RE's own strict grammar. Deliberately
|
|
273
|
+
* permissive: the only anchor is the shared prefix both regexes start on,
|
|
274
|
+
* so any separator/id/reason shape SUPPRESS_RE would refuse still gets
|
|
275
|
+
* caught here.
|
|
276
|
+
*/
|
|
277
|
+
const LOOSE_SUPPRESS_RE = /\/\*\s*adia-lint-disable\b.*?\*\//g;
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* gh#4161: SUPPRESS_RE is both matcher and detector, so a directive it
|
|
281
|
+
* refuses produces no suppression, no SUPPRESS-NO-REASON, and no
|
|
282
|
+
* diagnostic anywhere, silently invisible. gh#3868 fixed the hyphen-
|
|
283
|
+
* separator case and gh#3871 swept existing inert directives; neither
|
|
284
|
+
* closed the general class. This is the loose scan that closes it: every
|
|
285
|
+
* candidate suppression-directive comment (per LOOSE_SUPPRESS_RE above)
|
|
286
|
+
* that the STRICT regex refuses to parse, one entry per hit, naming the
|
|
287
|
+
* line and a trimmed snippet, so the engine (run.mjs) can turn each into a
|
|
288
|
+
* SUPPRESS-MALFORMED (warn) finding.
|
|
289
|
+
*
|
|
290
|
+
* `strictRe` is injectable (defaults to the live SUPPRESS_RE) so a test can
|
|
291
|
+
* prove this scan against a narrower, superseded grammar too, e.g.
|
|
292
|
+
* gh#3868's own pre-fix, em-dash-only regex, not only today's.
|
|
293
|
+
*/
|
|
294
|
+
function malformedSuppressions(rawLines, strictRe = SUPPRESS_RE) {
|
|
295
|
+
const out = [];
|
|
296
|
+
rawLines.forEach((line, idx) => {
|
|
297
|
+
LOOSE_SUPPRESS_RE.lastIndex = 0;
|
|
298
|
+
let m;
|
|
299
|
+
while ((m = LOOSE_SUPPRESS_RE.exec(line))) {
|
|
300
|
+
const candidate = m[0];
|
|
301
|
+
const re = new RegExp(strictRe.source);
|
|
302
|
+
if (!re.test(candidate)) {
|
|
303
|
+
out.push({ lineNo: idx + 1, snippet: candidate.trim().slice(0, 90) });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
|
|
254
310
|
const RE = {
|
|
255
311
|
HEXCOLOR: /#[0-9a-fA-F]{3,8}\b/,
|
|
256
312
|
FUNCCOLOR: /\b(?:rgba?|hsla?|oklch|oklab|lab|lch)\s*\(/,
|
|
@@ -265,6 +321,11 @@ const RE = {
|
|
|
265
321
|
SSR_SIGNAL: /['"]use client['"]|\buseEffect\b|\bonMounted\b|\bonMount\b|from\s+['"](?:react|vue|svelte|next|nuxt|@sveltejs|astro)|getServerSideProps|defineNuxtComponent/i,
|
|
266
322
|
TOPLEVEL_IMPORT: /^\s*import\s+['"]@adia-ai\/web-components['"]\s*;?\s*$/m,
|
|
267
323
|
OVERLAY_OPEN: /<(?:modal|drawer)-ui\b[^>]*?(?<![:.\w])\bopen\b(?!\s*=\s*\{)/,
|
|
324
|
+
// gh#3888: a real @scope at-rule opening, never a bare substring match
|
|
325
|
+
// on the word "@scope" (which also matches prose in a comment, the
|
|
326
|
+
// exact loophole this fixes: MISSING-SCOPE's old `text.includes('@scope')`
|
|
327
|
+
// passed any file whose comments merely mentioned the word).
|
|
328
|
+
SCOPE_BLOCK: /@scope\s*\(/,
|
|
268
329
|
};
|
|
269
330
|
|
|
270
331
|
/** Per-declaration split on `;` — mirrors both Python scripts' `line.split(';')` loops. */
|
|
@@ -737,6 +798,32 @@ function markupScanSurface(text, path = '') {
|
|
|
737
798
|
return surface;
|
|
738
799
|
}
|
|
739
800
|
|
|
801
|
+
const CSS_COMMENT_RE = /\/\*[\s\S]*?(?:\*\/|$)/g;
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* gh#4008: the text `RE.SCOPE_BLOCK` (MISSING-SCOPE) actually tests, BLANKING
|
|
805
|
+
* (not deleting: line numbers must survive) every `/* ... *\/` CSS comment
|
|
806
|
+
* range the same way `markupScanSurface`'s HTML_COMMENT_RE already blanks
|
|
807
|
+
* markup comments above. gh#3888 closed "`@scope` appearing only in a bare
|
|
808
|
+
* word inside a comment must not satisfy MISSING-SCOPE" by switching the
|
|
809
|
+
* rule's substring test to a real at-rule regex (`/@scope\s*\(/`), but never
|
|
810
|
+
* blanked the comment itself, so a comment that carries the FULL pattern,
|
|
811
|
+
* `/* @scope (admin-shell) *\/`, still matches the same regex the raw-text
|
|
812
|
+
* word never could. An unterminated comment blanks to end-of-file, mirroring
|
|
813
|
+
* `markupScanSurface`'s own behavior for an unterminated `<!--`.
|
|
814
|
+
*
|
|
815
|
+
* Only used where a real, live at-rule (or declaration) is what the caller
|
|
816
|
+
* means to detect, NOT a general-purpose "strip all CSS comments" helper.
|
|
817
|
+
* `isFoundationCss`'s `/* forge-lint: foundation *\/` opt-in tag and
|
|
818
|
+
* missing-scope.mjs's own `PREFIX_SCOPED_RE` are deliberately read straight
|
|
819
|
+
* out of raw text instead: those tags are AUTHORED inside a comment on
|
|
820
|
+
* purpose, so blanking comments before reading them would break the very
|
|
821
|
+
* mechanism they are.
|
|
822
|
+
*/
|
|
823
|
+
function cssCommentBlankedSurface(text) {
|
|
824
|
+
return text.replace(CSS_COMMENT_RE, (m) => m.replace(/[^\n]/g, ' '));
|
|
825
|
+
}
|
|
826
|
+
|
|
740
827
|
/**
|
|
741
828
|
* ADR-0108 D5 / LLD-0019 C5: builds lookup tables from a manifest object
|
|
742
829
|
* shaped like `packages/gen-ui/mcp/factory/manifest.js`'s `getManifest()`
|
|
@@ -1146,14 +1233,25 @@ function makeLinter(rules, opts = {}) {
|
|
|
1146
1233
|
if (rule.scope === 'forge-only' && !isForgeOnlyPath(path)) continue;
|
|
1147
1234
|
const hits = rule.match(text, path, { scopes }) || [];
|
|
1148
1235
|
for (const h of hits) {
|
|
1149
|
-
findings.push({
|
|
1236
|
+
findings.push({
|
|
1237
|
+
name: rule.id,
|
|
1238
|
+
line: h.line,
|
|
1239
|
+
snippet: h.snippet,
|
|
1240
|
+
why: h.why,
|
|
1241
|
+
...(h.severity ? { severity: h.severity } : {}),
|
|
1242
|
+
});
|
|
1150
1243
|
}
|
|
1151
1244
|
}
|
|
1152
1245
|
|
|
1153
1246
|
// Suppression syntax (LLD-0016 §Interfaces):
|
|
1154
|
-
// /* adia-lint-disable
|
|
1155
|
-
// or the line before it drops that one finding.
|
|
1156
|
-
//
|
|
1247
|
+
// /* adia-lint-disable RULE-ID: reason text */ on the finding's own line
|
|
1248
|
+
// or the line before it drops that one finding. The separator may be a
|
|
1249
|
+
// colon, a space-padded hyphen, or an em dash (SUPPRESS_RE in
|
|
1250
|
+
// primitives.mjs is the one source); the padding on the hyphen is
|
|
1251
|
+
// required because rule ids contain hyphens. Prefer the colon or the
|
|
1252
|
+
// padded hyphen when writing one, since check:em-dash-added-lines
|
|
1253
|
+
// (gh#3767) rejects an em dash on any added line. A bare disable with no
|
|
1254
|
+
// <reason> is itself a finding (SUPPRESS-NO-REASON, warn), checked
|
|
1157
1255
|
// once per suppression comment in the file, independent of whether it
|
|
1158
1256
|
// actually suppressed anything this run.
|
|
1159
1257
|
findings = findings.filter((f) => !suppressionFor(rawLines, f.line, f.name).suppressed);
|
|
@@ -1168,6 +1266,24 @@ function makeLinter(rules, opts = {}) {
|
|
|
1168
1266
|
}
|
|
1169
1267
|
}
|
|
1170
1268
|
|
|
1269
|
+
// gh#4161: SUPPRESS_RE is both matcher and detector, so a directive it
|
|
1270
|
+
// refuses produces no suppression, no SUPPRESS-NO-REASON, and no
|
|
1271
|
+
// diagnostic at all, silently invisible (gh#3868/gh#3871 each closed
|
|
1272
|
+
// one specific inert shape, never the general class). This loose scan
|
|
1273
|
+
// closes it: every adia-lint-disable-shaped comment the strict grammar
|
|
1274
|
+
// refuses to parse becomes a warn-severity SUPPRESS-MALFORMED finding
|
|
1275
|
+
// naming the file (via the caller) and line, so a malformed directive
|
|
1276
|
+
// is loud instead of vanishing.
|
|
1277
|
+
for (const s of malformedSuppressions(rawLines)) {
|
|
1278
|
+
findings.push({
|
|
1279
|
+
name: 'SUPPRESS-MALFORMED',
|
|
1280
|
+
line: s.lineNo,
|
|
1281
|
+
snippet: s.snippet,
|
|
1282
|
+
why: 'a comment mentioning adia-lint-disable that the strict directive grammar (SUPPRESS_RE) refuses to parse; it suppresses nothing and raises no other diagnostic, fix the <id>/<separator>/<reason> shape or drop the comment',
|
|
1283
|
+
severity: 'warn',
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1171
1287
|
findings.sort((a, b) => (a.line - b.line) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
1172
1288
|
return findings;
|
|
1173
1289
|
}
|
|
@@ -1185,6 +1301,130 @@ function render(path, findings) {
|
|
|
1185
1301
|
}
|
|
1186
1302
|
|
|
1187
1303
|
|
|
1304
|
+
/**
|
|
1305
|
+
* scripts/lint/engine/baseline.mjs (gh#3718): the checked-in baseline of
|
|
1306
|
+
* known SCOPE-EXTENT sites, and the normalization that keys them.
|
|
1307
|
+
*
|
|
1308
|
+
* No static `node:fs`/`node:path`/`node:url` import, same as
|
|
1309
|
+
* `scripts/lint/engine/primitives.mjs` and `scripts/lint/engine/run.mjs`,
|
|
1310
|
+
* because this module is inlined into the shared generated bank (LLD-0016
|
|
1311
|
+
* §C5) and copied byte-for-byte to 4 destinations, one of which
|
|
1312
|
+
* (anti-patterns.generated.js) is browser-reachable; check:browser-safe
|
|
1313
|
+
* forbids a top-level `node:*` import there. `process.getBuiltinModule()`
|
|
1314
|
+
* reaches the same builtins with zero import statements, undefined in a
|
|
1315
|
+
* browser, so this fails soft to "excuse nothing" there instead of throwing.
|
|
1316
|
+
*
|
|
1317
|
+
* Loaded once per process. Four outcomes, all of them chosen rather than
|
|
1318
|
+
* incidental:
|
|
1319
|
+
*
|
|
1320
|
+
* 1. No Node fs/path/url builtins (browser context). Not an error: no site
|
|
1321
|
+
* is excused, so every finding reports at the rule's own severity.
|
|
1322
|
+
* 2. No baseline file on disk. Same as (1).
|
|
1323
|
+
* 3. An unusable baseline file: unparseable, OR parseable but not an object
|
|
1324
|
+
* with an object "sites" (JSON.parse succeeds on null, on a bare string, on
|
|
1325
|
+
* a number and on an array, so "it parsed" says nothing about the shape).
|
|
1326
|
+
* Same as (1), excuse nothing, but WARN loudly naming what was wrong. It
|
|
1327
|
+
* must not throw: this is one rule's data file, and an uncaught error here
|
|
1328
|
+
* takes down the whole lint run for every other rule, which is a far worse
|
|
1329
|
+
* failure than losing the downgrades.
|
|
1330
|
+
* 4. A readable baseline. Its sites are excused, nothing else is.
|
|
1331
|
+
*
|
|
1332
|
+
* What is never an outcome is "unreadable, therefore everything is excused",
|
|
1333
|
+
* which would disable the rule silently.
|
|
1334
|
+
*/
|
|
1335
|
+
let cache = null;
|
|
1336
|
+
|
|
1337
|
+
const isPlainObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
1338
|
+
|
|
1339
|
+
const describe = (v) => (v === null ? 'null' : Array.isArray(v) ? 'an array' : typeof v);
|
|
1340
|
+
|
|
1341
|
+
function nodeBuiltins() {
|
|
1342
|
+
if (typeof process === 'undefined' || typeof process.getBuiltinModule !== 'function') return null;
|
|
1343
|
+
return {
|
|
1344
|
+
fs: process.getBuiltinModule('node:fs'),
|
|
1345
|
+
path: process.getBuiltinModule('node:path'),
|
|
1346
|
+
url: process.getBuiltinModule('node:url'),
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
function load() {
|
|
1351
|
+
if (cache) return cache;
|
|
1352
|
+
const builtins = nodeBuiltins();
|
|
1353
|
+
if (!builtins) {
|
|
1354
|
+
cache = { sites: {} };
|
|
1355
|
+
return cache;
|
|
1356
|
+
}
|
|
1357
|
+
const here = builtins.path.dirname(builtins.url.fileURLToPath(import.meta.url));
|
|
1358
|
+
const baselinePath = builtins.path.join(here, '..', 'scope-extent-baseline.json');
|
|
1359
|
+
if (!builtins.fs.existsSync(baselinePath)) {
|
|
1360
|
+
cache = { sites: {} };
|
|
1361
|
+
return cache;
|
|
1362
|
+
}
|
|
1363
|
+
try {
|
|
1364
|
+
const parsed = JSON.parse(builtins.fs.readFileSync(baselinePath, 'utf8'));
|
|
1365
|
+
// Shape validation lives INSIDE the try, deliberately. JSON.parse succeeds
|
|
1366
|
+
// on `null`, on a bare string, on a number and on an array, so a parse that
|
|
1367
|
+
// did not throw proves nothing about the shape. Validating out here is how
|
|
1368
|
+
// a file containing the four characters `null` reached `cache.sites` and
|
|
1369
|
+
// threw past the catch, crashing every other rule's run.
|
|
1370
|
+
if (!isPlainObject(parsed)) {
|
|
1371
|
+
throw new TypeError(`expected a JSON object at the top level, got ${describe(parsed)}`);
|
|
1372
|
+
}
|
|
1373
|
+
// An absent `sites` is a legitimately empty baseline (excuses nothing) and
|
|
1374
|
+
// is NOT warned about; a present-but-wrong-typed `sites` is a real mistake
|
|
1375
|
+
// and is. Warning on the empty case would train readers to ignore the
|
|
1376
|
+
// warning, which is how a loud instrument stops being read.
|
|
1377
|
+
if (parsed.sites !== undefined && !isPlainObject(parsed.sites)) {
|
|
1378
|
+
throw new TypeError(`"sites" must be an object, got ${describe(parsed.sites)}`);
|
|
1379
|
+
}
|
|
1380
|
+
cache = { ...parsed, sites: parsed.sites || {} };
|
|
1381
|
+
} catch (err) {
|
|
1382
|
+
console.warn(
|
|
1383
|
+
`[SCOPE-EXTENT] baseline at ${baselinePath} is unusable, excusing nothing: ${err.message}`,
|
|
1384
|
+
);
|
|
1385
|
+
cache = { sites: {} };
|
|
1386
|
+
}
|
|
1387
|
+
return cache;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
/**
|
|
1391
|
+
* Normalize a matched block so that reformatting does not invalidate an entry:
|
|
1392
|
+
* strip CSS comments, collapse all whitespace, drop spaces around the
|
|
1393
|
+
* punctuation that separates declarations. Editing the selector or the
|
|
1394
|
+
* declaration itself still changes the key, which is intended, and that is a real
|
|
1395
|
+
* change to a known site.
|
|
1396
|
+
*
|
|
1397
|
+
* The matcher itself can stop mid-comment (a comment mentioning a property
|
|
1398
|
+
* name like "min-width:" satisfies the same width/height text test the real
|
|
1399
|
+
* rule looks for), so a raw match can end inside an unterminated `/* ... `
|
|
1400
|
+
* with no closing marker in the captured text yet. That trailing open
|
|
1401
|
+
* comment is stripped too, same as a closed one: it carries no declaration
|
|
1402
|
+
* text, and dropping it is what keeps a baseline key from carrying prose
|
|
1403
|
+
* (comment wording, including punctuation the rest of this codebase avoids)
|
|
1404
|
+
* that has nothing to do with the actual finding.
|
|
1405
|
+
*/
|
|
1406
|
+
function normalizeMatch(raw) {
|
|
1407
|
+
return raw
|
|
1408
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
1409
|
+
.replace(/\/\*[\s\S]*$/g, '')
|
|
1410
|
+
.replace(/\s+/g, ' ')
|
|
1411
|
+
.replace(/\s*([{};:,])\s*/g, '$1')
|
|
1412
|
+
.trim();
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
/** The baselined keys for one file, as a multiset (duplicates preserved). */
|
|
1416
|
+
function baselineFor(path) {
|
|
1417
|
+
const p = (path || '').replace(/\\/g, '/');
|
|
1418
|
+
return load().sites[p] || [];
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
/** Total baselined entries. The acceptance compares this to a live count. */
|
|
1422
|
+
function baselineCount() {
|
|
1423
|
+
const { sites } = load();
|
|
1424
|
+
return Object.values(sites).reduce((n, list) => n + list.length, 0);
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
|
|
1188
1428
|
const RULES = [];
|
|
1189
1429
|
{
|
|
1190
1430
|
|
|
@@ -1428,8 +1668,11 @@ RULES.push({
|
|
|
1428
1668
|
const pages = (text.match(/<admin-page[\s/>]/g) || []).length;
|
|
1429
1669
|
const scrolls = (text.match(/<admin-scroll[\s/>]/g) || []).length;
|
|
1430
1670
|
if (scrolls && pages > scrolls) {
|
|
1671
|
+
// report the first <admin-page> occurrence, the line a developer would
|
|
1672
|
+
// actually attach an adia-lint-disable comment to for this finding.
|
|
1673
|
+
const firstIndex = text.search(/<admin-page[\s/>]/);
|
|
1431
1674
|
out.push({
|
|
1432
|
-
line:
|
|
1675
|
+
line: lineOf(text, firstIndex),
|
|
1433
1676
|
snippet: '<admin-page> × N inside <admin-scroll>',
|
|
1434
1677
|
why: 'each <admin-scroll> hosts exactly one <admin-page> — multiple pages need multiple scroll regions',
|
|
1435
1678
|
});
|
|
@@ -1449,6 +1692,8 @@ RULES.push({
|
|
|
1449
1692
|
}
|
|
1450
1693
|
|
|
1451
1694
|
{
|
|
1695
|
+
|
|
1696
|
+
|
|
1452
1697
|
const APPLIES_EXT = ['.html', '.htm', '.vue', '.svelte', '.astro', '.tsx', '.jsx', '.js', '.mjs', '.ts'];
|
|
1453
1698
|
|
|
1454
1699
|
|
|
@@ -1458,9 +1703,10 @@ RULES.push({
|
|
|
1458
1703
|
scope: 'consumer-only',
|
|
1459
1704
|
fileTypes: APPLIES_EXT,
|
|
1460
1705
|
match(text) {
|
|
1461
|
-
|
|
1706
|
+
const m = /<admin-sidebar[^>]*\bresizable\b/.exec(text);
|
|
1707
|
+
if (m && !text.includes('data-sidebar-resize')) {
|
|
1462
1708
|
return [{
|
|
1463
|
-
line:
|
|
1709
|
+
line: lineOf(text, m.index),
|
|
1464
1710
|
snippet: '<admin-sidebar resizable> without [data-sidebar-resize]',
|
|
1465
1711
|
why: '[resizable] needs a child <div data-sidebar-resize> or there is no drag handle',
|
|
1466
1712
|
}];
|
|
@@ -1528,6 +1774,29 @@ RULES.push(ssrDoubleRouter, ssrToplevelImport);
|
|
|
1528
1774
|
{
|
|
1529
1775
|
|
|
1530
1776
|
|
|
1777
|
+
// gh#3888: a second, honestly-named opt-out alongside `forge-lint:
|
|
1778
|
+
// foundation`. Some CSS intentionally scopes via a repeated parent-tag
|
|
1779
|
+
// selector prefix (`admin-shell [data-x] { … }`) instead of a real
|
|
1780
|
+
// `@scope` at-rule. Labeling that shape `forge-lint: foundation` would
|
|
1781
|
+
// be a lie (neither file is a token sheet); this tag says what the
|
|
1782
|
+
// file actually does instead.
|
|
1783
|
+
//
|
|
1784
|
+
// ALLOWLISTED, not a self-serve bypass (gh#3888 critic finding): an
|
|
1785
|
+
// undocumented, unrestricted opt-out comment is the same shape as the
|
|
1786
|
+
// substring-loophole bug this rule just fixed, only deliberate. A
|
|
1787
|
+
// file adding this comment on its own must FAIL, naming the allowlist,
|
|
1788
|
+
// so escaping MISSING-SCOPE this way always needs a human to edit this
|
|
1789
|
+
// rule module, never just a CSS comment.
|
|
1790
|
+
//
|
|
1791
|
+
// gh#3922 closed the follow-up: the two files gh#3888 allowlisted
|
|
1792
|
+
// (admin-shell.helpers.css, admin-shell.mobile-nav.css) are now real
|
|
1793
|
+
// `@scope (admin-shell) { … }` blocks and admin-shell.test.js's prefix
|
|
1794
|
+
// assertions were rewritten to match. The allowlist is empty again;
|
|
1795
|
+
// `forge-lint: prefix-scoped` stays available as a mechanism, not a
|
|
1796
|
+
// standing exemption; a future file needs a human to add it here.
|
|
1797
|
+
const PREFIX_SCOPED_RE = /forge-lint:\s*prefix-scoped/i;
|
|
1798
|
+
const PREFIX_SCOPED_ALLOWLIST = [];
|
|
1799
|
+
|
|
1531
1800
|
|
|
1532
1801
|
RULES.push({
|
|
1533
1802
|
id: 'MISSING-SCOPE',
|
|
@@ -1537,12 +1806,27 @@ RULES.push({
|
|
|
1537
1806
|
match(text, path) {
|
|
1538
1807
|
const isTokenish = isFoundationCss(path, text, 'forge-lint');
|
|
1539
1808
|
if (isTokenish) return [];
|
|
1540
|
-
|
|
1809
|
+
// gh#4008: a comment carrying the full `@scope (...)` pattern (not just
|
|
1810
|
+
// the bare word gh#3888 already excluded) must not satisfy this test:
|
|
1811
|
+
// blank CSS comments first, mirroring markupScanSurface's HTML-comment
|
|
1812
|
+
// blanking, so only a REAL, live at-rule can match.
|
|
1813
|
+
if (RE.SCOPE_BLOCK.test(cssCommentBlankedSurface(text))) return [];
|
|
1814
|
+
const normalizedPath = (path || '').replace(/\\/g, '/');
|
|
1815
|
+
const claimsPrefixScoped = PREFIX_SCOPED_RE.test(text.slice(0, 1000));
|
|
1816
|
+
const isAllowlisted = PREFIX_SCOPED_ALLOWLIST.some((p) => normalizedPath.endsWith(p));
|
|
1817
|
+
if (claimsPrefixScoped && !isAllowlisted) {
|
|
1818
|
+
return [{
|
|
1819
|
+
line: 1,
|
|
1820
|
+
snippet: '(file has CSS rules but no @scope block)',
|
|
1821
|
+
why: `forge-lint: prefix-scoped is allowlisted to zero files (gh#3922 closed the last two, gh#3888). Adding this comment does not opt a file out; either wrap it in a real @scope block, or ask for the allowlist to be extended in scripts/lint/rules/forge/missing-scope.mjs itself.`,
|
|
1822
|
+
}];
|
|
1823
|
+
}
|
|
1824
|
+
if (claimsPrefixScoped && isAllowlisted) return [];
|
|
1541
1825
|
if (!RE.SELECTOR_RULE.test(text)) return [];
|
|
1542
1826
|
return [{
|
|
1543
1827
|
line: 1,
|
|
1544
1828
|
snippet: '(file has CSS rules but no @scope block)',
|
|
1545
|
-
why: 'component CSS must be wrapped in `@scope (<tag>) { … }` so styles don\'t leak in light DOM (foundation/token sheets excepted
|
|
1829
|
+
why: 'component CSS must be wrapped in `@scope (<tag>) { … }` so styles don\'t leak in light DOM (foundation/token sheets excepted with /* forge-lint: foundation */)',
|
|
1546
1830
|
}];
|
|
1547
1831
|
},
|
|
1548
1832
|
});
|
|
@@ -1562,7 +1846,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
1562
1846
|
|
|
1563
1847
|
RULES.push({
|
|
1564
1848
|
id: "COMPOSITION-ACCORDION-ITEM-UI",
|
|
1565
|
-
severity: '
|
|
1849
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
1566
1850
|
scope: 'shared',
|
|
1567
1851
|
fileTypes: MARKUP_EXT,
|
|
1568
1852
|
match(text) {
|
|
@@ -1594,7 +1878,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
1594
1878
|
|
|
1595
1879
|
RULES.push({
|
|
1596
1880
|
id: "COMPOSITION-ACCORDION-UI",
|
|
1597
|
-
severity: '
|
|
1881
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
1598
1882
|
scope: 'shared',
|
|
1599
1883
|
fileTypes: MARKUP_EXT,
|
|
1600
1884
|
match(text) {
|
|
@@ -1627,7 +1911,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
1627
1911
|
|
|
1628
1912
|
RULES.push({
|
|
1629
1913
|
id: "COMPOSITION-ACTION-ITEM-UI",
|
|
1630
|
-
severity: '
|
|
1914
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
1631
1915
|
scope: 'shared',
|
|
1632
1916
|
fileTypes: MARKUP_EXT,
|
|
1633
1917
|
match(text) {
|
|
@@ -1659,7 +1943,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
1659
1943
|
|
|
1660
1944
|
RULES.push({
|
|
1661
1945
|
id: "COMPOSITION-ACTION-LIST-UI",
|
|
1662
|
-
severity: '
|
|
1946
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
1663
1947
|
scope: 'shared',
|
|
1664
1948
|
fileTypes: MARKUP_EXT,
|
|
1665
1949
|
match(text) {
|
|
@@ -1692,7 +1976,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
1692
1976
|
|
|
1693
1977
|
RULES.push({
|
|
1694
1978
|
id: "COMPOSITION-ADMIN-COMMAND",
|
|
1695
|
-
severity: '
|
|
1979
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
1696
1980
|
scope: 'shared',
|
|
1697
1981
|
fileTypes: MARKUP_EXT,
|
|
1698
1982
|
match(text) {
|
|
@@ -1724,7 +2008,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
1724
2008
|
|
|
1725
2009
|
RULES.push({
|
|
1726
2010
|
id: "COMPOSITION-ADMIN-CONTENT",
|
|
1727
|
-
severity: '
|
|
2011
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
1728
2012
|
scope: 'shared',
|
|
1729
2013
|
fileTypes: MARKUP_EXT,
|
|
1730
2014
|
match(text) {
|
|
@@ -1756,7 +2040,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
1756
2040
|
|
|
1757
2041
|
RULES.push({
|
|
1758
2042
|
id: "COMPOSITION-ADMIN-ENTITY-ITEM",
|
|
1759
|
-
severity: '
|
|
2043
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
1760
2044
|
scope: 'shared',
|
|
1761
2045
|
fileTypes: MARKUP_EXT,
|
|
1762
2046
|
match(text) {
|
|
@@ -1788,7 +2072,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
1788
2072
|
|
|
1789
2073
|
RULES.push({
|
|
1790
2074
|
id: "COMPOSITION-ADMIN-ROSTER-UI",
|
|
1791
|
-
severity: '
|
|
2075
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
1792
2076
|
scope: 'shared',
|
|
1793
2077
|
fileTypes: MARKUP_EXT,
|
|
1794
2078
|
match(text) {
|
|
@@ -1806,38 +2090,6 @@ RULES.push({
|
|
|
1806
2090
|
});
|
|
1807
2091
|
}
|
|
1808
2092
|
|
|
1809
|
-
{
|
|
1810
|
-
// GENERATED: do not hand-edit. Source: packages/web-modules/shell/admin-scroll/admin-scroll.yaml's a2ui.allowedParents/allowedChildren.
|
|
1811
|
-
// Rebuild: node scripts/build/gen-composition-rules.mjs
|
|
1812
|
-
// Freshness gate: node scripts/verify/check-composition-rules-fresh.mjs --verify
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
const TAG = "admin-scroll";
|
|
1816
|
-
const ALLOWED_PARENTS = ["admin-content"];
|
|
1817
|
-
const ALLOWED_CHILDREN = [];
|
|
1818
|
-
const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
RULES.push({
|
|
1822
|
-
id: "COMPOSITION-ADMIN-SCROLL",
|
|
1823
|
-
severity: 'warn', // LLD-0016 §C4: new, unproven-at-scale matcher (Risk R4): advisory until a corpus-wide rollout promotes it, same posture check-example-ids.mjs's Phase-1 rule started at.
|
|
1824
|
-
scope: 'shared',
|
|
1825
|
-
fileTypes: MARKUP_EXT,
|
|
1826
|
-
match(text) {
|
|
1827
|
-
return compositionFindings(text, { tag: TAG, allowedParents: ALLOWED_PARENTS, allowedChildren: ALLOWED_CHILDREN, allowedChildrenBySlot: ALLOWED_CHILDREN_BY_SLOT });
|
|
1828
|
-
},
|
|
1829
|
-
fix: null, // moving markup to satisfy a composition constraint is a judgment call, not a mechanical rename (LLD-0016 §Risks R4/R5)
|
|
1830
|
-
fixtures: {
|
|
1831
|
-
"smelly": [
|
|
1832
|
-
"<div><admin-scroll></admin-scroll></div>"
|
|
1833
|
-
],
|
|
1834
|
-
"clean": [
|
|
1835
|
-
"<admin-content><admin-scroll></admin-scroll></admin-content>"
|
|
1836
|
-
]
|
|
1837
|
-
},
|
|
1838
|
-
});
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
2093
|
{
|
|
1842
2094
|
// GENERATED: do not hand-edit. Source: packages/web-modules/agent-admin/admin-settings/admin-settings.yaml's a2ui.allowedParents/allowedChildren.
|
|
1843
2095
|
// Rebuild: node scripts/build/gen-composition-rules.mjs
|
|
@@ -1852,7 +2104,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
1852
2104
|
|
|
1853
2105
|
RULES.push({
|
|
1854
2106
|
id: "COMPOSITION-ADMIN-SETTINGS-UI",
|
|
1855
|
-
severity: '
|
|
2107
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
1856
2108
|
scope: 'shared',
|
|
1857
2109
|
fileTypes: MARKUP_EXT,
|
|
1858
2110
|
match(text) {
|
|
@@ -1884,7 +2136,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
1884
2136
|
|
|
1885
2137
|
RULES.push({
|
|
1886
2138
|
id: "COMPOSITION-ADMIN-SHELL",
|
|
1887
|
-
severity: '
|
|
2139
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
1888
2140
|
scope: 'shared',
|
|
1889
2141
|
fileTypes: MARKUP_EXT,
|
|
1890
2142
|
match(text) {
|
|
@@ -1916,7 +2168,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
1916
2168
|
|
|
1917
2169
|
RULES.push({
|
|
1918
2170
|
id: "COMPOSITION-ADMIN-SIDEBAR",
|
|
1919
|
-
severity: '
|
|
2171
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
1920
2172
|
scope: 'shared',
|
|
1921
2173
|
fileTypes: MARKUP_EXT,
|
|
1922
2174
|
match(text) {
|
|
@@ -1948,7 +2200,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
1948
2200
|
|
|
1949
2201
|
RULES.push({
|
|
1950
2202
|
id: "COMPOSITION-ADMIN-STATUSBAR",
|
|
1951
|
-
severity: '
|
|
2203
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
1952
2204
|
scope: 'shared',
|
|
1953
2205
|
fileTypes: MARKUP_EXT,
|
|
1954
2206
|
match(text) {
|
|
@@ -1980,7 +2232,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
1980
2232
|
|
|
1981
2233
|
RULES.push({
|
|
1982
2234
|
id: "COMPOSITION-ADMIN-TOPBAR",
|
|
1983
|
-
severity: '
|
|
2235
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
1984
2236
|
scope: 'shared',
|
|
1985
2237
|
fileTypes: MARKUP_EXT,
|
|
1986
2238
|
match(text) {
|
|
@@ -2012,7 +2264,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2012
2264
|
|
|
2013
2265
|
RULES.push({
|
|
2014
2266
|
id: "COMPOSITION-AGENT-ADMIN-UI",
|
|
2015
|
-
severity: '
|
|
2267
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2016
2268
|
scope: 'shared',
|
|
2017
2269
|
fileTypes: MARKUP_EXT,
|
|
2018
2270
|
match(text) {
|
|
@@ -2045,7 +2297,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2045
2297
|
|
|
2046
2298
|
RULES.push({
|
|
2047
2299
|
id: "COMPOSITION-AVATAR-GROUP-UI",
|
|
2048
|
-
severity: '
|
|
2300
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2049
2301
|
scope: 'shared',
|
|
2050
2302
|
fileTypes: MARKUP_EXT,
|
|
2051
2303
|
match(text) {
|
|
@@ -2078,7 +2330,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2078
2330
|
|
|
2079
2331
|
RULES.push({
|
|
2080
2332
|
id: "COMPOSITION-CHAT-COMPOSER",
|
|
2081
|
-
severity: '
|
|
2333
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2082
2334
|
scope: 'shared',
|
|
2083
2335
|
fileTypes: MARKUP_EXT,
|
|
2084
2336
|
match(text) {
|
|
@@ -2110,7 +2362,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2110
2362
|
|
|
2111
2363
|
RULES.push({
|
|
2112
2364
|
id: "COMPOSITION-CHAT-EMPTY",
|
|
2113
|
-
severity: '
|
|
2365
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2114
2366
|
scope: 'shared',
|
|
2115
2367
|
fileTypes: MARKUP_EXT,
|
|
2116
2368
|
match(text) {
|
|
@@ -2142,7 +2394,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2142
2394
|
|
|
2143
2395
|
RULES.push({
|
|
2144
2396
|
id: "COMPOSITION-CHAT-HEADER",
|
|
2145
|
-
severity: '
|
|
2397
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2146
2398
|
scope: 'shared',
|
|
2147
2399
|
fileTypes: MARKUP_EXT,
|
|
2148
2400
|
match(text) {
|
|
@@ -2167,14 +2419,14 @@ RULES.push({
|
|
|
2167
2419
|
|
|
2168
2420
|
|
|
2169
2421
|
const TAG = "chat-shell";
|
|
2170
|
-
const ALLOWED_PARENTS = ["Surface","agent-admin-ui"];
|
|
2422
|
+
const ALLOWED_PARENTS = ["Surface","agent-admin-ui","pane-ui"];
|
|
2171
2423
|
const ALLOWED_CHILDREN = [];
|
|
2172
2424
|
const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
2173
2425
|
|
|
2174
2426
|
|
|
2175
2427
|
RULES.push({
|
|
2176
2428
|
id: "COMPOSITION-CHAT-SHELL",
|
|
2177
|
-
severity: '
|
|
2429
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2178
2430
|
scope: 'shared',
|
|
2179
2431
|
fileTypes: MARKUP_EXT,
|
|
2180
2432
|
match(text) {
|
|
@@ -2206,7 +2458,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2206
2458
|
|
|
2207
2459
|
RULES.push({
|
|
2208
2460
|
id: "COMPOSITION-CHAT-SIDEBAR",
|
|
2209
|
-
severity: '
|
|
2461
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2210
2462
|
scope: 'shared',
|
|
2211
2463
|
fileTypes: MARKUP_EXT,
|
|
2212
2464
|
match(text) {
|
|
@@ -2238,7 +2490,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2238
2490
|
|
|
2239
2491
|
RULES.push({
|
|
2240
2492
|
id: "COMPOSITION-CHAT-STATUS",
|
|
2241
|
-
severity: '
|
|
2493
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2242
2494
|
scope: 'shared',
|
|
2243
2495
|
fileTypes: MARKUP_EXT,
|
|
2244
2496
|
match(text) {
|
|
@@ -2270,7 +2522,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2270
2522
|
|
|
2271
2523
|
RULES.push({
|
|
2272
2524
|
id: "COMPOSITION-CHAT-THREAD",
|
|
2273
|
-
severity: '
|
|
2525
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2274
2526
|
scope: 'shared',
|
|
2275
2527
|
fileTypes: MARKUP_EXT,
|
|
2276
2528
|
match(text) {
|
|
@@ -2302,7 +2554,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2302
2554
|
|
|
2303
2555
|
RULES.push({
|
|
2304
2556
|
id: "COMPOSITION-CHOICE-CARD-UI",
|
|
2305
|
-
severity: '
|
|
2557
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2306
2558
|
scope: 'shared',
|
|
2307
2559
|
fileTypes: MARKUP_EXT,
|
|
2308
2560
|
match(text) {
|
|
@@ -2335,7 +2587,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2335
2587
|
|
|
2336
2588
|
RULES.push({
|
|
2337
2589
|
id: "COMPOSITION-CHOICE-UI",
|
|
2338
|
-
severity: '
|
|
2590
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2339
2591
|
scope: 'shared',
|
|
2340
2592
|
fileTypes: MARKUP_EXT,
|
|
2341
2593
|
match(text) {
|
|
@@ -2367,7 +2619,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2367
2619
|
|
|
2368
2620
|
RULES.push({
|
|
2369
2621
|
id: "COMPOSITION-DRILLDOWN-UI",
|
|
2370
|
-
severity: '
|
|
2622
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2371
2623
|
scope: 'shared',
|
|
2372
2624
|
fileTypes: MARKUP_EXT,
|
|
2373
2625
|
match(text) {
|
|
@@ -2400,7 +2652,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2400
2652
|
|
|
2401
2653
|
RULES.push({
|
|
2402
2654
|
id: "COMPOSITION-EDITOR-CANVAS-EMPTY",
|
|
2403
|
-
severity: '
|
|
2655
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2404
2656
|
scope: 'shared',
|
|
2405
2657
|
fileTypes: MARKUP_EXT,
|
|
2406
2658
|
match(text) {
|
|
@@ -2432,7 +2684,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2432
2684
|
|
|
2433
2685
|
RULES.push({
|
|
2434
2686
|
id: "COMPOSITION-EDITOR-CANVAS-TOOLBAR",
|
|
2435
|
-
severity: '
|
|
2687
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2436
2688
|
scope: 'shared',
|
|
2437
2689
|
fileTypes: MARKUP_EXT,
|
|
2438
2690
|
match(text) {
|
|
@@ -2464,7 +2716,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2464
2716
|
|
|
2465
2717
|
RULES.push({
|
|
2466
2718
|
id: "COMPOSITION-EDITOR-CANVAS",
|
|
2467
|
-
severity: '
|
|
2719
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2468
2720
|
scope: 'shared',
|
|
2469
2721
|
fileTypes: MARKUP_EXT,
|
|
2470
2722
|
match(text) {
|
|
@@ -2496,7 +2748,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2496
2748
|
|
|
2497
2749
|
RULES.push({
|
|
2498
2750
|
id: "COMPOSITION-EDITOR-SHELL",
|
|
2499
|
-
severity: '
|
|
2751
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2500
2752
|
scope: 'shared',
|
|
2501
2753
|
fileTypes: MARKUP_EXT,
|
|
2502
2754
|
match(text) {
|
|
@@ -2528,7 +2780,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2528
2780
|
|
|
2529
2781
|
RULES.push({
|
|
2530
2782
|
id: "COMPOSITION-EDITOR-SIDEBAR",
|
|
2531
|
-
severity: '
|
|
2783
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2532
2784
|
scope: 'shared',
|
|
2533
2785
|
fileTypes: MARKUP_EXT,
|
|
2534
2786
|
match(text) {
|
|
@@ -2560,7 +2812,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2560
2812
|
|
|
2561
2813
|
RULES.push({
|
|
2562
2814
|
id: "COMPOSITION-EDITOR-STATUSBAR",
|
|
2563
|
-
severity: '
|
|
2815
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2564
2816
|
scope: 'shared',
|
|
2565
2817
|
fileTypes: MARKUP_EXT,
|
|
2566
2818
|
match(text) {
|
|
@@ -2592,7 +2844,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2592
2844
|
|
|
2593
2845
|
RULES.push({
|
|
2594
2846
|
id: "COMPOSITION-EDITOR-TOOLBAR",
|
|
2595
|
-
severity: '
|
|
2847
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2596
2848
|
scope: 'shared',
|
|
2597
2849
|
fileTypes: MARKUP_EXT,
|
|
2598
2850
|
match(text) {
|
|
@@ -2617,14 +2869,14 @@ RULES.push({
|
|
|
2617
2869
|
|
|
2618
2870
|
|
|
2619
2871
|
const TAG = "embed-shell";
|
|
2620
|
-
const ALLOWED_PARENTS = ["Surface"];
|
|
2872
|
+
const ALLOWED_PARENTS = ["Surface","theme-provider"];
|
|
2621
2873
|
const ALLOWED_CHILDREN = [];
|
|
2622
2874
|
const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
2623
2875
|
|
|
2624
2876
|
|
|
2625
2877
|
RULES.push({
|
|
2626
2878
|
id: "COMPOSITION-EMBED-SHELL",
|
|
2627
|
-
severity: '
|
|
2879
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2628
2880
|
scope: 'shared',
|
|
2629
2881
|
fileTypes: MARKUP_EXT,
|
|
2630
2882
|
match(text) {
|
|
@@ -2656,7 +2908,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2656
2908
|
|
|
2657
2909
|
RULES.push({
|
|
2658
2910
|
id: "COMPOSITION-FEED-ITEM-UI",
|
|
2659
|
-
severity: '
|
|
2911
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2660
2912
|
scope: 'shared',
|
|
2661
2913
|
fileTypes: MARKUP_EXT,
|
|
2662
2914
|
match(text) {
|
|
@@ -2688,7 +2940,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2688
2940
|
|
|
2689
2941
|
RULES.push({
|
|
2690
2942
|
id: "COMPOSITION-FEED-UI",
|
|
2691
|
-
severity: '
|
|
2943
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2692
2944
|
scope: 'shared',
|
|
2693
2945
|
fileTypes: MARKUP_EXT,
|
|
2694
2946
|
match(text) {
|
|
@@ -2721,7 +2973,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2721
2973
|
|
|
2722
2974
|
RULES.push({
|
|
2723
2975
|
id: "COMPOSITION-FIELDS-UI",
|
|
2724
|
-
severity: '
|
|
2976
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2725
2977
|
scope: 'shared',
|
|
2726
2978
|
fileTypes: MARKUP_EXT,
|
|
2727
2979
|
match(text) {
|
|
@@ -2754,7 +3006,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2754
3006
|
|
|
2755
3007
|
RULES.push({
|
|
2756
3008
|
id: "COMPOSITION-FOOTER-UI",
|
|
2757
|
-
severity: '
|
|
3009
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2758
3010
|
scope: 'shared',
|
|
2759
3011
|
fileTypes: MARKUP_EXT,
|
|
2760
3012
|
match(text) {
|
|
@@ -2786,7 +3038,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2786
3038
|
|
|
2787
3039
|
RULES.push({
|
|
2788
3040
|
id: "COMPOSITION-FORM-POPOVER-UI",
|
|
2789
|
-
severity: '
|
|
3041
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2790
3042
|
scope: 'shared',
|
|
2791
3043
|
fileTypes: MARKUP_EXT,
|
|
2792
3044
|
match(text) {
|
|
@@ -2819,7 +3071,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2819
3071
|
|
|
2820
3072
|
RULES.push({
|
|
2821
3073
|
id: "COMPOSITION-HEADER-UI",
|
|
2822
|
-
severity: '
|
|
3074
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2823
3075
|
scope: 'shared',
|
|
2824
3076
|
fileTypes: MARKUP_EXT,
|
|
2825
3077
|
match(text) {
|
|
@@ -2851,7 +3103,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2851
3103
|
|
|
2852
3104
|
RULES.push({
|
|
2853
3105
|
id: "COMPOSITION-LIST-ITEM-UI",
|
|
2854
|
-
severity: '
|
|
3106
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2855
3107
|
scope: 'shared',
|
|
2856
3108
|
fileTypes: MARKUP_EXT,
|
|
2857
3109
|
match(text) {
|
|
@@ -2883,7 +3135,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2883
3135
|
|
|
2884
3136
|
RULES.push({
|
|
2885
3137
|
id: "COMPOSITION-MENU-DIVIDER-UI",
|
|
2886
|
-
severity: '
|
|
3138
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2887
3139
|
scope: 'shared',
|
|
2888
3140
|
fileTypes: MARKUP_EXT,
|
|
2889
3141
|
match(text) {
|
|
@@ -2915,7 +3167,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2915
3167
|
|
|
2916
3168
|
RULES.push({
|
|
2917
3169
|
id: "COMPOSITION-MENU-ITEM-UI",
|
|
2918
|
-
severity: '
|
|
3170
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2919
3171
|
scope: 'shared',
|
|
2920
3172
|
fileTypes: MARKUP_EXT,
|
|
2921
3173
|
match(text) {
|
|
@@ -2947,7 +3199,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2947
3199
|
|
|
2948
3200
|
RULES.push({
|
|
2949
3201
|
id: "COMPOSITION-MENU-LABEL-UI",
|
|
2950
|
-
severity: '
|
|
3202
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2951
3203
|
scope: 'shared',
|
|
2952
3204
|
fileTypes: MARKUP_EXT,
|
|
2953
3205
|
match(text) {
|
|
@@ -2979,7 +3231,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
2979
3231
|
|
|
2980
3232
|
RULES.push({
|
|
2981
3233
|
id: "COMPOSITION-MENU-UI",
|
|
2982
|
-
severity: '
|
|
3234
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
2983
3235
|
scope: 'shared',
|
|
2984
3236
|
fileTypes: MARKUP_EXT,
|
|
2985
3237
|
match(text) {
|
|
@@ -3012,7 +3264,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3012
3264
|
|
|
3013
3265
|
RULES.push({
|
|
3014
3266
|
id: "COMPOSITION-NAV-GROUP-UI",
|
|
3015
|
-
severity: '
|
|
3267
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3016
3268
|
scope: 'shared',
|
|
3017
3269
|
fileTypes: MARKUP_EXT,
|
|
3018
3270
|
match(text) {
|
|
@@ -3047,7 +3299,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3047
3299
|
|
|
3048
3300
|
RULES.push({
|
|
3049
3301
|
id: "COMPOSITION-NAV-ITEM-UI",
|
|
3050
|
-
severity: '
|
|
3302
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3051
3303
|
scope: 'shared',
|
|
3052
3304
|
fileTypes: MARKUP_EXT,
|
|
3053
3305
|
match(text) {
|
|
@@ -3079,7 +3331,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3079
3331
|
|
|
3080
3332
|
RULES.push({
|
|
3081
3333
|
id: "COMPOSITION-PAGE-SCROLL",
|
|
3082
|
-
severity: '
|
|
3334
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3083
3335
|
scope: 'shared',
|
|
3084
3336
|
fileTypes: MARKUP_EXT,
|
|
3085
3337
|
match(text) {
|
|
@@ -3111,7 +3363,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3111
3363
|
|
|
3112
3364
|
RULES.push({
|
|
3113
3365
|
id: "COMPOSITION-RADIO-GROUP-UI",
|
|
3114
|
-
severity: '
|
|
3366
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3115
3367
|
scope: 'shared',
|
|
3116
3368
|
fileTypes: MARKUP_EXT,
|
|
3117
3369
|
match(text) {
|
|
@@ -3144,7 +3396,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3144
3396
|
|
|
3145
3397
|
RULES.push({
|
|
3146
3398
|
id: "COMPOSITION-SEARCH-UI",
|
|
3147
|
-
severity: '
|
|
3399
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3148
3400
|
scope: 'shared',
|
|
3149
3401
|
fileTypes: MARKUP_EXT,
|
|
3150
3402
|
match(text) {
|
|
@@ -3177,7 +3429,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3177
3429
|
|
|
3178
3430
|
RULES.push({
|
|
3179
3431
|
id: "COMPOSITION-SECTION-UI",
|
|
3180
|
-
severity: '
|
|
3432
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3181
3433
|
scope: 'shared',
|
|
3182
3434
|
fileTypes: MARKUP_EXT,
|
|
3183
3435
|
match(text) {
|
|
@@ -3209,7 +3461,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3209
3461
|
|
|
3210
3462
|
RULES.push({
|
|
3211
3463
|
id: "COMPOSITION-SEGMENT-UI",
|
|
3212
|
-
severity: '
|
|
3464
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3213
3465
|
scope: 'shared',
|
|
3214
3466
|
fileTypes: MARKUP_EXT,
|
|
3215
3467
|
match(text) {
|
|
@@ -3241,7 +3493,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3241
3493
|
|
|
3242
3494
|
RULES.push({
|
|
3243
3495
|
id: "COMPOSITION-SEGMENTED-UI",
|
|
3244
|
-
severity: '
|
|
3496
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3245
3497
|
scope: 'shared',
|
|
3246
3498
|
fileTypes: MARKUP_EXT,
|
|
3247
3499
|
match(text) {
|
|
@@ -3274,7 +3526,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3274
3526
|
|
|
3275
3527
|
RULES.push({
|
|
3276
3528
|
id: "COMPOSITION-SIMPLE-CONTENT",
|
|
3277
|
-
severity: '
|
|
3529
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3278
3530
|
scope: 'shared',
|
|
3279
3531
|
fileTypes: MARKUP_EXT,
|
|
3280
3532
|
match(text) {
|
|
@@ -3306,7 +3558,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3306
3558
|
|
|
3307
3559
|
RULES.push({
|
|
3308
3560
|
id: "COMPOSITION-SIMPLE-HERO",
|
|
3309
|
-
severity: '
|
|
3561
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3310
3562
|
scope: 'shared',
|
|
3311
3563
|
fileTypes: MARKUP_EXT,
|
|
3312
3564
|
match(text) {
|
|
@@ -3338,7 +3590,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3338
3590
|
|
|
3339
3591
|
RULES.push({
|
|
3340
3592
|
id: "COMPOSITION-SIMPLE-SHELL",
|
|
3341
|
-
severity: '
|
|
3593
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3342
3594
|
scope: 'shared',
|
|
3343
3595
|
fileTypes: MARKUP_EXT,
|
|
3344
3596
|
match(text) {
|
|
@@ -3370,7 +3622,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3370
3622
|
|
|
3371
3623
|
RULES.push({
|
|
3372
3624
|
id: "COMPOSITION-SKIP-NAV-UI",
|
|
3373
|
-
severity: '
|
|
3625
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3374
3626
|
scope: 'shared',
|
|
3375
3627
|
fileTypes: MARKUP_EXT,
|
|
3376
3628
|
match(text) {
|
|
@@ -3402,7 +3654,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3402
3654
|
|
|
3403
3655
|
RULES.push({
|
|
3404
3656
|
id: "COMPOSITION-STEPPER-ITEM-UI",
|
|
3405
|
-
severity: '
|
|
3657
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3406
3658
|
scope: 'shared',
|
|
3407
3659
|
fileTypes: MARKUP_EXT,
|
|
3408
3660
|
match(text) {
|
|
@@ -3434,7 +3686,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3434
3686
|
|
|
3435
3687
|
RULES.push({
|
|
3436
3688
|
id: "COMPOSITION-STEPPER-UI",
|
|
3437
|
-
severity: '
|
|
3689
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3438
3690
|
scope: 'shared',
|
|
3439
3691
|
fileTypes: MARKUP_EXT,
|
|
3440
3692
|
match(text) {
|
|
@@ -3467,7 +3719,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3467
3719
|
|
|
3468
3720
|
RULES.push({
|
|
3469
3721
|
id: "COMPOSITION-TAB-UI",
|
|
3470
|
-
severity: '
|
|
3722
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3471
3723
|
scope: 'shared',
|
|
3472
3724
|
fileTypes: MARKUP_EXT,
|
|
3473
3725
|
match(text) {
|
|
@@ -3499,7 +3751,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3499
3751
|
|
|
3500
3752
|
RULES.push({
|
|
3501
3753
|
id: "COMPOSITION-TABS-UI",
|
|
3502
|
-
severity: '
|
|
3754
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3503
3755
|
scope: 'shared',
|
|
3504
3756
|
fileTypes: MARKUP_EXT,
|
|
3505
3757
|
match(text) {
|
|
@@ -3532,7 +3784,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3532
3784
|
|
|
3533
3785
|
RULES.push({
|
|
3534
3786
|
id: "COMPOSITION-TIMELINE-ITEM-UI",
|
|
3535
|
-
severity: '
|
|
3787
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3536
3788
|
scope: 'shared',
|
|
3537
3789
|
fileTypes: MARKUP_EXT,
|
|
3538
3790
|
match(text) {
|
|
@@ -3564,7 +3816,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3564
3816
|
|
|
3565
3817
|
RULES.push({
|
|
3566
3818
|
id: "COMPOSITION-TIMELINE-UI",
|
|
3567
|
-
severity: '
|
|
3819
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3568
3820
|
scope: 'shared',
|
|
3569
3821
|
fileTypes: MARKUP_EXT,
|
|
3570
3822
|
match(text) {
|
|
@@ -3597,7 +3849,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3597
3849
|
|
|
3598
3850
|
RULES.push({
|
|
3599
3851
|
id: "COMPOSITION-TOOLBAR-GROUP-UI",
|
|
3600
|
-
severity: '
|
|
3852
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3601
3853
|
scope: 'shared',
|
|
3602
3854
|
fileTypes: MARKUP_EXT,
|
|
3603
3855
|
match(text) {
|
|
@@ -3629,7 +3881,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3629
3881
|
|
|
3630
3882
|
RULES.push({
|
|
3631
3883
|
id: "COMPOSITION-TOUR-STEP-UI",
|
|
3632
|
-
severity: '
|
|
3884
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3633
3885
|
scope: 'shared',
|
|
3634
3886
|
fileTypes: MARKUP_EXT,
|
|
3635
3887
|
match(text) {
|
|
@@ -3661,7 +3913,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3661
3913
|
|
|
3662
3914
|
RULES.push({
|
|
3663
3915
|
id: "COMPOSITION-TOUR-UI",
|
|
3664
|
-
severity: '
|
|
3916
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3665
3917
|
scope: 'shared',
|
|
3666
3918
|
fileTypes: MARKUP_EXT,
|
|
3667
3919
|
match(text) {
|
|
@@ -3694,7 +3946,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3694
3946
|
|
|
3695
3947
|
RULES.push({
|
|
3696
3948
|
id: "COMPOSITION-TREE-ITEM-UI",
|
|
3697
|
-
severity: '
|
|
3949
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3698
3950
|
scope: 'shared',
|
|
3699
3951
|
fileTypes: MARKUP_EXT,
|
|
3700
3952
|
match(text) {
|
|
@@ -3726,7 +3978,7 @@ const ALLOWED_CHILDREN_BY_SLOT = null;
|
|
|
3726
3978
|
|
|
3727
3979
|
RULES.push({
|
|
3728
3980
|
id: "COMPOSITION-TREE-UI",
|
|
3729
|
-
severity: '
|
|
3981
|
+
severity: 'error', // gh#3719: promoted from warn once the corpus-wide rollout (LLD-0016 §C4) cleared clean, every COMPOSITION-* rule's real corpus hits fixed, so this stays advisory no longer.
|
|
3730
3982
|
scope: 'shared',
|
|
3731
3983
|
fileTypes: MARKUP_EXT,
|
|
3732
3984
|
match(text) {
|
|
@@ -3977,10 +4229,6 @@ const MANIFEST_SNAPSHOT = {
|
|
|
3977
4229
|
}
|
|
3978
4230
|
]
|
|
3979
4231
|
},
|
|
3980
|
-
{
|
|
3981
|
-
"tag": "admin-scroll",
|
|
3982
|
-
"attrs": []
|
|
3983
|
-
},
|
|
3984
4232
|
{
|
|
3985
4233
|
"tag": "admin-settings-ui",
|
|
3986
4234
|
"attrs": [
|
|
@@ -9180,6 +9428,10 @@ const MANIFEST_SNAPSHOT = {
|
|
|
9180
9428
|
{
|
|
9181
9429
|
"value": "dot-grid",
|
|
9182
9430
|
"tier": "default"
|
|
9431
|
+
},
|
|
9432
|
+
{
|
|
9433
|
+
"value": "noise",
|
|
9434
|
+
"tier": "alternative"
|
|
9183
9435
|
}
|
|
9184
9436
|
]
|
|
9185
9437
|
}
|
|
@@ -11374,10 +11626,6 @@ const MANIFEST_SNAPSHOT = {
|
|
|
11374
11626
|
}
|
|
11375
11627
|
]
|
|
11376
11628
|
},
|
|
11377
|
-
{
|
|
11378
|
-
"tag": "admin-scroll",
|
|
11379
|
-
"attrs": []
|
|
11380
|
-
},
|
|
11381
11629
|
{
|
|
11382
11630
|
"tag": "admin-settings-ui",
|
|
11383
11631
|
"attrs": [
|
|
@@ -16577,6 +16825,10 @@ const MANIFEST_SNAPSHOT = {
|
|
|
16577
16825
|
{
|
|
16578
16826
|
"value": "dot-grid",
|
|
16579
16827
|
"tier": "default"
|
|
16828
|
+
},
|
|
16829
|
+
{
|
|
16830
|
+
"value": "noise",
|
|
16831
|
+
"tier": "alternative"
|
|
16580
16832
|
}
|
|
16581
16833
|
]
|
|
16582
16834
|
}
|
|
@@ -18767,10 +19019,6 @@ const MANIFEST_SNAPSHOT = {
|
|
|
18767
19019
|
}
|
|
18768
19020
|
]
|
|
18769
19021
|
},
|
|
18770
|
-
{
|
|
18771
|
-
"tag": "admin-scroll",
|
|
18772
|
-
"attrs": []
|
|
18773
|
-
},
|
|
18774
19022
|
{
|
|
18775
19023
|
"tag": "admin-settings-ui",
|
|
18776
19024
|
"attrs": [
|
|
@@ -23970,6 +24218,10 @@ const MANIFEST_SNAPSHOT = {
|
|
|
23970
24218
|
{
|
|
23971
24219
|
"value": "dot-grid",
|
|
23972
24220
|
"tier": "default"
|
|
24221
|
+
},
|
|
24222
|
+
{
|
|
24223
|
+
"value": "noise",
|
|
24224
|
+
"tier": "alternative"
|
|
23973
24225
|
}
|
|
23974
24226
|
]
|
|
23975
24227
|
}
|
|
@@ -26082,10 +26334,6 @@ const MANIFEST_SNAPSHOT = {
|
|
|
26082
26334
|
}
|
|
26083
26335
|
]
|
|
26084
26336
|
},
|
|
26085
|
-
{
|
|
26086
|
-
"tag": "admin-scroll",
|
|
26087
|
-
"attrs": []
|
|
26088
|
-
},
|
|
26089
26337
|
{
|
|
26090
26338
|
"tag": "admin-settings-ui",
|
|
26091
26339
|
"attrs": [
|
|
@@ -30572,10 +30820,6 @@ const MANIFEST_SNAPSHOT = {
|
|
|
30572
30820
|
"tag": "admin-roster-ui",
|
|
30573
30821
|
"attrs": []
|
|
30574
30822
|
},
|
|
30575
|
-
{
|
|
30576
|
-
"tag": "admin-scroll",
|
|
30577
|
-
"attrs": []
|
|
30578
|
-
},
|
|
30579
30823
|
{
|
|
30580
30824
|
"tag": "admin-settings-ui",
|
|
30581
30825
|
"attrs": []
|
|
@@ -32176,10 +32420,30 @@ RULES.push({
|
|
|
32176
32420
|
// literal URL, so this hasn't triggered in practice; a literal `//` URL
|
|
32177
32421
|
// sharing a line with the dimension attributes is the one shape to watch
|
|
32178
32422
|
// for in review.
|
|
32423
|
+
//
|
|
32424
|
+
// gh#3985 (a second consequence of the same string-literal-blind gap
|
|
32425
|
+
// above): a plain JS string mentioning markup IN PROSE, not authoring it,
|
|
32426
|
+
// still satisfies `<img\b[^>]*?>`, PR #3977's own vitest-setup error
|
|
32427
|
+
// message ("...a <link>, <script> or " + "<img> src), turn that loading
|
|
32428
|
+
// off...") matched a bare, zero-attribute `<img>` and read as an
|
|
32429
|
+
// error-severity finding on a file that emits no DOM at all. Every REAL
|
|
32430
|
+
// `<img>` this rule was written to catch (gh#3604's select.class.js
|
|
32431
|
+
// sites, this corpus's only non-comment instances) sets at least one real
|
|
32432
|
+
// attribute (`src=` at minimum: an img with none renders nothing, so
|
|
32433
|
+
// hand-authored markup never omits it); a bare `<img>`/`<img >` with zero
|
|
32434
|
+
// `name=value` pairs is never genuine output markup in this corpus, only
|
|
32435
|
+
// prose describing one, so HAS_ANY_ATTR below gates the match the same
|
|
32436
|
+
// way HAS_WIDTH/HAS_HEIGHT already gate the dimension check. This is a
|
|
32437
|
+
// narrow, corpus-verified discriminator, not a full JS/HTML parse (an
|
|
32438
|
+
// attribute-bearing `<img width=100>` still mentioned in a doc comment or
|
|
32439
|
+
// prose sentence would still false-positive; none exists in this corpus
|
|
32440
|
+
// today, watch for it in review the same way the paragraph above already
|
|
32441
|
+
// asks).
|
|
32179
32442
|
const IMG_TAG_RE = /<img\b[^>]*?>/gis;
|
|
32180
32443
|
const HAS_WIDTH = /\bwidth\s*=/i;
|
|
32181
32444
|
const HAS_HEIGHT = /\bheight\s*=/i;
|
|
32182
32445
|
const HAS_ASPECT_RATIO = /aspect-ratio\s*:/i;
|
|
32446
|
+
const HAS_ANY_ATTR = /\S=/;
|
|
32183
32447
|
const CORPUS_PATH_RE = /packages[\\/](web-components|web-modules)[\\/]/;
|
|
32184
32448
|
|
|
32185
32449
|
|
|
@@ -32203,6 +32467,7 @@ RULES.push({
|
|
|
32203
32467
|
let m;
|
|
32204
32468
|
while ((m = IMG_TAG_RE.exec(blanked))) {
|
|
32205
32469
|
const tag = m[0];
|
|
32470
|
+
if (!HAS_ANY_ATTR.test(tag)) continue; // gh#3985: no attribute at all, prose mentioning <img>, not authored markup
|
|
32206
32471
|
if ((HAS_WIDTH.test(tag) && HAS_HEIGHT.test(tag)) || HAS_ASPECT_RATIO.test(tag)) continue;
|
|
32207
32472
|
const lineNo = lineOf(blanked, m.index);
|
|
32208
32473
|
out.push({
|
|
@@ -32427,6 +32692,39 @@ function optInTagsFor(scopes) {
|
|
|
32427
32692
|
return ['forge-lint', 'adia-lint'];
|
|
32428
32693
|
}
|
|
32429
32694
|
|
|
32695
|
+
// gh#3839: a `var(--token, <literal>)` fallback is still a shipped literal
|
|
32696
|
+
// whenever the named token is undefined: RAW-COLOR must see inside it.
|
|
32697
|
+
// `light-dark(<light>, <dark>)` is the one legitimate literal-pair form (both
|
|
32698
|
+
// arms are meant to be literal color values by design), so its call is
|
|
32699
|
+
// stripped out, balanced on parens, so a color function nested in either
|
|
32700
|
+
// arm (e.g. `light-dark(oklch(...), oklch(...))`) is stripped too, before
|
|
32701
|
+
// the rest of the declaration (var() fallbacks included) is scanned as
|
|
32702
|
+
// plain text. Custom-property names never match HEXCOLOR/FUNCCOLOR, so
|
|
32703
|
+
// leaving `var(` unstripped carries no risk of matching the reference
|
|
32704
|
+
// itself, only a literal actually sitting in its fallback.
|
|
32705
|
+
function stripBalancedCalls(text, fnName) {
|
|
32706
|
+
const token = `${fnName}(`;
|
|
32707
|
+
let result = '';
|
|
32708
|
+
let i = 0;
|
|
32709
|
+
while (i < text.length) {
|
|
32710
|
+
const idx = text.indexOf(token, i);
|
|
32711
|
+
if (idx === -1) {
|
|
32712
|
+
result += text.slice(i);
|
|
32713
|
+
break;
|
|
32714
|
+
}
|
|
32715
|
+
result += text.slice(i, idx);
|
|
32716
|
+
let depth = 1;
|
|
32717
|
+
let j = idx + token.length;
|
|
32718
|
+
while (j < text.length && depth > 0) {
|
|
32719
|
+
if (text[j] === '(') depth += 1;
|
|
32720
|
+
else if (text[j] === ')') depth -= 1;
|
|
32721
|
+
j += 1;
|
|
32722
|
+
}
|
|
32723
|
+
i = j;
|
|
32724
|
+
}
|
|
32725
|
+
return result;
|
|
32726
|
+
}
|
|
32727
|
+
|
|
32430
32728
|
|
|
32431
32729
|
RULES.push({
|
|
32432
32730
|
id: 'RAW-COLOR',
|
|
@@ -32442,8 +32740,8 @@ RULES.push({
|
|
|
32442
32740
|
for (const decl of stripped.split(';')) {
|
|
32443
32741
|
const d = decl.trim();
|
|
32444
32742
|
if (!d || d.startsWith('//') || d.startsWith('/*') || d.startsWith('*')) continue;
|
|
32445
|
-
|
|
32446
|
-
if (RE.HEXCOLOR.test(
|
|
32743
|
+
const scanText = decl.includes('light-dark(') ? stripBalancedCalls(decl, 'light-dark') : decl;
|
|
32744
|
+
if (RE.HEXCOLOR.test(scanText) || RE.FUNCCOLOR.test(scanText)) {
|
|
32447
32745
|
out.push({
|
|
32448
32746
|
line: lineNo,
|
|
32449
32747
|
snippet: raw.trim().slice(0, 90),
|
|
@@ -32508,20 +32806,48 @@ RULES.push({
|
|
|
32508
32806
|
|
|
32509
32807
|
|
|
32510
32808
|
|
|
32809
|
+
/**
|
|
32810
|
+
* SCOPE-EXTENT (gh#3718): error severity, with the pre-existing corpus of
|
|
32811
|
+
* findings downgraded to warn via a checked-in baseline rather than by a
|
|
32812
|
+
* blanket severity.json entry.
|
|
32813
|
+
*
|
|
32814
|
+
* The blanket downgrade is what this replaces. It made every site invisible,
|
|
32815
|
+
* including sites added after the downgrade, so the rule could not do the one
|
|
32816
|
+
* job it was written for. The baseline inverts that: known sites stay warn,
|
|
32817
|
+
* anything not in the baseline errors.
|
|
32818
|
+
*
|
|
32819
|
+
* Sites are keyed on the normalized text of the matched block, per file, as a
|
|
32820
|
+
* MULTISET, not on line numbers. A line-keyed baseline goes stale whenever an
|
|
32821
|
+
* unrelated edit shifts a selector down, which reports a pre-existing finding
|
|
32822
|
+
* as new for a reason that has nothing to do with this rule. The multiset also
|
|
32823
|
+
* keeps duplicates, so a second identical violation in an already-baselined
|
|
32824
|
+
* file exceeds its entry and errors, which a per-file count would miss.
|
|
32825
|
+
*/
|
|
32826
|
+
|
|
32511
32827
|
RULES.push({
|
|
32512
32828
|
id: 'SCOPE-EXTENT',
|
|
32513
32829
|
severity: 'error',
|
|
32514
32830
|
scope: 'shared',
|
|
32515
32831
|
fileTypes: STYLE_EXT,
|
|
32516
|
-
match(text) {
|
|
32832
|
+
match(text, path) {
|
|
32517
32833
|
const out = [];
|
|
32518
32834
|
const re = new RegExp(RE.SCOPE_EXTENT.source, 'gs');
|
|
32835
|
+
// A local copy, consumed as we go: each baselined entry excuses exactly
|
|
32836
|
+
// one finding, so the second identical violation in the same file finds
|
|
32837
|
+
// the entry already spent and reports at full severity.
|
|
32838
|
+
const remaining = [...baselineFor(path)];
|
|
32519
32839
|
let m;
|
|
32520
32840
|
while ((m = re.exec(text))) {
|
|
32841
|
+
const key = normalizeMatch(m[0]);
|
|
32842
|
+
const at = remaining.indexOf(key);
|
|
32843
|
+
const baselined = at !== -1;
|
|
32844
|
+
if (baselined) remaining.splice(at, 1);
|
|
32521
32845
|
out.push({
|
|
32522
32846
|
line: lineOf(text, m.index),
|
|
32523
32847
|
snippet: ':scope { … width/height … }',
|
|
32524
32848
|
why: "a primitive is size-agnostic — let the consumer own width/height; don't set extent on :scope",
|
|
32849
|
+
// Per-finding override; absent means the rule's own severity applies.
|
|
32850
|
+
...(baselined ? { severity: 'warn' } : {}),
|
|
32525
32851
|
});
|
|
32526
32852
|
}
|
|
32527
32853
|
return out;
|