@formspec/build 0.1.0-alpha.47 → 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/internals.js CHANGED
@@ -999,6 +999,92 @@ function supportsConstraintCapability(type, checker, capability) {
999
999
  }
1000
1000
  return false;
1001
1001
  }
1002
+ var MAX_HINT_CANDIDATES = 5;
1003
+ var MAX_HINT_DEPTH = 3;
1004
+ function stripHintNullishUnion(type) {
1005
+ if (!type.isUnion()) {
1006
+ return type;
1007
+ }
1008
+ const nonNullish = type.types.filter(
1009
+ (member) => (member.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) === 0
1010
+ );
1011
+ if (nonNullish.length === 1 && nonNullish[0] !== void 0) {
1012
+ return nonNullish[0];
1013
+ }
1014
+ return type;
1015
+ }
1016
+ function isCallableType(type) {
1017
+ return type.getCallSignatures().length > 0 || type.getConstructSignatures().length > 0;
1018
+ }
1019
+ function isUserEmittableHintProperty(property, declaration) {
1020
+ if (property.name.startsWith("__")) {
1021
+ return false;
1022
+ }
1023
+ if ("name" in declaration && declaration.name !== void 0) {
1024
+ const name = declaration.name;
1025
+ if (ts.isComputedPropertyName(name) || ts.isPrivateIdentifier(name)) {
1026
+ return false;
1027
+ }
1028
+ if (!ts.isIdentifier(name) && !ts.isStringLiteral(name) && !ts.isNumericLiteral(name)) {
1029
+ return false;
1030
+ }
1031
+ }
1032
+ return true;
1033
+ }
1034
+ function collectObjectSubfieldCandidates(type, checker, capability) {
1035
+ const out = [];
1036
+ const visit = (current, prefix, depth) => {
1037
+ if (depth > MAX_HINT_DEPTH) {
1038
+ return;
1039
+ }
1040
+ const stripped = stripHintNullishUnion(current);
1041
+ if (isCallableType(stripped)) {
1042
+ return;
1043
+ }
1044
+ if (!hasTypeSemanticCapability(stripped, checker, "object-like")) {
1045
+ return;
1046
+ }
1047
+ for (const property of stripped.getProperties()) {
1048
+ const declaration = property.valueDeclaration ?? property.declarations?.[0];
1049
+ if (declaration === void 0) {
1050
+ continue;
1051
+ }
1052
+ if (!isUserEmittableHintProperty(property, declaration)) {
1053
+ continue;
1054
+ }
1055
+ const propertyType = checker.getTypeOfSymbolAtLocation(property, declaration);
1056
+ const path3 = [...prefix, property.name];
1057
+ if (supportsConstraintCapability(propertyType, checker, capability)) {
1058
+ out.push(path3.join("."));
1059
+ continue;
1060
+ }
1061
+ const strippedPropertyType = stripHintNullishUnion(propertyType);
1062
+ if (!isCallableType(strippedPropertyType) && hasTypeSemanticCapability(strippedPropertyType, checker, "object-like")) {
1063
+ visit(strippedPropertyType, path3, depth + 1);
1064
+ }
1065
+ }
1066
+ };
1067
+ visit(type, [], 0);
1068
+ return out;
1069
+ }
1070
+ function buildPathTargetHint(subjectType, checker, capability, tagName, argumentText) {
1071
+ if (!hasTypeSemanticCapability(subjectType, checker, "object-like")) {
1072
+ return null;
1073
+ }
1074
+ const candidates = collectObjectSubfieldCandidates(subjectType, checker, capability);
1075
+ const primary = candidates[0];
1076
+ if (primary === void 0) {
1077
+ return null;
1078
+ }
1079
+ const argText = argumentText?.trim() ?? "";
1080
+ const renderExample = (path3) => argText === "" ? `@${tagName} :${path3}` : `@${tagName} :${path3} ${argText}`;
1081
+ if (candidates.length === 1) {
1082
+ return `Hint: use a path target to constrain a subfield, e.g. ${renderExample(primary)}`;
1083
+ }
1084
+ const shown = candidates.slice(0, MAX_HINT_CANDIDATES);
1085
+ const overflow = candidates.length > MAX_HINT_CANDIDATES ? ", \u2026" : "";
1086
+ return `Hint: use a path target to constrain a subfield (candidates: ${shown.join(", ")}${overflow}), e.g. ${renderExample(primary)}`;
1087
+ }
1002
1088
  function makeDiagnostic(code, message, provenance) {
1003
1089
  return {
1004
1090
  code,
@@ -1166,10 +1252,18 @@ function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, par
1166
1252
  const requiredCapability = definition.capabilities[0];
1167
1253
  if (requiredCapability !== void 0 && !supportsConstraintCapability(subjectType, checker, requiredCapability)) {
1168
1254
  const actualType = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
1255
+ const baseMessage = `Target "${node.getText(sourceFile)}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`;
1256
+ const hint = buildPathTargetHint(
1257
+ subjectType,
1258
+ checker,
1259
+ requiredCapability,
1260
+ tagName,
1261
+ parsedTag?.argumentText
1262
+ );
1169
1263
  return [
1170
1264
  makeDiagnostic(
1171
1265
  "TYPE_MISMATCH",
1172
- `Target "${node.getText(sourceFile)}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
1266
+ hint === null ? baseMessage : `${baseMessage}. ${hint}`,
1173
1267
  provenance
1174
1268
  )
1175
1269
  ];
@@ -2819,6 +2913,9 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2819
2913
  if (type.flags & ts3.TypeFlags.Undefined) {
2820
2914
  return { kind: "primitive", primitiveKind: "null" };
2821
2915
  }
2916
+ if (type.flags & ts3.TypeFlags.Void) {
2917
+ return { kind: "primitive", primitiveKind: "null" };
2918
+ }
2822
2919
  if (type.isStringLiteral()) {
2823
2920
  return {
2824
2921
  kind: "enum",