@atscript/typescript 0.1.25 → 0.1.26

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/cli.cjs CHANGED
@@ -971,6 +971,7 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
971
971
  __is_atscript_annotated_type: true,
972
972
  type: newBase.type,
973
973
  metadata,
974
+ id: newBase.id,
974
975
  validator(opts) {
975
976
  return new Validator(this, opts);
976
977
  }
@@ -981,6 +982,10 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
981
982
  annotate(key, value, asArray) {
982
983
  annotate(this.$metadata, key, value, asArray);
983
984
  return this;
985
+ },
986
+ id(value) {
987
+ this.$type.id = value;
988
+ return this;
984
989
  }
985
990
  };
986
991
  return handle;
@@ -1032,47 +1037,71 @@ function isPhantomType(def) {
1032
1037
  return null;
1033
1038
  }
1034
1039
  function buildJsonSchema(type) {
1040
+ const defs = {};
1041
+ let isRoot = true;
1042
+ const buildObject = (d) => {
1043
+ const properties = {};
1044
+ const required = [];
1045
+ for (const [key, val] of d.type.props.entries()) {
1046
+ if (isPhantomType(val)) continue;
1047
+ properties[key] = build$1(val);
1048
+ if (!val.optional) required.push(key);
1049
+ }
1050
+ const schema$1 = {
1051
+ type: "object",
1052
+ properties
1053
+ };
1054
+ if (required.length > 0) schema$1.required = required;
1055
+ return schema$1;
1056
+ };
1035
1057
  const build$1 = (def) => {
1058
+ if (def.id && def.type.kind === "object" && !isRoot) {
1059
+ const name = def.id;
1060
+ if (!defs[name]) {
1061
+ defs[name] = {};
1062
+ defs[name] = buildObject(def);
1063
+ }
1064
+ return { $ref: `#/$defs/${name}` };
1065
+ }
1066
+ isRoot = false;
1036
1067
  const meta = def.metadata;
1037
1068
  return forAnnotatedType(def, {
1038
1069
  phantom() {
1039
1070
  return {};
1040
1071
  },
1041
1072
  object(d) {
1042
- const properties = {};
1043
- const required = [];
1044
- for (const [key, val] of d.type.props.entries()) {
1045
- if (isPhantomType(val)) continue;
1046
- properties[key] = build$1(val);
1047
- if (!val.optional) required.push(key);
1048
- }
1049
- const schema = {
1050
- type: "object",
1051
- properties
1052
- };
1053
- if (required.length > 0) schema.required = required;
1054
- return schema;
1073
+ return buildObject(d);
1055
1074
  },
1056
1075
  array(d) {
1057
- const schema = {
1076
+ const schema$1 = {
1058
1077
  type: "array",
1059
1078
  items: build$1(d.type.of)
1060
1079
  };
1061
1080
  const minLength = meta.get("expect.minLength");
1062
- if (minLength) schema.minItems = typeof minLength === "number" ? minLength : minLength.length;
1081
+ if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
1063
1082
  const maxLength = meta.get("expect.maxLength");
1064
- if (maxLength) schema.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
1065
- return schema;
1083
+ if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
1084
+ return schema$1;
1066
1085
  },
1067
1086
  union(d) {
1068
1087
  const disc = detectDiscriminator(d.type.items);
1069
- if (disc) return {
1070
- oneOf: d.type.items.map(build$1),
1071
- discriminator: {
1072
- propertyName: disc.propertyName,
1073
- mapping: disc.mapping
1088
+ if (disc) {
1089
+ const oneOf = d.type.items.map(build$1);
1090
+ const mapping = {};
1091
+ for (const [val, origPath] of Object.entries(disc.mapping)) {
1092
+ const idx = Number.parseInt(origPath.split("/").pop());
1093
+ const item = d.type.items[idx];
1094
+ if (item.id && defs[item.id]) mapping[val] = `#/$defs/${item.id}`;
1095
+ else mapping[val] = origPath;
1074
1096
  }
1075
- };
1097
+ return {
1098
+ oneOf,
1099
+ discriminator: {
1100
+ propertyName: disc.propertyName,
1101
+ mapping
1102
+ }
1103
+ };
1104
+ }
1076
1105
  return { anyOf: d.type.items.map(build$1) };
1077
1106
  },
1078
1107
  intersection(d) {
@@ -1086,33 +1115,38 @@ function buildJsonSchema(type) {
1086
1115
  };
1087
1116
  },
1088
1117
  final(d) {
1089
- const schema = {};
1090
- if (d.type.value !== undefined) schema.const = d.type.value;
1118
+ const schema$1 = {};
1119
+ if (d.type.value !== undefined) schema$1.const = d.type.value;
1091
1120
  if (d.type.designType && d.type.designType !== "any") {
1092
- schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
1093
- if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
1121
+ schema$1.type = d.type.designType === "undefined" ? "null" : d.type.designType;
1122
+ if (schema$1.type === "number" && meta.get("expect.int")) schema$1.type = "integer";
1094
1123
  }
1095
- if (schema.type === "string") {
1096
- if (meta.get("meta.required")) schema.minLength = 1;
1124
+ if (schema$1.type === "string") {
1125
+ if (meta.get("meta.required")) schema$1.minLength = 1;
1097
1126
  const minLength = meta.get("expect.minLength");
1098
- if (minLength) schema.minLength = typeof minLength === "number" ? minLength : minLength.length;
1127
+ if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
1099
1128
  const maxLength = meta.get("expect.maxLength");
1100
- if (maxLength) schema.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
1129
+ if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
1101
1130
  const patterns = meta.get("expect.pattern");
1102
- if (patterns?.length) if (patterns.length === 1) schema.pattern = patterns[0].pattern;
1103
- else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
1131
+ if (patterns?.length) if (patterns.length === 1) schema$1.pattern = patterns[0].pattern;
1132
+ else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
1104
1133
  }
1105
- if (schema.type === "number" || schema.type === "integer") {
1134
+ if (schema$1.type === "number" || schema$1.type === "integer") {
1106
1135
  const min = meta.get("expect.min");
1107
- if (min) schema.minimum = typeof min === "number" ? min : min.minValue;
1136
+ if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
1108
1137
  const max = meta.get("expect.max");
1109
- if (max) schema.maximum = typeof max === "number" ? max : max.maxValue;
1138
+ if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
1110
1139
  }
1111
- return schema;
1140
+ return schema$1;
1112
1141
  }
1113
1142
  });
1114
1143
  };
1115
- return build$1(type);
1144
+ const schema = build$1(type);
1145
+ if (Object.keys(defs).length > 0) return {
1146
+ ...schema,
1147
+ $defs: defs
1148
+ };
1149
+ return schema;
1116
1150
  }
1117
1151
 
1118
1152
  //#endregion
@@ -1133,11 +1167,23 @@ var JsRenderer = class extends BaseRenderer {
1133
1167
  this.writeln("/* eslint-disable */");
1134
1168
  this.writeln("/* oxlint-disable */");
1135
1169
  const imports = ["defineAnnotatedType as $", "annotate as $a"];
1170
+ const hasMutatingAnnotate = this.doc.nodes.some((n) => n.entity === "annotate" && n.isMutating);
1171
+ if (hasMutatingAnnotate) imports.push("cloneRefProp as $c");
1136
1172
  const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
1137
1173
  if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
1138
1174
  if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
1139
1175
  if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
1140
1176
  this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
1177
+ const nameCounts = new Map();
1178
+ const nodesByName = new Map();
1179
+ for (const node of this.doc.nodes) if (node.__typeId != null && node.id) {
1180
+ const name = node.id;
1181
+ nameCounts.set(name, (nameCounts.get(name) || 0) + 1);
1182
+ if (!nodesByName.has(name)) nodesByName.set(name, []);
1183
+ nodesByName.get(name).push(node);
1184
+ }
1185
+ for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
1186
+ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
1141
1187
  }
1142
1188
  buildAdHocMap(annotateNodes) {
1143
1189
  const map = new Map();
@@ -1151,6 +1197,15 @@ var JsRenderer = class extends BaseRenderer {
1151
1197
  }
1152
1198
  return map.size > 0 ? map : null;
1153
1199
  }
1200
+ /**
1201
+ * Checks if any ad-hoc annotation path extends beyond the current _propPath,
1202
+ * meaning annotations target properties inside a referenced type.
1203
+ */ hasAdHocAnnotationsThroughRef() {
1204
+ if (!this._adHocAnnotations || this._propPath.length === 0) return false;
1205
+ const prefix = this._propPath.join(".") + ".";
1206
+ for (const key of this._adHocAnnotations.keys()) if (key.startsWith(prefix)) return true;
1207
+ return false;
1208
+ }
1154
1209
  post() {
1155
1210
  for (const node of this.postAnnotate) if (node.entity === "annotate") {
1156
1211
  const annotateNode = node;
@@ -1181,6 +1236,8 @@ else {
1181
1236
  this.writeln("static __is_atscript_annotated_type = true");
1182
1237
  this.writeln("static type = {}");
1183
1238
  this.writeln("static metadata = new Map()");
1239
+ const typeId = this.typeIds.get(node);
1240
+ if (typeId) this.writeln(`static id = "${typeId}"`);
1184
1241
  this.renderJsonSchemaMethod(node);
1185
1242
  this.renderExampleDataMethod(node);
1186
1243
  }
@@ -1259,6 +1316,8 @@ else {
1259
1316
  case "type": {
1260
1317
  const def = node.getDefinition();
1261
1318
  const handle = this.toAnnotatedHandle(def, true);
1319
+ const typeId = this.typeIds.get(node) ?? (node.__typeId != null ? node.id : undefined);
1320
+ if (typeId) handle.id(typeId);
1262
1321
  return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1263
1322
  }
1264
1323
  case "prop": {
@@ -1385,6 +1444,14 @@ else handle.prop(prop.id, propHandle.$type);
1385
1444
  return this;
1386
1445
  }
1387
1446
  }
1447
+ if (this._adHocAnnotations && this.hasAdHocAnnotationsThroughRef()) {
1448
+ let resolved = decl ? this.doc.mergeIntersection(decl) : undefined;
1449
+ if (resolved && (0, __atscript_core.isInterface)(resolved)) resolved = resolved.getDefinition() || resolved;
1450
+ if (resolved) {
1451
+ this.annotateType(resolved, name);
1452
+ return this;
1453
+ }
1454
+ }
1388
1455
  const chain = ref.hasChain ? `, [${ref.chain.map((c) => `"${escapeQuotes(c.text)}"`).join(", ")}]` : "";
1389
1456
  this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().writeln(`.refTo(${ref.id}${chain})`);
1390
1457
  if (!ref.hasChain) {
@@ -1618,11 +1685,29 @@ else targetValue = "true";
1618
1685
  const targetName = node.targetName;
1619
1686
  const targetDef = this.resolveTargetDef(targetName);
1620
1687
  this.writeln("// Ad-hoc annotations for ", targetName);
1688
+ const allClones = [];
1689
+ const entryAccessors = [];
1621
1690
  for (const entry of node.entries) {
1622
1691
  const anns = entry.annotations;
1623
1692
  if (!anns || anns.length === 0) continue;
1624
1693
  const parts = entry.hasChain ? [entry.id, ...entry.chain.map((c) => c.text)] : [entry.id];
1625
- const accessors = this.buildMutatingAccessors(targetName, targetDef, parts);
1694
+ const { accessors, clones } = this.buildMutatingAccessors(targetName, targetDef, parts);
1695
+ allClones.push(...clones);
1696
+ entryAccessors.push({
1697
+ entry,
1698
+ accessors
1699
+ });
1700
+ }
1701
+ const cloneKeys = new Set();
1702
+ for (const clone of allClones) {
1703
+ const key = `${clone.parentPath}|${clone.propName}`;
1704
+ if (!cloneKeys.has(key)) {
1705
+ cloneKeys.add(key);
1706
+ this.writeln(`$c(${clone.parentPath}, "${escapeQuotes(clone.propName)}")`);
1707
+ }
1708
+ }
1709
+ for (const { entry, accessors } of entryAccessors) {
1710
+ const anns = entry.annotations;
1626
1711
  for (const accessor of accessors) {
1627
1712
  const cleared = new Set();
1628
1713
  for (const an of anns) {
@@ -1672,15 +1757,21 @@ else targetValue = "true";
1672
1757
  prefix: `${targetName}.type`,
1673
1758
  def: targetDef
1674
1759
  }];
1760
+ const clones = [];
1675
1761
  for (let i = 0; i < parts.length; i++) {
1676
1762
  const nextAccessors = [];
1677
1763
  for (const { prefix, def } of accessors) {
1678
1764
  const results = this.buildPropPaths(def, parts[i]);
1679
- if (results.length > 0) for (const result of results) if (i < parts.length - 1) nextAccessors.push({
1680
- prefix: `${prefix}${result.path}?.type`,
1681
- def: result.propDef
1682
- });
1683
- else nextAccessors.push({
1765
+ if (results.length > 0) for (const result of results) if (i < parts.length - 1) {
1766
+ if (result.propDef && (0, __atscript_core.isRef)(result.propDef)) clones.push({
1767
+ parentPath: prefix,
1768
+ propName: parts[i]
1769
+ });
1770
+ nextAccessors.push({
1771
+ prefix: `${prefix}${result.path}?.type`,
1772
+ def: result.propDef
1773
+ });
1774
+ } else nextAccessors.push({
1684
1775
  prefix: `${prefix}${result.path}?`,
1685
1776
  def: result.propDef
1686
1777
  });
@@ -1694,7 +1785,10 @@ else {
1694
1785
  }
1695
1786
  accessors = nextAccessors;
1696
1787
  }
1697
- return accessors.map((a) => a.prefix);
1788
+ return {
1789
+ accessors: accessors.map((a) => a.prefix),
1790
+ clones
1791
+ };
1698
1792
  }
1699
1793
  /**
1700
1794
  * Finds a property in a type tree at compile time, returning all
@@ -1730,7 +1824,7 @@ else {
1730
1824
  return [];
1731
1825
  }
1732
1826
  constructor(doc, opts) {
1733
- super(doc), _define_property$1(this, "opts", void 0), _define_property$1(this, "postAnnotate", void 0), _define_property$1(this, "_adHocAnnotations", void 0), _define_property$1(this, "_propPath", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [];
1827
+ super(doc), _define_property$1(this, "opts", void 0), _define_property$1(this, "postAnnotate", void 0), _define_property$1(this, "_adHocAnnotations", void 0), _define_property$1(this, "_propPath", void 0), _define_property$1(this, "typeIds", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [], this.typeIds = new Map();
1734
1828
  }
1735
1829
  };
1736
1830
 
package/dist/index.cjs CHANGED
@@ -968,6 +968,7 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
968
968
  __is_atscript_annotated_type: true,
969
969
  type: newBase.type,
970
970
  metadata,
971
+ id: newBase.id,
971
972
  validator(opts) {
972
973
  return new Validator(this, opts);
973
974
  }
@@ -978,6 +979,10 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
978
979
  annotate(key, value, asArray) {
979
980
  annotate(this.$metadata, key, value, asArray);
980
981
  return this;
982
+ },
983
+ id(value) {
984
+ this.$type.id = value;
985
+ return this;
981
986
  }
982
987
  };
983
988
  return handle;
@@ -1029,47 +1034,71 @@ function isPhantomType(def) {
1029
1034
  return null;
1030
1035
  }
1031
1036
  function buildJsonSchema(type) {
1037
+ const defs = {};
1038
+ let isRoot = true;
1039
+ const buildObject = (d) => {
1040
+ const properties = {};
1041
+ const required = [];
1042
+ for (const [key, val] of d.type.props.entries()) {
1043
+ if (isPhantomType(val)) continue;
1044
+ properties[key] = build(val);
1045
+ if (!val.optional) required.push(key);
1046
+ }
1047
+ const schema$1 = {
1048
+ type: "object",
1049
+ properties
1050
+ };
1051
+ if (required.length > 0) schema$1.required = required;
1052
+ return schema$1;
1053
+ };
1032
1054
  const build = (def) => {
1055
+ if (def.id && def.type.kind === "object" && !isRoot) {
1056
+ const name = def.id;
1057
+ if (!defs[name]) {
1058
+ defs[name] = {};
1059
+ defs[name] = buildObject(def);
1060
+ }
1061
+ return { $ref: `#/$defs/${name}` };
1062
+ }
1063
+ isRoot = false;
1033
1064
  const meta = def.metadata;
1034
1065
  return forAnnotatedType(def, {
1035
1066
  phantom() {
1036
1067
  return {};
1037
1068
  },
1038
1069
  object(d) {
1039
- const properties = {};
1040
- const required = [];
1041
- for (const [key, val] of d.type.props.entries()) {
1042
- if (isPhantomType(val)) continue;
1043
- properties[key] = build(val);
1044
- if (!val.optional) required.push(key);
1045
- }
1046
- const schema = {
1047
- type: "object",
1048
- properties
1049
- };
1050
- if (required.length > 0) schema.required = required;
1051
- return schema;
1070
+ return buildObject(d);
1052
1071
  },
1053
1072
  array(d) {
1054
- const schema = {
1073
+ const schema$1 = {
1055
1074
  type: "array",
1056
1075
  items: build(d.type.of)
1057
1076
  };
1058
1077
  const minLength = meta.get("expect.minLength");
1059
- if (minLength) schema.minItems = typeof minLength === "number" ? minLength : minLength.length;
1078
+ if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
1060
1079
  const maxLength = meta.get("expect.maxLength");
1061
- if (maxLength) schema.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
1062
- return schema;
1080
+ if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
1081
+ return schema$1;
1063
1082
  },
1064
1083
  union(d) {
1065
1084
  const disc = detectDiscriminator(d.type.items);
1066
- if (disc) return {
1067
- oneOf: d.type.items.map(build),
1068
- discriminator: {
1069
- propertyName: disc.propertyName,
1070
- mapping: disc.mapping
1085
+ if (disc) {
1086
+ const oneOf = d.type.items.map(build);
1087
+ const mapping = {};
1088
+ for (const [val, origPath] of Object.entries(disc.mapping)) {
1089
+ const idx = Number.parseInt(origPath.split("/").pop());
1090
+ const item = d.type.items[idx];
1091
+ if (item.id && defs[item.id]) mapping[val] = `#/$defs/${item.id}`;
1092
+ else mapping[val] = origPath;
1071
1093
  }
1072
- };
1094
+ return {
1095
+ oneOf,
1096
+ discriminator: {
1097
+ propertyName: disc.propertyName,
1098
+ mapping
1099
+ }
1100
+ };
1101
+ }
1073
1102
  return { anyOf: d.type.items.map(build) };
1074
1103
  },
1075
1104
  intersection(d) {
@@ -1083,33 +1112,38 @@ function buildJsonSchema(type) {
1083
1112
  };
1084
1113
  },
1085
1114
  final(d) {
1086
- const schema = {};
1087
- if (d.type.value !== undefined) schema.const = d.type.value;
1115
+ const schema$1 = {};
1116
+ if (d.type.value !== undefined) schema$1.const = d.type.value;
1088
1117
  if (d.type.designType && d.type.designType !== "any") {
1089
- schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
1090
- if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
1118
+ schema$1.type = d.type.designType === "undefined" ? "null" : d.type.designType;
1119
+ if (schema$1.type === "number" && meta.get("expect.int")) schema$1.type = "integer";
1091
1120
  }
1092
- if (schema.type === "string") {
1093
- if (meta.get("meta.required")) schema.minLength = 1;
1121
+ if (schema$1.type === "string") {
1122
+ if (meta.get("meta.required")) schema$1.minLength = 1;
1094
1123
  const minLength = meta.get("expect.minLength");
1095
- if (minLength) schema.minLength = typeof minLength === "number" ? minLength : minLength.length;
1124
+ if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
1096
1125
  const maxLength = meta.get("expect.maxLength");
1097
- if (maxLength) schema.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
1126
+ if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
1098
1127
  const patterns = meta.get("expect.pattern");
1099
- if (patterns?.length) if (patterns.length === 1) schema.pattern = patterns[0].pattern;
1100
- else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
1128
+ if (patterns?.length) if (patterns.length === 1) schema$1.pattern = patterns[0].pattern;
1129
+ else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
1101
1130
  }
1102
- if (schema.type === "number" || schema.type === "integer") {
1131
+ if (schema$1.type === "number" || schema$1.type === "integer") {
1103
1132
  const min = meta.get("expect.min");
1104
- if (min) schema.minimum = typeof min === "number" ? min : min.minValue;
1133
+ if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
1105
1134
  const max = meta.get("expect.max");
1106
- if (max) schema.maximum = typeof max === "number" ? max : max.maxValue;
1135
+ if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
1107
1136
  }
1108
- return schema;
1137
+ return schema$1;
1109
1138
  }
1110
1139
  });
1111
1140
  };
1112
- return build(type);
1141
+ const schema = build(type);
1142
+ if (Object.keys(defs).length > 0) return {
1143
+ ...schema,
1144
+ $defs: defs
1145
+ };
1146
+ return schema;
1113
1147
  }
1114
1148
 
1115
1149
  //#endregion
@@ -1130,11 +1164,23 @@ var JsRenderer = class extends BaseRenderer {
1130
1164
  this.writeln("/* eslint-disable */");
1131
1165
  this.writeln("/* oxlint-disable */");
1132
1166
  const imports = ["defineAnnotatedType as $", "annotate as $a"];
1167
+ const hasMutatingAnnotate = this.doc.nodes.some((n) => n.entity === "annotate" && n.isMutating);
1168
+ if (hasMutatingAnnotate) imports.push("cloneRefProp as $c");
1133
1169
  const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
1134
1170
  if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
1135
1171
  if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
1136
1172
  if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
1137
1173
  this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
1174
+ const nameCounts = new Map();
1175
+ const nodesByName = new Map();
1176
+ for (const node of this.doc.nodes) if (node.__typeId != null && node.id) {
1177
+ const name = node.id;
1178
+ nameCounts.set(name, (nameCounts.get(name) || 0) + 1);
1179
+ if (!nodesByName.has(name)) nodesByName.set(name, []);
1180
+ nodesByName.get(name).push(node);
1181
+ }
1182
+ for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
1183
+ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
1138
1184
  }
1139
1185
  buildAdHocMap(annotateNodes) {
1140
1186
  const map = new Map();
@@ -1148,6 +1194,15 @@ var JsRenderer = class extends BaseRenderer {
1148
1194
  }
1149
1195
  return map.size > 0 ? map : null;
1150
1196
  }
1197
+ /**
1198
+ * Checks if any ad-hoc annotation path extends beyond the current _propPath,
1199
+ * meaning annotations target properties inside a referenced type.
1200
+ */ hasAdHocAnnotationsThroughRef() {
1201
+ if (!this._adHocAnnotations || this._propPath.length === 0) return false;
1202
+ const prefix = this._propPath.join(".") + ".";
1203
+ for (const key of this._adHocAnnotations.keys()) if (key.startsWith(prefix)) return true;
1204
+ return false;
1205
+ }
1151
1206
  post() {
1152
1207
  for (const node of this.postAnnotate) if (node.entity === "annotate") {
1153
1208
  const annotateNode = node;
@@ -1178,6 +1233,8 @@ else {
1178
1233
  this.writeln("static __is_atscript_annotated_type = true");
1179
1234
  this.writeln("static type = {}");
1180
1235
  this.writeln("static metadata = new Map()");
1236
+ const typeId = this.typeIds.get(node);
1237
+ if (typeId) this.writeln(`static id = "${typeId}"`);
1181
1238
  this.renderJsonSchemaMethod(node);
1182
1239
  this.renderExampleDataMethod(node);
1183
1240
  }
@@ -1256,6 +1313,8 @@ else {
1256
1313
  case "type": {
1257
1314
  const def = node.getDefinition();
1258
1315
  const handle = this.toAnnotatedHandle(def, true);
1316
+ const typeId = this.typeIds.get(node) ?? (node.__typeId != null ? node.id : undefined);
1317
+ if (typeId) handle.id(typeId);
1259
1318
  return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1260
1319
  }
1261
1320
  case "prop": {
@@ -1382,6 +1441,14 @@ else handle.prop(prop.id, propHandle.$type);
1382
1441
  return this;
1383
1442
  }
1384
1443
  }
1444
+ if (this._adHocAnnotations && this.hasAdHocAnnotationsThroughRef()) {
1445
+ let resolved = decl ? this.doc.mergeIntersection(decl) : undefined;
1446
+ if (resolved && (0, __atscript_core.isInterface)(resolved)) resolved = resolved.getDefinition() || resolved;
1447
+ if (resolved) {
1448
+ this.annotateType(resolved, name);
1449
+ return this;
1450
+ }
1451
+ }
1385
1452
  const chain = ref.hasChain ? `, [${ref.chain.map((c) => `"${escapeQuotes(c.text)}"`).join(", ")}]` : "";
1386
1453
  this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().writeln(`.refTo(${ref.id}${chain})`);
1387
1454
  if (!ref.hasChain) {
@@ -1615,11 +1682,29 @@ else targetValue = "true";
1615
1682
  const targetName = node.targetName;
1616
1683
  const targetDef = this.resolveTargetDef(targetName);
1617
1684
  this.writeln("// Ad-hoc annotations for ", targetName);
1685
+ const allClones = [];
1686
+ const entryAccessors = [];
1618
1687
  for (const entry of node.entries) {
1619
1688
  const anns = entry.annotations;
1620
1689
  if (!anns || anns.length === 0) continue;
1621
1690
  const parts = entry.hasChain ? [entry.id, ...entry.chain.map((c) => c.text)] : [entry.id];
1622
- const accessors = this.buildMutatingAccessors(targetName, targetDef, parts);
1691
+ const { accessors, clones } = this.buildMutatingAccessors(targetName, targetDef, parts);
1692
+ allClones.push(...clones);
1693
+ entryAccessors.push({
1694
+ entry,
1695
+ accessors
1696
+ });
1697
+ }
1698
+ const cloneKeys = new Set();
1699
+ for (const clone of allClones) {
1700
+ const key = `${clone.parentPath}|${clone.propName}`;
1701
+ if (!cloneKeys.has(key)) {
1702
+ cloneKeys.add(key);
1703
+ this.writeln(`$c(${clone.parentPath}, "${escapeQuotes(clone.propName)}")`);
1704
+ }
1705
+ }
1706
+ for (const { entry, accessors } of entryAccessors) {
1707
+ const anns = entry.annotations;
1623
1708
  for (const accessor of accessors) {
1624
1709
  const cleared = new Set();
1625
1710
  for (const an of anns) {
@@ -1669,15 +1754,21 @@ else targetValue = "true";
1669
1754
  prefix: `${targetName}.type`,
1670
1755
  def: targetDef
1671
1756
  }];
1757
+ const clones = [];
1672
1758
  for (let i = 0; i < parts.length; i++) {
1673
1759
  const nextAccessors = [];
1674
1760
  for (const { prefix, def } of accessors) {
1675
1761
  const results = this.buildPropPaths(def, parts[i]);
1676
- if (results.length > 0) for (const result of results) if (i < parts.length - 1) nextAccessors.push({
1677
- prefix: `${prefix}${result.path}?.type`,
1678
- def: result.propDef
1679
- });
1680
- else nextAccessors.push({
1762
+ if (results.length > 0) for (const result of results) if (i < parts.length - 1) {
1763
+ if (result.propDef && (0, __atscript_core.isRef)(result.propDef)) clones.push({
1764
+ parentPath: prefix,
1765
+ propName: parts[i]
1766
+ });
1767
+ nextAccessors.push({
1768
+ prefix: `${prefix}${result.path}?.type`,
1769
+ def: result.propDef
1770
+ });
1771
+ } else nextAccessors.push({
1681
1772
  prefix: `${prefix}${result.path}?`,
1682
1773
  def: result.propDef
1683
1774
  });
@@ -1691,7 +1782,10 @@ else {
1691
1782
  }
1692
1783
  accessors = nextAccessors;
1693
1784
  }
1694
- return accessors.map((a) => a.prefix);
1785
+ return {
1786
+ accessors: accessors.map((a) => a.prefix),
1787
+ clones
1788
+ };
1695
1789
  }
1696
1790
  /**
1697
1791
  * Finds a property in a type tree at compile time, returning all
@@ -1727,7 +1821,7 @@ else {
1727
1821
  return [];
1728
1822
  }
1729
1823
  constructor(doc, opts) {
1730
- super(doc), _define_property(this, "opts", void 0), _define_property(this, "postAnnotate", void 0), _define_property(this, "_adHocAnnotations", void 0), _define_property(this, "_propPath", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [];
1824
+ super(doc), _define_property(this, "opts", void 0), _define_property(this, "postAnnotate", void 0), _define_property(this, "_adHocAnnotations", void 0), _define_property(this, "_propPath", void 0), _define_property(this, "typeIds", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [], this.typeIds = new Map();
1731
1825
  }
1732
1826
  };
1733
1827