@formspec/build 0.1.0-alpha.54 → 0.1.0-alpha.57

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/internals.js CHANGED
@@ -828,6 +828,7 @@ import {
828
828
  isBuiltinConstraintName
829
829
  } from "@formspec/core/internals";
830
830
  import "@formspec/core/internals";
831
+ import { noopLogger } from "@formspec/core";
831
832
 
832
833
  // src/extensions/resolve-custom-type.ts
833
834
  import * as ts2 from "typescript";
@@ -955,6 +956,15 @@ function isIntegerBrandedType(type) {
955
956
  }
956
957
 
957
958
  // src/analyzer/tsdoc-parser.ts
959
+ import {
960
+ getBuildLogger,
961
+ getBroadeningLogger,
962
+ getSyntheticLogger,
963
+ describeTypeKind,
964
+ elapsedMicros,
965
+ nowMicros,
966
+ logTagApplication
967
+ } from "@formspec/analysis/internal";
958
968
  function sharedTagValueOptions(options) {
959
969
  return {
960
970
  ...options?.extensionRegistry !== void 0 ? { registry: options.extensionRegistry } : {},
@@ -1361,56 +1371,82 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
1361
1371
  if (definition === null) {
1362
1372
  return [];
1363
1373
  }
1374
+ const nonNullPlacement = placement;
1375
+ const log = getBuildLogger();
1376
+ const broadeningLog = getBroadeningLogger();
1377
+ const syntheticLog = getSyntheticLogger();
1378
+ const logsEnabled = log !== noopLogger || broadeningLog !== noopLogger;
1379
+ const syntheticTraceEnabled = syntheticLog !== noopLogger;
1380
+ const logStart = logsEnabled ? nowMicros() : 0;
1381
+ const subjectTypeKind = logsEnabled ? describeTypeKind(subjectType, checker) : "";
1382
+ function emit(outcome, result2) {
1383
+ if (!logsEnabled) {
1384
+ return result2;
1385
+ }
1386
+ const entry = {
1387
+ consumer: "build",
1388
+ tag: tagName,
1389
+ placement: nonNullPlacement,
1390
+ subjectTypeKind,
1391
+ roleOutcome: outcome,
1392
+ elapsedMicros: elapsedMicros(logStart)
1393
+ };
1394
+ logTagApplication(log, entry);
1395
+ if (outcome === "bypass" || outcome === "D1" || outcome === "D2") {
1396
+ logTagApplication(broadeningLog, entry);
1397
+ }
1398
+ return result2;
1399
+ }
1364
1400
  if (!definition.placements.includes(placement)) {
1365
- return [
1401
+ return emit("A-reject", [
1366
1402
  makeDiagnostic(
1367
1403
  "INVALID_TAG_PLACEMENT",
1368
1404
  `Tag "@${tagName}" is not allowed on ${placementLabel(placement)}.`,
1369
1405
  provenance
1370
1406
  )
1371
- ];
1407
+ ]);
1372
1408
  }
1373
1409
  const target = parsedTag?.target ?? null;
1374
1410
  let evaluatedType = subjectType;
1375
1411
  let targetLabel = node.getText(sourceFile);
1376
1412
  if (target !== null) {
1377
1413
  if (target.kind !== "path") {
1378
- return [
1414
+ return emit("B-reject", [
1379
1415
  makeDiagnostic(
1380
1416
  "UNSUPPORTED_TARGETING_SYNTAX",
1381
1417
  `Tag "@${tagName}" does not support ${target.kind} targeting syntax.`,
1382
1418
  provenance
1383
1419
  )
1384
- ];
1420
+ ]);
1385
1421
  }
1386
1422
  if (!target.valid || target.path === null) {
1387
- return [
1423
+ return emit("B-reject", [
1388
1424
  makeDiagnostic(
1389
1425
  "UNSUPPORTED_TARGETING_SYNTAX",
1390
1426
  `Tag "@${tagName}" has invalid path targeting syntax.`,
1391
1427
  provenance
1392
1428
  )
1393
- ];
1429
+ ]);
1394
1430
  }
1395
1431
  const resolution = resolvePathTargetType(subjectType, checker, target.path.segments);
1396
1432
  if (resolution.kind === "missing-property") {
1397
- return [
1433
+ return emit("B-reject", [
1398
1434
  makeDiagnostic(
1399
1435
  "UNKNOWN_PATH_TARGET",
1400
1436
  `Target "${target.rawText}": path-targeted constraint "${tagName}" references unknown path segment "${resolution.segment}"`,
1401
1437
  provenance
1402
1438
  )
1403
- ];
1439
+ ]);
1404
1440
  }
1405
1441
  if (resolution.kind === "unresolvable") {
1406
1442
  const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
1407
- return [
1443
+ return emit("B-reject", [
1408
1444
  makeDiagnostic(
1409
1445
  "TYPE_MISMATCH",
1410
1446
  `Target "${target.rawText}": path-targeted constraint "${tagName}" is invalid because type "${actualType}" cannot be traversed`,
1411
1447
  provenance
1412
1448
  )
1413
- ];
1449
+ ]);
1414
1450
  }
1415
1451
  evaluatedType = resolution.type;
1416
1452
  targetLabel = target.rawText;
@@ -1439,13 +1475,13 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
1439
1475
  tagName,
1440
1476
  parsedTag?.argumentText
1441
1477
  ) : null;
1442
- return [
1478
+ return emit("B-reject", [
1443
1479
  makeDiagnostic(
1444
1480
  "TYPE_MISMATCH",
1445
1481
  hint === null ? baseMessage : `${baseMessage}. ${hint}`,
1446
1482
  provenance
1447
1483
  )
1448
- ];
1484
+ ]);
1449
1485
  }
1450
1486
  }
