@kerfjs/ui 5.0.0-beta.70 → 5.0.0-beta.72
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/ai/component-catalog.json +21 -5
- package/ai/component-catalog.schema.json +2 -0
- package/ai/component-composition-rules.json +4 -4
- package/ai/component-composition.d.ts +2 -0
- package/ai/component-composition.json +23 -5
- package/ai/component-composition.schema.json +9 -0
- package/ai/components/toolbar-text.md +3 -1
- package/ai/components/toolbar.md +6 -6
- package/ai/public-api-signatures-v1.md +16 -11
- package/ai/skill.md +1 -1
- package/ai/webawesome-jsx-signatures-v1.md +1 -1
- package/analyzer/component-ownership.mjs +131 -16
- package/analyzer/index.d.mts +2 -1
- package/analyzer/index.mjs +298 -29
- package/dist/catalog.js +2 -2
- package/dist/{chunk-KCCEH7RF.js → chunk-3UE24JSC.js} +2 -2
- package/dist/{chunk-KCCEH7RF.js.map → chunk-3UE24JSC.js.map} +1 -1
- package/dist/{chunk-V3DX3K65.js → chunk-R5F5XUJ7.js} +19 -2
- package/dist/chunk-R5F5XUJ7.js.map +1 -0
- package/dist/{chunk-T2E5WBCZ.js → chunk-SLWCCHNP.js} +11 -2
- package/dist/chunk-SLWCCHNP.js.map +1 -0
- package/dist/{chunk-DC6CN2DS.js → chunk-VLYIPBCQ.js} +2 -2
- package/dist/{chunk-DC6CN2DS.js.map → chunk-VLYIPBCQ.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/nav-stack.js +1 -1
- package/dist/split-view.js +1 -1
- package/dist/styles/app-tab.css +3 -0
- package/dist/styles/chip.css +6 -0
- package/dist/styles/toolbar-text.css +20 -0
- package/dist/styles/toolbar.css +10 -1
- package/dist/toolbar-text.d.ts +15 -10
- package/dist/toolbar-text.js +1 -1
- package/dist/wire-tab-bars.js +1 -1
- package/dist/workbench.js +1 -1
- package/docs/accessibility.md +5 -0
- package/docs/component-contract.md +17 -2
- package/docs/component-selection.md +27 -16
- package/docs/layout.md +4 -0
- package/docs/ui-analyzer.md +42 -5
- package/docs/ui-doctor.md +28 -3
- package/doctor/config.schema.json +23 -1
- package/doctor/index.d.mts +2 -1
- package/doctor/index.mjs +42 -2
- package/package.json +1 -1
- package/dist/chunk-T2E5WBCZ.js.map +0 -1
- package/dist/chunk-V3DX3K65.js.map +0 -1
package/analyzer/index.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
componentOwnershipFacts,
|
|
21
21
|
contextualComponents,
|
|
22
22
|
configurationFor,
|
|
23
|
+
ownsClassBlock,
|
|
23
24
|
privateVariableOwner,
|
|
24
25
|
reportGap,
|
|
25
26
|
restyledComponents,
|
|
@@ -114,6 +115,13 @@ const adoptionRules = new Set([
|
|
|
114
115
|
]);
|
|
115
116
|
|
|
116
117
|
const sourceExtensions = new Set(['.js', '.jsx', '.mjs', '.ts', '.tsx']);
|
|
118
|
+
function isTestModule(file) {
|
|
119
|
+
const path = file.replaceAll('\\', '/');
|
|
120
|
+
return (
|
|
121
|
+
/(?:^|\/)__tests__\//.test(path) ||
|
|
122
|
+
/\.(?:test|spec)\.(?:js|jsx|mjs|ts|tsx)$/.test(path)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
117
125
|
const spacingProperties = /^(?:margin|padding|gap|inset)(?:-|$)/;
|
|
118
126
|
const approvedSpacing = new Set([0, 4, 8, 16, 24]);
|
|
119
127
|
async function collectFiles(root, paths) {
|
|
@@ -441,6 +449,7 @@ function catalogFacts(entries) {
|
|
|
441
449
|
const publicTokens = new Set();
|
|
442
450
|
const publicParts = new Map();
|
|
443
451
|
const classEntries = new Map();
|
|
452
|
+
const blockEntries = new Map();
|
|
444
453
|
const exportEntries = new Map();
|
|
445
454
|
const addExport = (name, entry) => {
|
|
446
455
|
const candidates = exportEntries.get(name) ?? [];
|
|
@@ -453,11 +462,20 @@ function catalogFacts(entries) {
|
|
|
453
462
|
const name = typeof item === 'string' ? item : item.name;
|
|
454
463
|
if (isComponentExport(name)) addExport(name, entry);
|
|
455
464
|
}
|
|
456
|
-
for (const className of
|
|
465
|
+
for (const className of new Set(
|
|
466
|
+
[
|
|
467
|
+
entry.boundaries?.rootClass,
|
|
468
|
+
...(entry.boundaries?.publicClasses ?? []),
|
|
469
|
+
].filter(Boolean),
|
|
470
|
+
)) {
|
|
457
471
|
publicClasses.add(className);
|
|
458
472
|
const owners = classEntries.get(className) ?? [];
|
|
459
473
|
owners.push(entry);
|
|
460
474
|
classEntries.set(className, owners);
|
|
475
|
+
const block = bemBlock(className);
|
|
476
|
+
const blockOwners = blockEntries.get(block) ?? [];
|
|
477
|
+
if (!blockOwners.includes(entry)) blockOwners.push(entry);
|
|
478
|
+
blockEntries.set(block, blockOwners);
|
|
461
479
|
}
|
|
462
480
|
for (const token of entry.boundaries?.publicTokens ?? [])
|
|
463
481
|
publicTokens.add(token);
|
|
@@ -473,6 +491,7 @@ function catalogFacts(entries) {
|
|
|
473
491
|
publicTokens,
|
|
474
492
|
publicParts,
|
|
475
493
|
classEntries,
|
|
494
|
+
blockEntries,
|
|
476
495
|
exportEntries,
|
|
477
496
|
ownership: componentOwnershipFacts(entries),
|
|
478
497
|
};
|
|
@@ -553,6 +572,46 @@ function inferOwnedClasses(entries, contents, imports) {
|
|
|
553
572
|
});
|
|
554
573
|
}
|
|
555
574
|
|
|
575
|
+
async function ownershipGroupEntries(groups, root, contents) {
|
|
576
|
+
if (!groups?.length) return [];
|
|
577
|
+
const manifest = JSON.parse(
|
|
578
|
+
await readFile(resolve(root, 'package.json'), 'utf8'),
|
|
579
|
+
);
|
|
580
|
+
return Promise.all(
|
|
581
|
+
groups.map(async (group, index) => {
|
|
582
|
+
const classes = new Set();
|
|
583
|
+
for (const style of group.styleSources) {
|
|
584
|
+
const file = resolve(root, style);
|
|
585
|
+
let css = contents.get(file);
|
|
586
|
+
if (css === undefined)
|
|
587
|
+
try {
|
|
588
|
+
css = await readFile(file, 'utf8');
|
|
589
|
+
} catch {
|
|
590
|
+
// A missing configured file owns no classes.
|
|
591
|
+
}
|
|
592
|
+
if (!css) continue;
|
|
593
|
+
try {
|
|
594
|
+
postcss.parse(css, { from: file }).walkRules((rule) => {
|
|
595
|
+
for (const name of subjectClasses(rule.selector)) classes.add(name);
|
|
596
|
+
});
|
|
597
|
+
} catch {
|
|
598
|
+
// The normal stylesheet pass reports malformed CSS.
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return {
|
|
602
|
+
key: `${manifest.name}:ownership-group-${index + 1}`,
|
|
603
|
+
package: manifest.name,
|
|
604
|
+
id: `ownership-group-${index + 1}`,
|
|
605
|
+
name: `Ownership group ${index + 1}`,
|
|
606
|
+
catalogDirectory: root,
|
|
607
|
+
styleSources: group.styleSources,
|
|
608
|
+
ownershipGroupSources: group.sources,
|
|
609
|
+
boundaries: { publicClasses: [...classes] },
|
|
610
|
+
};
|
|
611
|
+
}),
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
556
615
|
async function implicitOwnershipEntries(files, imports, root) {
|
|
557
616
|
const found = [];
|
|
558
617
|
const packages = new Map();
|
|
@@ -662,7 +721,7 @@ function inspectComponentOwnership(
|
|
|
662
721
|
'KUI-L019',
|
|
663
722
|
location(file, rule),
|
|
664
723
|
via === 'descendant'
|
|
665
|
-
? `\`${name}\` reaches an unclassed descendant inside ${componentLabel(entry)} (${properties.join(', ')}); components own their styles.
|
|
724
|
+
? `\`${name}\` reaches an unclassed descendant inside ${componentLabel(entry)} (${properties.join(', ')}); components own their styles. Use a child combinator or an own class to target your element, or configure the child through ${configurationFor(entry)}. ${reportGap(entry)}`
|
|
666
725
|
: `\`${name}\` makes ${componentLabel(entry)} the subject of an application rule (${properties.join(', ')}); components own their styles. Configure it through ${configurationFor(entry)}. To place your own content in its context, style your own element (\`${name} > .your-element\`). ${reportGap(entry)}`,
|
|
667
726
|
{
|
|
668
727
|
selector: rule.selector,
|
|
@@ -675,12 +734,13 @@ function inspectComponentOwnership(
|
|
|
675
734
|
adoption,
|
|
676
735
|
),
|
|
677
736
|
);
|
|
678
|
-
if (ownershipContext
|
|
737
|
+
if (ownershipContext === 'subject' || !ownershipEvidence?.ownPackage) return;
|
|
679
738
|
for (const { entry, name, position } of contextualComponents(
|
|
680
739
|
rule.selector,
|
|
681
740
|
facts.ownership,
|
|
682
741
|
isForeign,
|
|
683
742
|
ownershipEvidence.ownPackage,
|
|
743
|
+
ownershipContext === 'any-package',
|
|
684
744
|
)) {
|
|
685
745
|
if (position === 'subject') continue;
|
|
686
746
|
if (
|
|
@@ -693,7 +753,7 @@ function inspectComponentOwnership(
|
|
|
693
753
|
diagnostic(
|
|
694
754
|
'KUI-L019',
|
|
695
755
|
location(file, rule),
|
|
696
|
-
`\`${name}\` uses ${componentLabel(entry)} as ${position} selector context (${properties.join(', ')});
|
|
756
|
+
`\`${name}\` uses ${componentLabel(entry)} as ${position} selector context (${properties.join(', ')}); foreign component handles cannot key this stylesheet in this ownership mode. Configure the component through ${configurationFor(entry)}. ${reportGap(entry)}`,
|
|
697
757
|
{
|
|
698
758
|
selector: rule.selector,
|
|
699
759
|
component: entry.key,
|
|
@@ -995,8 +1055,10 @@ function literalClassValues(expression) {
|
|
|
995
1055
|
function jsxOwnershipEvidence(style, facts, contents, root, compilerCache) {
|
|
996
1056
|
const hooks = new Map();
|
|
997
1057
|
const composedChildren = new Map();
|
|
1058
|
+
const composedDescendants = new Map();
|
|
998
1059
|
const ambiguousHooks = new Set();
|
|
999
1060
|
const ambiguousChildren = new Set();
|
|
1061
|
+
const ambiguousDescendants = new Set();
|
|
1000
1062
|
const record = (map, ambiguous, key, entry) => {
|
|
1001
1063
|
if (ambiguous.has(key)) return;
|
|
1002
1064
|
if (map.has(key) && map.get(key).key !== entry.key) {
|
|
@@ -1021,6 +1083,27 @@ function jsxOwnershipEvidence(style, facts, contents, root, compilerCache) {
|
|
|
1021
1083
|
true,
|
|
1022
1084
|
ts.ScriptKind.TSX,
|
|
1023
1085
|
);
|
|
1086
|
+
const localComponents = new Map();
|
|
1087
|
+
for (const statement of source.statements) {
|
|
1088
|
+
if (
|
|
1089
|
+
ts.isFunctionDeclaration(statement) &&
|
|
1090
|
+
statement.name &&
|
|
1091
|
+
statement.body
|
|
1092
|
+
)
|
|
1093
|
+
localComponents.set(statement.name.text, statement.body);
|
|
1094
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
1095
|
+
for (const declaration of statement.declarationList.declarations)
|
|
1096
|
+
if (
|
|
1097
|
+
ts.isIdentifier(declaration.name) &&
|
|
1098
|
+
declaration.initializer &&
|
|
1099
|
+
(ts.isArrowFunction(declaration.initializer) ||
|
|
1100
|
+
ts.isFunctionExpression(declaration.initializer))
|
|
1101
|
+
)
|
|
1102
|
+
localComponents.set(
|
|
1103
|
+
declaration.name.text,
|
|
1104
|
+
declaration.initializer.body,
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1024
1107
|
const imports = new Map();
|
|
1025
1108
|
const namespaces = new Map();
|
|
1026
1109
|
const compilerOptions = compilerOptionsFor(file, root, compilerCache);
|
|
@@ -1084,6 +1167,82 @@ function jsxOwnershipEvidence(style, facts, contents, root, compilerCache) {
|
|
|
1084
1167
|
['class', 'className'].includes(item.name.getText(source)),
|
|
1085
1168
|
),
|
|
1086
1169
|
).values;
|
|
1170
|
+
const returnedJsx = (body) => {
|
|
1171
|
+
if (!ts.isBlock(body)) return [body];
|
|
1172
|
+
const expressions = [];
|
|
1173
|
+
const visitReturns = (node) => {
|
|
1174
|
+
if (ts.isReturnStatement(node)) {
|
|
1175
|
+
if (node.expression) expressions.push(node.expression);
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
if (
|
|
1179
|
+
node !== body &&
|
|
1180
|
+
(ts.isFunctionDeclaration(node) ||
|
|
1181
|
+
ts.isFunctionExpression(node) ||
|
|
1182
|
+
ts.isArrowFunction(node))
|
|
1183
|
+
)
|
|
1184
|
+
return;
|
|
1185
|
+
ts.forEachChild(node, visitReturns);
|
|
1186
|
+
};
|
|
1187
|
+
visitReturns(body);
|
|
1188
|
+
return expressions;
|
|
1189
|
+
};
|
|
1190
|
+
const localReturns = (opening, seen) => {
|
|
1191
|
+
const name = opening.tagName.getText(source);
|
|
1192
|
+
const body = localComponents.get(name);
|
|
1193
|
+
if (!body || seen.has(name)) return undefined;
|
|
1194
|
+
return { name, expressions: returnedJsx(body) };
|
|
1195
|
+
};
|
|
1196
|
+
const composedBelow = (node, found, seen = new Set()) => {
|
|
1197
|
+
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
1198
|
+
const opening = openingOf(node);
|
|
1199
|
+
const entry = entryFor(opening);
|
|
1200
|
+
if (entry) {
|
|
1201
|
+
for (const type of [
|
|
1202
|
+
entry.boundaries?.rootElement,
|
|
1203
|
+
...(entry.boundaries?.descendantElements ?? []),
|
|
1204
|
+
]) {
|
|
1205
|
+
if (!type) continue;
|
|
1206
|
+
const entries = found.get(type) ?? new Set();
|
|
1207
|
+
entries.add(entry);
|
|
1208
|
+
found.set(type, entries);
|
|
1209
|
+
}
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const local = localReturns(opening, seen);
|
|
1213
|
+
if (local) {
|
|
1214
|
+
const next = new Set([...seen, local.name]);
|
|
1215
|
+
for (const expression of local.expressions)
|
|
1216
|
+
composedBelow(expression, found, next);
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
if (ts.isJsxElement(node))
|
|
1220
|
+
for (const child of node.children) composedBelow(child, found, seen);
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
if (ts.isJsxFragment(node)) {
|
|
1224
|
+
for (const child of node.children) composedBelow(child, found, seen);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
ts.forEachChild(node, (child) => composedBelow(child, found, seen));
|
|
1228
|
+
};
|
|
1229
|
+
const expandedRoots = (node, seen = new Set()) => {
|
|
1230
|
+
if (ts.isJsxFragment(node))
|
|
1231
|
+
return node.children.flatMap((child) => expandedRoots(child, seen));
|
|
1232
|
+
if (!ts.isJsxElement(node) && !ts.isJsxSelfClosingElement(node)) {
|
|
1233
|
+
const roots = [];
|
|
1234
|
+
ts.forEachChild(node, (child) =>
|
|
1235
|
+
roots.push(...expandedRoots(child, seen)),
|
|
1236
|
+
);
|
|
1237
|
+
return roots;
|
|
1238
|
+
}
|
|
1239
|
+
const local = localReturns(openingOf(node), seen);
|
|
1240
|
+
if (!local) return [node];
|
|
1241
|
+
const next = new Set([...seen, local.name]);
|
|
1242
|
+
return local.expressions.flatMap((expression) =>
|
|
1243
|
+
expandedRoots(expression, next),
|
|
1244
|
+
);
|
|
1245
|
+
};
|
|
1087
1246
|
const visit = (node) => {
|
|
1088
1247
|
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
1089
1248
|
const opening = openingOf(node);
|
|
@@ -1093,23 +1252,39 @@ function jsxOwnershipEvidence(style, facts, contents, root, compilerCache) {
|
|
|
1093
1252
|
record(hooks, ambiguousHooks, name, entry);
|
|
1094
1253
|
if (ts.isJsxElement(node)) {
|
|
1095
1254
|
const parentClasses = classesOf(opening);
|
|
1255
|
+
if (parentClasses.length) {
|
|
1256
|
+
const descendants = new Map();
|
|
1257
|
+
for (const child of node.children)
|
|
1258
|
+
composedBelow(child, descendants);
|
|
1259
|
+
for (const [type, entries] of descendants)
|
|
1260
|
+
for (const entry of entries)
|
|
1261
|
+
for (const name of parentClasses)
|
|
1262
|
+
record(
|
|
1263
|
+
composedDescendants,
|
|
1264
|
+
ambiguousDescendants,
|
|
1265
|
+
`${name}|${type}`,
|
|
1266
|
+
entry,
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1096
1269
|
const possible = new Map();
|
|
1097
1270
|
let dynamic = false;
|
|
1098
1271
|
for (const child of node.children) {
|
|
1099
1272
|
if (ts.isJsxExpression(child) && child.expression) dynamic = true;
|
|
1100
1273
|
if (!ts.isJsxElement(child) && !ts.isJsxSelfClosingElement(child))
|
|
1101
1274
|
continue;
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1275
|
+
for (const root of expandedRoots(child)) {
|
|
1276
|
+
const childOpening = openingOf(root);
|
|
1277
|
+
const childEntry = entryFor(childOpening);
|
|
1278
|
+
const type =
|
|
1279
|
+
childEntry?.boundaries?.rootElement ??
|
|
1280
|
+
(childEntry
|
|
1281
|
+
? undefined
|
|
1282
|
+
: childOpening.tagName.getText(source).toLowerCase());
|
|
1283
|
+
if (!type) continue;
|
|
1284
|
+
const candidates = possible.get(type) ?? [];
|
|
1285
|
+
candidates.push(childEntry);
|
|
1286
|
+
possible.set(type, candidates);
|
|
1287
|
+
}
|
|
1113
1288
|
}
|
|
1114
1289
|
if (!dynamic)
|
|
1115
1290
|
for (const [type, candidates] of possible)
|
|
@@ -1133,7 +1308,7 @@ function jsxOwnershipEvidence(style, facts, contents, root, compilerCache) {
|
|
|
1133
1308
|
};
|
|
1134
1309
|
visit(source);
|
|
1135
1310
|
}
|
|
1136
|
-
return { componentMode: true, hooks, composedChildren };
|
|
1311
|
+
return { componentMode: true, hooks, composedChildren, composedDescendants };
|
|
1137
1312
|
}
|
|
1138
1313
|
|
|
1139
1314
|
function cssPreferred(contract) {
|
|
@@ -1308,6 +1483,7 @@ function inspectTsx(
|
|
|
1308
1483
|
isForeign = () => true,
|
|
1309
1484
|
packageDirectory,
|
|
1310
1485
|
compilerOptions,
|
|
1486
|
+
componentMode = false,
|
|
1311
1487
|
) {
|
|
1312
1488
|
const source = ts.createSourceFile(
|
|
1313
1489
|
file,
|
|
@@ -1365,10 +1541,18 @@ function inspectTsx(
|
|
|
1365
1541
|
}
|
|
1366
1542
|
}
|
|
1367
1543
|
const stack = [];
|
|
1544
|
+
const inspectBorrowedMarkup = !isTestModule(file);
|
|
1368
1545
|
const borrowed = (className, node, via) => {
|
|
1369
|
-
|
|
1546
|
+
if (!inspectBorrowedMarkup) return;
|
|
1547
|
+
const owners =
|
|
1548
|
+
facts.classEntries.get(className) ??
|
|
1549
|
+
(componentMode && bemBlock(className) !== className
|
|
1550
|
+
? facts.blockEntries.get(bemBlock(className))
|
|
1551
|
+
: undefined) ??
|
|
1552
|
+
[];
|
|
1553
|
+
for (const owner of owners) {
|
|
1370
1554
|
if (
|
|
1371
|
-
!isForeign(owner) ||
|
|
1555
|
+
!isForeign(owner, className) ||
|
|
1372
1556
|
owner.boundaries?.placeableClasses?.includes(className)
|
|
1373
1557
|
)
|
|
1374
1558
|
continue;
|
|
@@ -1387,6 +1571,22 @@ function inspectTsx(
|
|
|
1387
1571
|
const borrowedValues = (values, node, via) => {
|
|
1388
1572
|
for (const className of new Set(values)) borrowed(className, node, via);
|
|
1389
1573
|
};
|
|
1574
|
+
const templateGap = '\uE000';
|
|
1575
|
+
const rawHtmlClasses = (value) => {
|
|
1576
|
+
const classes = [];
|
|
1577
|
+
for (const match of value.matchAll(
|
|
1578
|
+
/<[a-z][^>]*\sclass\s*=\s*["']([^"']+)["']/gi,
|
|
1579
|
+
)) {
|
|
1580
|
+
const pieces = match[1].split(templateGap);
|
|
1581
|
+
for (const [index, piece] of pieces.entries()) {
|
|
1582
|
+
const words = piece.split(/\s+/).filter(Boolean);
|
|
1583
|
+
if (index > 0 && !/^\s/.test(piece)) words.shift();
|
|
1584
|
+
if (index < pieces.length - 1 && !/\s$/.test(piece)) words.pop();
|
|
1585
|
+
classes.push(...words);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
return classes;
|
|
1589
|
+
};
|
|
1390
1590
|
const visit = (node) => {
|
|
1391
1591
|
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
1392
1592
|
const opening = ts.isJsxElement(node) ? node.openingElement : node;
|
|
@@ -1411,7 +1611,7 @@ function inspectTsx(
|
|
|
1411
1611
|
);
|
|
1412
1612
|
const classes = literalClasses(classAttribute);
|
|
1413
1613
|
const at = location(file, opening, source);
|
|
1414
|
-
if (!entry && /^[a-z]/.test(tag))
|
|
1614
|
+
if ((!entry && /^[a-z]/.test(tag)) || (componentMode && entry))
|
|
1415
1615
|
borrowedValues(
|
|
1416
1616
|
classAttribute?.initializer &&
|
|
1417
1617
|
ts.isJsxExpression(classAttribute.initializer)
|
|
@@ -1432,7 +1632,8 @@ function inspectTsx(
|
|
|
1432
1632
|
);
|
|
1433
1633
|
if (entry && isForeign(entry))
|
|
1434
1634
|
for (const className of classes.values) {
|
|
1435
|
-
if (facts.ownership
|
|
1635
|
+
if (ownsClassBlock(facts.ownership, className, componentMode))
|
|
1636
|
+
continue;
|
|
1436
1637
|
const styled = cssFacts.get(className)?.styled;
|
|
1437
1638
|
if (!styled) continue;
|
|
1438
1639
|
diagnostics.push(
|
|
@@ -1585,11 +1786,15 @@ function inspectTsx(
|
|
|
1585
1786
|
node.right,
|
|
1586
1787
|
'className assignment',
|
|
1587
1788
|
);
|
|
1588
|
-
if (ts.isStringLiteralLike(node) && node.text.includes('<'))
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1789
|
+
if (ts.isStringLiteralLike(node) && node.text.includes('<'))
|
|
1790
|
+
borrowedValues(rawHtmlClasses(node.text), node, 'raw HTML');
|
|
1791
|
+
if (ts.isTemplateExpression(node)) {
|
|
1792
|
+
const staticHtml = [
|
|
1793
|
+
node.head.text,
|
|
1794
|
+
...node.templateSpans.map((span) => span.literal.text),
|
|
1795
|
+
].join(templateGap);
|
|
1796
|
+
if (staticHtml.includes('<'))
|
|
1797
|
+
borrowedValues(rawHtmlClasses(staticHtml), node, 'raw HTML');
|
|
1593
1798
|
}
|
|
1594
1799
|
ts.forEachChild(node, visit);
|
|
1595
1800
|
};
|
|
@@ -1604,6 +1809,7 @@ export async function analyzeUiProject({
|
|
|
1604
1809
|
ownership = 'package',
|
|
1605
1810
|
ownershipContext = 'subject',
|
|
1606
1811
|
implicitComponentOwnership = false,
|
|
1812
|
+
ownershipGroups = [],
|
|
1607
1813
|
profile: packageProfile = resolve(
|
|
1608
1814
|
import.meta.dirname,
|
|
1609
1815
|
'../ai/application-ui-profile.defaults.json',
|
|
@@ -1646,6 +1852,10 @@ export async function analyzeUiProject({
|
|
|
1646
1852
|
ownership === 'component' && implicitComponentOwnership
|
|
1647
1853
|
? await implicitOwnershipEntries(files, imports, root)
|
|
1648
1854
|
: [];
|
|
1855
|
+
const groupEntries =
|
|
1856
|
+
ownership === 'component'
|
|
1857
|
+
? await ownershipGroupEntries(ownershipGroups, root, contents)
|
|
1858
|
+
: [];
|
|
1649
1859
|
const profileContexts = new Map();
|
|
1650
1860
|
const loadContext = async (file) => {
|
|
1651
1861
|
const startDirectory = file === root ? root : dirname(file);
|
|
@@ -1669,6 +1879,7 @@ export async function analyzeUiProject({
|
|
|
1669
1879
|
);
|
|
1670
1880
|
const ownershipEntries = [
|
|
1671
1881
|
...entries,
|
|
1882
|
+
...groupEntries,
|
|
1672
1883
|
...implicitEntries.filter(
|
|
1673
1884
|
(entry) =>
|
|
1674
1885
|
!catalogedSources.has(
|
|
@@ -1726,12 +1937,69 @@ export async function analyzeUiProject({
|
|
|
1726
1937
|
);
|
|
1727
1938
|
return packages.get(directory);
|
|
1728
1939
|
};
|
|
1729
|
-
const
|
|
1940
|
+
const styledBlocksByFile = new Map();
|
|
1941
|
+
const styledBlocksIn = (style) => {
|
|
1942
|
+
if (!styledBlocksByFile.has(style)) {
|
|
1943
|
+
const blocks = new Set();
|
|
1944
|
+
const css = contents.get(style);
|
|
1945
|
+
if (css)
|
|
1946
|
+
try {
|
|
1947
|
+
postcss.parse(css, { from: style }).walkRules((rule) => {
|
|
1948
|
+
for (const name of subjectClasses(rule.selector))
|
|
1949
|
+
blocks.add(bemBlock(name));
|
|
1950
|
+
});
|
|
1951
|
+
} catch {
|
|
1952
|
+
// Malformed CSS is reported by the normal inspection pass.
|
|
1953
|
+
}
|
|
1954
|
+
styledBlocksByFile.set(style, blocks);
|
|
1955
|
+
}
|
|
1956
|
+
return styledBlocksByFile.get(style);
|
|
1957
|
+
};
|
|
1958
|
+
const foreignTo = async (file, facts) => {
|
|
1730
1959
|
const own = await packageOf(dirname(file));
|
|
1731
1960
|
const source = own && relative(own.directory, file).replaceAll('\\', '/');
|
|
1732
|
-
|
|
1961
|
+
const ownedStyles = new Set(imports.get(file) ?? []);
|
|
1962
|
+
if (ownership === 'component' && !file.endsWith('.css'))
|
|
1963
|
+
for (const entry of facts.entries) {
|
|
1964
|
+
if (
|
|
1965
|
+
entry.package !== own?.name ||
|
|
1966
|
+
!entry.componentSource ||
|
|
1967
|
+
!entry.catalogDirectory ||
|
|
1968
|
+
resolve(entry.catalogDirectory, entry.componentSource) !== file
|
|
1969
|
+
)
|
|
1970
|
+
continue;
|
|
1971
|
+
for (const style of entry.styleSources ?? [])
|
|
1972
|
+
ownedStyles.add(resolve(entry.catalogDirectory, style));
|
|
1973
|
+
}
|
|
1974
|
+
return (entry, className) => {
|
|
1733
1975
|
if (entry.package !== own?.name) return true;
|
|
1734
1976
|
if (ownership !== 'component') return false;
|
|
1977
|
+
if (entry.ownershipGroupSources) {
|
|
1978
|
+
const relativeFile = relative(entry.catalogDirectory, file).replaceAll(
|
|
1979
|
+
'\\',
|
|
1980
|
+
'/',
|
|
1981
|
+
);
|
|
1982
|
+
if (file.endsWith('.css'))
|
|
1983
|
+
return !entry.styleSources?.includes(relativeFile);
|
|
1984
|
+
return !entry.ownershipGroupSources.some((path) =>
|
|
1985
|
+
path.endsWith('/')
|
|
1986
|
+
? relativeFile.startsWith(path)
|
|
1987
|
+
: relativeFile === path,
|
|
1988
|
+
);
|
|
1989
|
+
}
|
|
1990
|
+
if (
|
|
1991
|
+
!file.endsWith('.css') &&
|
|
1992
|
+
className &&
|
|
1993
|
+
entry.catalogDirectory &&
|
|
1994
|
+
entry.styleSources?.some((style) => {
|
|
1995
|
+
const path = resolve(entry.catalogDirectory, style);
|
|
1996
|
+
return (
|
|
1997
|
+
ownedStyles.has(path) &&
|
|
1998
|
+
styledBlocksIn(path).has(bemBlock(className))
|
|
1999
|
+
);
|
|
2000
|
+
})
|
|
2001
|
+
)
|
|
2002
|
+
return false;
|
|
1735
2003
|
const ownedSources = file.endsWith('.css')
|
|
1736
2004
|
? entry.styleSources
|
|
1737
2005
|
: [entry.componentSource];
|
|
@@ -1830,9 +2098,10 @@ export async function analyzeUiProject({
|
|
|
1830
2098
|
reachableStyleFacts(file, imports, styleFacts),
|
|
1831
2099
|
fileDiagnostics,
|
|
1832
2100
|
adoption,
|
|
1833
|
-
await foreignTo(file),
|
|
2101
|
+
await foreignTo(file, context.facts),
|
|
1834
2102
|
own?.directory,
|
|
1835
2103
|
compilerOptionsFor(file, root, compilerOptions),
|
|
2104
|
+
ownership === 'component',
|
|
1836
2105
|
);
|
|
1837
2106
|
recordDiagnostics(fileDiagnostics, context);
|
|
1838
2107
|
}
|
|
@@ -1851,7 +2120,7 @@ export async function analyzeUiProject({
|
|
|
1851
2120
|
new Map(),
|
|
1852
2121
|
adoption,
|
|
1853
2122
|
siblingOnLoud(file),
|
|
1854
|
-
await foreignTo(file),
|
|
2123
|
+
await foreignTo(file, context.facts),
|
|
1855
2124
|
ownership === 'component'
|
|
1856
2125
|
? {
|
|
1857
2126
|
...jsxOwnershipEvidence(
|
package/dist/catalog.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Workbench } from './chunk-
|
|
1
|
+
import { Workbench } from './chunk-3UE24JSC.js';
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
import { PopupMenu } from './chunk-J3KGJ6P6.js';
|
|
@@ -8,7 +8,7 @@ import { ListHeader } from './chunk-M6DEEBEF.js';
|
|
|
8
8
|
import { Text } from './chunk-YDGNU2OD.js';
|
|
9
9
|
import { ListInsetText } from './chunk-DG6G5EPF.js';
|
|
10
10
|
import { ListItem } from './chunk-NC4CFOID.js';
|
|
11
|
-
import { ToolbarText } from './chunk-
|
|
11
|
+
import { ToolbarText } from './chunk-R5F5XUJ7.js';
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WORKBENCH_MAIN_MIN_SIZE, WORKBENCH_MAIN_MIN_HEIGHT, workbenchRegionId, WORKBENCH_RESIZE_DEFAULTS } from './chunk-PSHUSWQH.js';
|
|
2
|
-
import { NavStack } from './chunk-
|
|
2
|
+
import { NavStack } from './chunk-VLYIPBCQ.js';
|
|
3
3
|
import { clampRegionSize, ResizeGrip } from './chunk-KLJKU6ZL.js';
|
|
4
4
|
import { FloatingToolbar } from './chunk-I3Q7KKYM.js';
|
|
5
5
|
import { panelToggleGroup, composedPanelBody, relocatedPanelGroups } from './chunk-RNQNEENY.js';
|
|
@@ -489,4 +489,4 @@ function Workbench({
|
|
|
489
489
|
|
|
490
490
|
export { Workbench };
|
|
491
491
|
|
|
492
|
-
//# sourceMappingURL=chunk-
|
|
492
|
+
//# sourceMappingURL=chunk-3UE24JSC.js.map
|