@formspec/build 0.1.0-alpha.48 → 0.1.0-alpha.49

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/index.js CHANGED
@@ -2377,6 +2377,92 @@ function supportsConstraintCapability(type, checker, capability) {
2377
2377
  }
2378
2378
  return false;
2379
2379
  }
2380
+ var MAX_HINT_CANDIDATES = 5;
2381
+ var MAX_HINT_DEPTH = 3;
2382
+ function stripHintNullishUnion(type) {
2383
+ if (!type.isUnion()) {
2384
+ return type;
2385
+ }
2386
+ const nonNullish = type.types.filter(
2387
+ (member) => (member.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) === 0
2388
+ );
2389
+ if (nonNullish.length === 1 && nonNullish[0] !== void 0) {
2390
+ return nonNullish[0];
2391
+ }
2392
+ return type;
2393
+ }
2394
+ function isCallableType(type) {
2395
+ return type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0;
2396
+ }
2397
+ function isUserEmittableHintProperty(property, declaration) {
2398
+ if (property.name.startsWith("__")) {
2399
+ return false;
2400
+ }
2401
+ if ("name" in declaration && declaration.name !== void 0) {
2402
+ const name = declaration.name;
2403
+ if (ts.isComputedPropertyName(name) || ts.isPrivateIdentifier(name)) {
2404
+ return false;
2405
+ }
2406
+ if (!ts.isIdentifier(name) && !ts.isStringLiteral(name) && !ts.isNumericLiteral(name)) {
2407
+ return false;
2408
+ }
2409
+ }
2410
+ return true;
2411
+ }
2412
+ function collectObjectSubfieldCandidates(type, checker, capability) {
2413
+ const out = [];
2414
+ const visit = (current, prefix, depth) => {
2415
+ if (depth > MAX_HINT_DEPTH) {
2416
+ return;
2417
+ }
2418
+ const stripped = stripHintNullishUnion(current);
2419
+ if (isCallableType(stripped)) {
2420
+ return;
2421
+ }
2422
+ if (!hasTypeSemanticCapability(stripped, checker, "object-like")) {
2423
+ return;
2424
+ }
2425
+ for (const property of stripped.getProperties()) {
2426
+ const declaration = property.valueDeclaration ?? property.declarations?.[0];
2427
+ if (declaration === void 0) {
2428
+ continue;
2429
+ }
2430
+ if (!isUserEmittableHintProperty(property, declaration)) {
2431
+ continue;
2432
+ }
2433
+ const propertyType = checker.getTypeOfSymbolAtLocation(property, declaration);
2434
+ const path4 = [...prefix, property.name];
2435
+ if (supportsConstraintCapability(propertyType, checker, capability)) {
2436
+ out.push(path4.join("."));
2437
+ continue;
2438
+ }
2439
+ const strippedPropertyType = stripHintNullishUnion(propertyType);
2440
+ if (!isCallableType(strippedPropertyType) && hasTypeSemanticCapability(strippedPropertyType, checker, "object-like")) {
2441
+ visit(strippedPropertyType, path4, depth + 1);
2442
+ }
2443
+ }
2444
+ };
2445
+ visit(type, [], 0);
2446
+ return out;
2447
+ }
2448
+ function buildPathTargetHint(subjectType, checker, capability, tagName, argumentText) {
2449
+ if (!hasTypeSemanticCapability(subjectType, checker, "object-like")) {
2450
+ return null;
2451
+ }
2452
+ const candidates = collectObjectSubfieldCandidates(subjectType, checker, capability);
2453
+ const primary = candidates[0];
2454
+ if (primary === void 0) {
2455
+ return null;
2456
+ }
2457
+ const argText = argumentText?.trim() ?? "";
2458
+ const renderExample = (path4) => argText === "" ? `@${tagName} :${path4}` : `@${tagName} :${path4} ${argText}`;
2459
+ if (candidates.length === 1) {
2460
+ return `Hint: use a path target to constrain a subfield, e.g. ${renderExample(primary)}`;
2461
+ }
2462
+ const shown = candidates.slice(0, MAX_HINT_CANDIDATES);
2463
+ const overflow = candidates.length > MAX_HINT_CANDIDATES ? ", \u2026" : "";
2464
+ return `Hint: use a path target to constrain a subfield (candidates: ${shown.join(", ")}${overflow}), e.g. ${renderExample(primary)}`;
2465
+ }
2380
2466
  function makeDiagnostic(code, message, provenance) {
2381
2467
  return {
2382
2468
  code,
@@ -2544,10 +2630,18 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
2544
2630
  const requiredCapability = definition.capabilities[0];
2545
2631
  if (requiredCapability !== void 0 && !supportsConstraintCapability(subjectType, checker, requiredCapability)) {
2546
2632
  const actualType = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
2633
+ const baseMessage = `Target "${node.getText(sourceFile)}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`;
2634
+ const hint = buildPathTargetHint(
2635
+ subjectType,
2636
+ checker,
2637
+ requiredCapability,
2638
+ tagName,
2639
+ parsedTag?.argumentText
2640
+ );
2547
2641
  return [
2548
2642
  makeDiagnostic(
2549
2643
  "TYPE_MISMATCH",
2550
- `Target "${node.getText(sourceFile)}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
2644
+ hint === null ? baseMessage : `${baseMessage}. ${hint}`,
2551
2645
  provenance
2552
2646
  )
2553
2647
  ];
@@ -4197,6 +4291,9 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
4197
4291
  if (type.flags & ts3.TypeFlags.Undefined) {
4198
4292
  return { kind: "primitive", primitiveKind: "null" };
4199
4293
  }
4294
+ if (type.flags & ts3.TypeFlags.Void) {
4295
+ return { kind: "primitive", primitiveKind: "null" };
4296
+ }
4200
4297
  if (type.isStringLiteral()) {
4201
4298
  return {
4202
4299
  kind: "enum",
@@ -6379,7 +6476,20 @@ function unwrapPromiseType(checker, type) {
6379
6476
  if (!("getAwaitedType" in checker) || typeof checker.getAwaitedType !== "function") {
6380
6477
  return type;
6381
6478
  }
6382
- return checker.getAwaitedType(type) ?? type;
6479
+ const awaited = checker.getAwaitedType(type) ?? type;
6480
+ if (awaited === type && looksLikePromiseType(checker, type)) {
6481
+ throw new Error(
6482
+ `FormSpec could not unwrap the awaited type from "${checker.typeToString(type)}". This usually indicates the TypeScript compiler host cannot resolve its default lib files (for example "lib.es2015.promise.d.ts"). Ensure the program is configured with the standard library available to \`ts.createProgram\`.`
6483
+ );
6484
+ }
6485
+ return awaited;
6486
+ }
6487
+ function looksLikePromiseType(checker, type) {
6488
+ const symbolName = type.getSymbol()?.getName();
6489
+ if (symbolName === "Promise" || symbolName === "PromiseLike") {
6490
+ return true;
6491
+ }
6492
+ return /^(?:Promise|PromiseLike)\b/.test(checker.typeToString(type));
6383
6493
  }
6384
6494
  function unwrapPromiseTypeNode(typeNode) {
6385
6495
  if (typeNode === void 0) {