1451
1487
  const argumentExpression = renderSyntheticArgumentExpression(
@@ -1453,14 +1489,23 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
1453
1489
  parsedTag?.argumentText ?? ""
1454
1490
  );
1455
1491
  if (definition.requiresArgument && argumentExpression === null) {
1456
- return [];
1492
+ return emit("A-pass", []);
1457
1493
  }
1458
1494
  if (hasBroadening) {
1459
- return [];
1495
+ return emit("bypass", []);
1460
1496
  }
1461
1497
  const subjectTypeText = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
1462
1498
  const hostType = options?.hostType ?? subjectType;
1463
1499
  const hostTypeText = checker.typeToString(hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
1500
+ if (syntheticTraceEnabled) {
1501
+ syntheticLog.trace("invoking synthetic checker", {
1502
+ consumer: "build",
1503
+ tag: tagName,
1504
+ placement,
1505
+ subjectTypeKind,
1506
+ subjectTypeText
1507
+ });
1508
+ }
1464
1509
  const result = checkSyntheticTagApplication({
1465
1510
  tagName,
1466
1511
  placement,
@@ -1487,26 +1532,26 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
1487
1532
  } : {}
1488
1533
  });
1489
1534
  if (result.diagnostics.length === 0) {
1490
- return [];
1535
+ return emit("C-pass", []);
1491
1536
  }
1492
1537
  const setupDiagnostic = result.diagnostics.find((diagnostic) => diagnostic.kind !== "typescript");
1493
1538
  if (setupDiagnostic !== void 0) {
1494
- return [
1539
+ return emit("C-reject", [
1495
1540
  makeDiagnostic(
1496
1541
  setupDiagnostic.kind === "unsupported-custom-type-override" ? "UNSUPPORTED_CUSTOM_TYPE_OVERRIDE" : "SYNTHETIC_SETUP_FAILURE",
1497
1542
  setupDiagnostic.message,
1498
1543
  provenance
1499
1544
  )
1500
- ];
1545
+ ]);
1501
1546
  }
