@contentful/experience-design-system-cli 2.11.4-dev-build-a9b4d34.0 → 2.11.4-dev-build-8cd3b18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +1 -1
- package/dist/src/analyze/extract/svelte.js +84 -10
- package/package.json +2 -2
package/dist/package.json
CHANGED
|
@@ -71,6 +71,7 @@ async function extractFromSvelteFile(filePath, source) {
|
|
|
71
71
|
let props = [];
|
|
72
72
|
let propNamesTreatedAsSnippet = new Set();
|
|
73
73
|
let snippetSlotsFromProps = [];
|
|
74
|
+
const extractionReasons = [];
|
|
74
75
|
if (propsCall) {
|
|
75
76
|
const result = await extractPropsFromCall({
|
|
76
77
|
propsCall,
|
|
@@ -84,6 +85,8 @@ async function extractFromSvelteFile(filePath, source) {
|
|
|
84
85
|
propNamesTreatedAsSnippet = result.snippetNames;
|
|
85
86
|
snippetSlotsFromProps = result.snippetSlots;
|
|
86
87
|
warnings.push(...result.warnings);
|
|
88
|
+
if (result.additionalReasons)
|
|
89
|
+
extractionReasons.push(...result.additionalReasons);
|
|
87
90
|
}
|
|
88
91
|
else if (instance) {
|
|
89
92
|
// No $props() call. We've already ruled out v4 above; this means $props was used
|
|
@@ -107,11 +110,17 @@ async function extractFromSvelteFile(filePath, source) {
|
|
|
107
110
|
props,
|
|
108
111
|
slots,
|
|
109
112
|
};
|
|
110
|
-
// Score & flag review.
|
|
111
|
-
|
|
113
|
+
// Score & flag review. Forward extraction-time reasons (e.g. props-type-unresolved)
|
|
114
|
+
// so they count toward the confidence score AND surface in reviewReasons.
|
|
115
|
+
const score = computeExtractionScore(component, {
|
|
116
|
+
additionalIssueCount: extractionReasons.length,
|
|
117
|
+
additionalReasons: extractionReasons,
|
|
118
|
+
});
|
|
112
119
|
component.extractionConfidence = score.confidence;
|
|
113
120
|
component.reviewReasons = score.reasons;
|
|
114
|
-
|
|
121
|
+
// A type-resolution failure is a strong signal something is wrong; force
|
|
122
|
+
// human review even when other heuristics keep confidence above the threshold.
|
|
123
|
+
component.needsReview = deriveNeedsReview(score.confidence) || extractionReasons.includes('props-type-unresolved');
|
|
115
124
|
// Validation issues (EMPTY_COMPONENT_NAME / EMPTY_PROP_NAME / PROP_SLOT_NAME_COLLISION /
|
|
116
125
|
// DUPLICATE_COMPONENT_NAME / EMPTY_COMPONENT / EMPTY_SLOT_NAME) are populated
|
|
117
126
|
// centrally by validateExtractedComponents() in analyze/command.ts after all
|
|
@@ -220,19 +229,83 @@ async function extractPropsFromCall(ctx) {
|
|
|
220
229
|
const typeMembers = annotation
|
|
221
230
|
? await resolveTypeMembers(annotation, ctx.instance, ctx.moduleScript, ctx.filePath, ctx.source)
|
|
222
231
|
: null;
|
|
232
|
+
// Detect "we tried to resolve a real type and got nothing back" — distinct from
|
|
233
|
+
// the user genuinely typing an empty literal. Surface as both a warning (CLI
|
|
234
|
+
// stderr / analyze report) and a review reason (TUI / downstream gating).
|
|
235
|
+
const unresolved = classifyUnresolved(annotation, typeMembers, ctx.instance, ctx.moduleScript);
|
|
236
|
+
const additionalReasons = [];
|
|
237
|
+
if (unresolved) {
|
|
238
|
+
const refLabel = describeAnnotationForUser(annotation);
|
|
239
|
+
const heritageNote = unresolved === 'partial-heritage' ? ' (heritage clauses extending unreachable types)' : '';
|
|
240
|
+
warnings.push(`${ctx.filePath}: declared Props type ${refLabel} resolved to ${unresolved === 'empty' ? '0' : 'only Snippet-typed'} properties${heritageNote} — possible cross-package extends or unreachable type. ` +
|
|
241
|
+
`See https://github.com/contentful/experience-design-system-sdk-public/pull/44 for context and partner workarounds.`);
|
|
242
|
+
additionalReasons.push('props-type-unresolved');
|
|
243
|
+
}
|
|
223
244
|
if (idType === 'ObjectPattern') {
|
|
224
|
-
return extractFromDestructure(ctx.propsCall, ctx, typeMembers, warnings);
|
|
245
|
+
return extractFromDestructure(ctx.propsCall, ctx, typeMembers, warnings, additionalReasons);
|
|
225
246
|
}
|
|
226
247
|
if (idType === 'Identifier') {
|
|
227
248
|
// const props: Props = $props(); — no destructure, no defaults, no per-name binding.
|
|
228
|
-
if (typeMembers) {
|
|
229
|
-
return extractFromTypeMembersOnly(typeMembers);
|
|
249
|
+
if (typeMembers && typeMembers.length > 0) {
|
|
250
|
+
return { ...extractFromTypeMembersOnly(typeMembers), warnings, additionalReasons };
|
|
230
251
|
}
|
|
231
|
-
|
|
232
|
-
|
|
252
|
+
if (!unresolved) {
|
|
253
|
+
warnings.push(`${ctx.filePath}: $props() called without destructuring; cannot extract individual props`);
|
|
254
|
+
}
|
|
255
|
+
return { props: [], snippetNames: new Set(), snippetSlots: [], warnings, additionalReasons };
|
|
233
256
|
}
|
|
234
257
|
warnings.push(`${ctx.filePath}: unrecognized $props() binding pattern (${idType})`);
|
|
235
|
-
return { props: [], snippetNames: new Set(), snippetSlots: [], warnings };
|
|
258
|
+
return { props: [], snippetNames: new Set(), snippetSlots: [], warnings, additionalReasons };
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Classify whether the user's declared Props type failed to resolve usefully.
|
|
262
|
+
*
|
|
263
|
+
* - `'empty'`: the resolver returned no members for a non-trivial annotation.
|
|
264
|
+
* - `'partial-heritage'`: the source declaration has heritage clauses (extends /
|
|
265
|
+
* intersection in module-script) but the resolver only surfaced Snippet-typed
|
|
266
|
+
* members — strong signal that one or more parents pointed at unreachable
|
|
267
|
+
* types (e.g. cross-package node_modules) and were silently dropped.
|
|
268
|
+
* - `null`: nothing to surface — either the user genuinely typed an empty
|
|
269
|
+
* literal, omitted the annotation, or the resolver returned regular props.
|
|
270
|
+
*/
|
|
271
|
+
function classifyUnresolved(annotation, members, instance, moduleScript) {
|
|
272
|
+
if (!annotation)
|
|
273
|
+
return null;
|
|
274
|
+
if (annotation.type === 'TSTypeLiteral') {
|
|
275
|
+
const litMembers = annotation['members'] ?? [];
|
|
276
|
+
if (litMembers.length === 0)
|
|
277
|
+
return null; // user genuinely typed `{}`
|
|
278
|
+
}
|
|
279
|
+
if (members === null || members.length === 0)
|
|
280
|
+
return 'empty';
|
|
281
|
+
// Partial-heritage signal: declaration has extends and every surviving member
|
|
282
|
+
// is Snippet-typed. Real prop interfaces almost never declare every prop as a
|
|
283
|
+
// Snippet, so this is a strong "something fell off the resolution edge" hint.
|
|
284
|
+
if (annotation.type === 'TSTypeReference') {
|
|
285
|
+
const refName = annotation['typeName']?.['name'] ?? null;
|
|
286
|
+
if (refName) {
|
|
287
|
+
const decl = findLocalTypeDeclaration(instance, refName, moduleScript);
|
|
288
|
+
if (decl && declarationHasHeritage(decl) && members.every((m) => m.isSnippet)) {
|
|
289
|
+
return 'partial-heritage';
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
function describeAnnotationForUser(annotation) {
|
|
296
|
+
if (!annotation)
|
|
297
|
+
return '<unknown>';
|
|
298
|
+
if (annotation.type === 'TSTypeReference') {
|
|
299
|
+
const name = annotation['typeName']?.['name'] ?? null;
|
|
300
|
+
return name ? `'${name}'` : '<unnamed reference>';
|
|
301
|
+
}
|
|
302
|
+
if (annotation.type === 'TSIntersectionType')
|
|
303
|
+
return '<intersection>';
|
|
304
|
+
if (annotation.type === 'TSUnionType')
|
|
305
|
+
return '<union>';
|
|
306
|
+
if (annotation.type === 'TSTypeLiteral')
|
|
307
|
+
return '<inline literal>';
|
|
308
|
+
return `<${annotation.type}>`;
|
|
236
309
|
}
|
|
237
310
|
async function resolveTypeMembers(annotation, instance, moduleScript, filePath, source) {
|
|
238
311
|
// Snippet imports may live in either script block; collect from both.
|
|
@@ -569,7 +642,7 @@ function extractJsdocText(comments) {
|
|
|
569
642
|
// ---------------------------------------------------------------------------
|
|
570
643
|
// Extraction: ObjectPattern destructure + type-members
|
|
571
644
|
// ---------------------------------------------------------------------------
|
|
572
|
-
function extractFromDestructure(propsCall, ctx, typeMembers, warnings) {
|
|
645
|
+
function extractFromDestructure(propsCall, ctx, typeMembers, warnings, additionalReasons) {
|
|
573
646
|
const id = propsCall['id'];
|
|
574
647
|
const properties = id['properties'] ?? [];
|
|
575
648
|
const typeByName = new Map();
|
|
@@ -633,6 +706,7 @@ function extractFromDestructure(propsCall, ctx, typeMembers, warnings) {
|
|
|
633
706
|
snippetNames,
|
|
634
707
|
snippetSlots,
|
|
635
708
|
warnings,
|
|
709
|
+
...(additionalReasons && additionalReasons.length > 0 ? { additionalReasons } : {}),
|
|
636
710
|
};
|
|
637
711
|
}
|
|
638
712
|
function extractFromTypeMembersOnly(typeMembers) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.11.4-dev-build-
|
|
3
|
+
"version": "2.11.4-dev-build-8cd3b18.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"svelte": "^5.56.4",
|
|
38
38
|
"ts-morph": "^27.0.2",
|
|
39
39
|
"typescript": "^5.9.3",
|
|
40
|
-
"@contentful/experience-design-system-types": "2.11.4-dev-build-
|
|
40
|
+
"@contentful/experience-design-system-types": "2.11.4-dev-build-8cd3b18.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@tsconfig/node24": "^24.0.3",
|