1502
1547
  const expectedLabel = definition.valueKind === null ? "compatible argument" : capabilityLabel(definition.valueKind);
1503
- return [
1548
+ return emit("C-reject", [
1504
1549
  makeDiagnostic(
1505
1550
  "TYPE_MISMATCH",
1506
1551
  `Tag "@${tagName}" received an invalid argument for ${expectedLabel}.`,
1507
1552
  provenance
1508
1553
  )
1509
- ];
1554
+ ]);
1510
1555
  }
1511
1556
  var parseResultCache = /* @__PURE__ */ new Map();
1512
1557
  function getExtensionTagNames(options) {
@@ -2666,6 +2711,22 @@ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiti
2666
2711
  }
2667
2712
  return referenceTypeNode.typeArguments.map((argumentNode) => {
2668
2713
  const argumentType = checker.getTypeFromTypeNode(argumentNode);
2714
+ const baseSymbol = argumentType.aliasSymbol ?? argumentType.getSymbol();
2715
+ const argumentSymbol = baseSymbol !== void 0 && baseSymbol.flags & ts6.SymbolFlags.Alias ? checker.getAliasedSymbol(baseSymbol) : baseSymbol;
2716
+ const argumentDecl = argumentSymbol?.declarations?.[0];
2717
+ if (argumentDecl !== void 0 && argumentDecl.getSourceFile().fileName !== file) {
2718
+ const argumentName = argumentSymbol?.getName() ?? baseSymbol?.getName();
2719
+ if (argumentName !== void 0) {
2720
+ return {
2721
+ tsType: argumentType,
2722
+ typeNode: {
2723
+ kind: "reference",
2724
+ name: argumentName,
2725
+ typeArguments: []
2726
+ }
2727
+ };
2728
+ }
2729
+ }
2669
2730
  return {
2670
2731
  tsType: argumentType,
2671
2732
  typeNode: resolveTypeNode(
@@ -2932,22 +2993,10 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2932
2993
  sourceNode
2933
2994
  );
2934
2995
  if (customTypeLookup !== null) {
2935
- const typeId = customTypeIdFromLookup(customTypeLookup);
2936
- let payload = null;
2937
- if (customTypeLookup.registration.extractPayload !== void 0) {
2938
- try {
2939
- payload = customTypeLookup.registration.extractPayload(type, checker) ?? null;
2940
- } catch (cause) {
2941
- throw new Error(
2942
- `extractPayload for custom type "${customTypeLookup.registration.typeName}" in extension "${customTypeLookup.extensionId}" threw`,
2943
- { cause }
2944
- );
2945
- }
2946
- }
2947
2996
  return {
2948
2997
  kind: "custom",
2949
- typeId,
2950
- payload
2998
+ typeId: customTypeIdFromLookup(customTypeLookup),
2999
+ payload: null
2951
3000
  };
2952
3001
  }
2953
3002
  const primitiveAlias = tryResolveNamedPrimitiveAlias(
@@ -3131,6 +3180,21 @@ function getReferencedTypeAliasDeclaration(sourceNode, checker) {
3131
3180
  }
3132
3181
  return getTypeAliasDeclarationFromTypeReference(typeNode, checker);
3133
3182
  }
3183
+ function resolveNamedTypeWithSourceRecovery(type, sourceNode, checker) {
3184
+ const typeName = getNamedTypeName(type);
3185
+ const namedDecl = getNamedTypeDeclaration(type);
3186
+ if (typeName !== null && namedDecl !== void 0) {
3187
+ return { typeName, namedDecl };
3188
+ }
3189
+ if (sourceNode === void 0) {
3190
+ return null;
3191
+ }
3192
+ const refAliasDecl = getReferencedTypeAliasDeclaration(sourceNode, checker);
3193
+ if (refAliasDecl === void 0) {
3194
+ return null;
3195
+ }
3196
+ return { typeName: refAliasDecl.name.text, namedDecl: refAliasDecl };
3197
+ }
3134
3198
  function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
3135
3199
  if (!ts6.isTypeReferenceNode(typeNode)) {
3136
3200
  return false;
@@ -3189,8 +3253,23 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
3189
3253
  );
3190
3254
  }
3191
3255
  function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = createAnalyzerMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3192
- const typeName = getNamedTypeName(type);
3193
- const namedDecl = getNamedTypeDeclaration(type);
3256
+ const recovered = resolveNamedTypeWithSourceRecovery(type, sourceNode, checker);
3257
+ let typeName = null;
3258
+ let namedDecl;
3259
+ if (recovered !== null) {
3260
+ const recoveredAliasDecl = ts6.isTypeAliasDeclaration(recovered.namedDecl) ? recovered.namedDecl : void 0;
3261
+ if (recoveredAliasDecl !== void 0) {
3262
+ const aliasUnderlyingType = checker.getTypeFromTypeNode(recoveredAliasDecl.type);
3263
+ const isNonGeneric = recoveredAliasDecl.typeParameters === void 0 || recoveredAliasDecl.typeParameters.length === 0;
3264
+ if (isNonGeneric && (aliasUnderlyingType.isUnion() || isObjectType(aliasUnderlyingType))) {
3265
+ typeName = recovered.typeName;
3266
+ namedDecl = recovered.namedDecl;
3267
+ }
3268
+ } else {
3269
+ typeName = recovered.typeName;
3270
+ namedDecl = recovered.namedDecl;
3271
+ }
3272
+ }
3194
3273
  if (typeName && typeName in typeRegistry) {
3195
3274
  return { kind: "reference", name: typeName, typeArguments: [] };
3196
3275
  }
@@ -3222,6 +3301,10 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
3222
3301
  if (!typeName) {
3223
3302
  return result;
3224
3303
  }
3304
+ const existing = typeRegistry[typeName];
3305
+ if (existing !== void 0 && existing.type !== RESOLVING_TYPE_PLACEHOLDER) {
3306
+ return { kind: "reference", name: typeName, typeArguments: [] };
3307
+ }
3225
3308
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
3226
3309
  const metadata = namedDecl !== void 0 ? resolveNodeMetadata(
3227
3310
  metadataPolicy,
@@ -4330,20 +4413,29 @@ function assertNoSerializedNameCollisions(ir) {
4330
4413
  }
4331
4414
 
4332
4415
  // src/json-schema/ir-generator.ts
4416
+ function parseEnumSerialization(value) {
4417
+ switch (value) {
4418
+ case void 0:
4419
+ case "enum":
4420
+ return "enum";
4421
+ case "oneOf":
4422
+ return "oneOf";
4423
+ case "smart-size":
4424
+ return "smart-size";
4425
+ default:
4426
+ throw new Error(
4427
+ `Invalid enumSerialization "${String(value)}". Expected "enum", "oneOf", or "smart-size".`
4428
+ );
4429
+ }
4430
+ }
4333
4431
  function makeContext(options) {
4334
4432
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
4335
- const rawEnumSerialization = options?.enumSerialization;
4433
+ const enumSerialization = parseEnumSerialization(options?.enumSerialization);
4336
4434
  if (!vendorPrefix.startsWith("x-")) {
4337
4435
  throw new Error(
4338
4436
  `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
4339
4437
  );
4340
4438
  }
4341
- if (rawEnumSerialization !== void 0 && rawEnumSerialization !== "enum" && rawEnumSerialization !== "oneOf") {
4342
- throw new Error(
4343
- `Invalid enumSerialization "${rawEnumSerialization}". Expected "enum" or "oneOf".`
4344
- );
4345
- }
4346
- const enumSerialization = rawEnumSerialization ?? "enum";
4347
4439
  return {
4348
4440
  defs: {},
4349
4441
  typeNameMap: {},
@@ -4551,21 +4643,31 @@ function generatePrimitiveType(type) {
4551
4643
  };
4552
4644
  }
4553
4645
  function generateEnumType(type, ctx) {
4554
- if (ctx.enumSerialization === "oneOf") {
4646
+ if (ctx.enumSerialization === "oneOf" || ctx.enumSerialization === "smart-size" && shouldSerializeEnumAsOneOf(type)) {
4555
4647
  return {
4556
- oneOf: type.members.map((m) => ({
4557
- const: m.value,
4558
- title: m.displayName ?? String(m.value)
4559
- }))
4648
+ oneOf: type.members.map((m) => {
4649
+ const stringValue = String(m.value);
4650
+ const title = m.displayName !== void 0 && m.displayName !== stringValue ? m.displayName : void 0;
4651
+ return title !== void 0 ? { const: m.value, title } : { const: m.value };
4652
+ })
4560
4653
  };
4561
4654
  }
4562
4655
  const schema = { enum: type.members.map((m) => m.value) };
4656
+ if (ctx.enumSerialization === "smart-size") {
4657
+ return schema;
4658
+ }
4563
4659
  const displayNames = buildEnumDisplayNameExtension(type);
4564
4660
  if (displayNames !== void 0) {
4565
4661
  schema[`${ctx.vendorPrefix}-display-names`] = displayNames;
4566
4662
  }
4567
4663
  return schema;
4568
4664
  }
4665
+ function shouldSerializeEnumAsOneOf(type) {
4666
+ return type.members.some((member) => {
4667
+ const title = member.displayName ?? String(member.value);
4668
+ return title !== String(member.value);
4669
+ });
4670
+ }
4569
4671
  function buildEnumDisplayNameExtension(type) {
4570
4672
  if (!type.members.some((member) => member.displayName !== void 0)) {
4571
4673
  return void 0;
@@ -5051,7 +5153,8 @@ import {
5051
5153
  } from "@formspec/core/internals";
5052
5154
  import {
5053
5155
  getTagDefinition as getTagDefinition2,
5054
- normalizeFormSpecTagName as normalizeFormSpecTagName2
5156
+ normalizeFormSpecTagName as normalizeFormSpecTagName2,
5157
+ getSyntheticLogger as getSyntheticLogger2
5055
5158
  } from "@formspec/analysis/internal";
5056
5159
  var BUILTIN_METADATA_TAGS = /* @__PURE__ */ new Set(["apiName", "displayName"]);
5057
5160
  function buildConstraintTagSources(extensions) {
@@ -5065,6 +5168,11 @@ function buildConstraintTagSources(extensions) {
5065
5168
  }));
5066
5169
  }
5067
5170
  function createExtensionRegistry(extensions) {
5171
+ const registryLog = getSyntheticLogger2();
5172
+ registryLog.debug("createExtensionRegistry: constructing", {
5173
+ extensionCount: extensions.length,
5174
+ extensionIds: extensions.map((e) => e.extensionId)
5175
+ });
5068
5176
  const reservedTagSources = buildConstraintTagSources(extensions);
5069
5177
  let symbolMap = /* @__PURE__ */ new Map();
5070
5178
  const typeMap = /* @__PURE__ */ new Map();
@@ -5196,6 +5304,14 @@ function createExtensionRegistry(extensions) {
5196
5304
  }
5197
5305
  }
5198
5306
  }
5307
+ registryLog.debug("createExtensionRegistry: complete", {
5308
+ typeCount: typeMap.size,
5309
+ constraintCount: constraintMap.size,
5310
+ constraintTagCount: constraintTagMap.size,
5311
+ broadeningCount: builtinBroadeningMap.size,
5312
+ annotationCount: annotationMap.size,
5313
+ metadataSlotCount: metadataSlotMap.size
5314
+ });
5199
5315
  return {
5200
5316
  extensions,
5201
5317
  findType: (typeId) => typeMap.get(typeId),