@gi-tcg/gts-transpiler 0.7.7 → 0.8.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/index.d.ts CHANGED
@@ -105,6 +105,12 @@ interface VolarMappingResult {
105
105
  mappings: CodeMapping[];
106
106
  }
107
107
  //#endregion
108
+ //#region src/transform/volar/index.d.ts
109
+ interface VolarTranspileOption extends TranspileOption {
110
+ /** Omit editor-only expressions, retaining all type validation. */
111
+ typeCheckingOnly: boolean;
112
+ }
113
+ //#endregion
108
114
  //#region src/transform/index.d.ts
109
115
  interface TranspileResult {
110
116
  code: string;
@@ -1534,6 +1540,6 @@ declare function isGtsShortcutArgumentStartingConciseBody(node: AST$1.Expression
1534
1540
  //#endregion
1535
1541
  //#region src/index.d.ts
1536
1542
  declare function transpile(source: string, filename: string, option: TranspileOption): TranspileResult;
1537
- declare function transpileForVolar(source: string, filename: string, option: TranspileOption): VolarMappingResult;
1543
+ declare function transpileForVolar(source: string, filename: string, option: VolarTranspileOption): VolarMappingResult;
1538
1544
  //#endregion
1539
- export { type AST, type GtsConfig, type ParseLooseOptions as GtsParseLooseOptions, type ParseOptions as GtsParseOptions, GtsTranspilerError, type PathModule, type ResolveGtsConfigAsyncOptions, type ResolveGtsConfigSyncOptions, type TranspileOption, type TranspileResult, type VolarMappingResult, isGtsShortcutArgumentStartingConciseBody, parse, parseLoose, resolveGtsConfig, resolveGtsConfigSync, transpile, transpileForVolar };
1545
+ export { type AST, type GtsConfig, type ParseLooseOptions as GtsParseLooseOptions, type ParseOptions as GtsParseOptions, GtsTranspilerError, type PathModule, type ResolveGtsConfigAsyncOptions, type ResolveGtsConfigSyncOptions, type TranspileOption, type TranspileResult, type VolarMappingResult, type VolarTranspileOption, isGtsShortcutArgumentStartingConciseBody, parse, parseLoose, resolveGtsConfig, resolveGtsConfigSync, transpile, transpileForVolar };
package/dist/index.js CHANGED
@@ -1163,16 +1163,11 @@ function applyReplacements(state, code, mappings) {
1163
1163
  const NamedDefinition = JSON.stringify(NamedDefinitionLit.value);
1164
1164
  const Meta = JSON.stringify(MetaLit.value);
1165
1165
  const matchInfos = [];
1166
+ let cumulativeOffset = 0;
1166
1167
  const result = code.replace(replacementRegex, (match, rawPayload, offset) => {
1167
1168
  const payload = JSON.parse(rawPayload.replace(/\\`/g, "`"));
1168
1169
  let replacement;
1169
- if (payload.type === "preface") replacement = dedent`
1170
- namespace ${state.utilNsId.name} {
1171
- export type UniqueKeyProbSegment = "__gts_unique_prob_seg__";
1172
- export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
1173
- }
1174
- `;
1175
- else if (payload.type === "enterVMFromRoot") replacement = dedent`
1170
+ if (payload.type === "enterVMFromRoot") replacement = dedent`
1176
1171
  type ${payload.defType} = (typeof ${payload.vm})[${NamedDefinition}];
1177
1172
  type ${payload.metaType} = ${payload.defType}[${Meta}];
1178
1173
  `;
@@ -1181,11 +1176,10 @@ function applyReplacements(state, code, mappings) {
1181
1176
  type ${payload.metaType} = ${payload.defType}[${Meta}];
1182
1177
  `;
1183
1178
  else if (payload.type === "exitVM") {
1184
- const lhs = `${payload.finalMetaType}_lhs`;
1185
1179
  const requiredAttrsNs = `${payload.finalMetaType}_rans`;
1186
- const collectedAttrsExpr = `${payload.collectedAttrs.join(" | ") || "never"}`;
1180
+ const collectedAttrsExpr = [...new Set(payload.collectedAttrs)].join(" | ") || "never";
1187
1181
  const length = payload.errorRange ? payload.errorRange[1] - payload.errorRange[0] : 0;
1188
- const needleString = `"${requiredAttrsNs}_NeedleString${"0".repeat(length)}" as string as ${requiredAttrsNs}.DiagMsg`;
1182
+ const needleString = `"${requiredAttrsNs}" as string as ${state.utilNsId.name}.RequiredMessage<${requiredAttrsNs}, ${collectedAttrsExpr}>`;
1189
1183
  if (payload.errorRange) state.extraMappings.push({
1190
1184
  sourceOffset: payload.errorRange[0],
1191
1185
  length,
@@ -1193,19 +1187,8 @@ function applyReplacements(state, code, mappings) {
1193
1187
  });
1194
1188
  replacement = dedent`
1195
1189
  type ${payload.finalMetaType} = ${payload.metaType};
1196
- let ${lhs}!: { ${Meta}: ${payload.metaType} } & Omit<${payload.defType}, ${Meta}>;
1197
- type ${lhs} = typeof ${lhs};
1198
- namespace ${requiredAttrsNs} {
1199
- export type Collected = ${collectedAttrsExpr};
1200
- export type Expected = {
1201
- [K in keyof ${payload.defType}]: ${lhs}[K] extends { required(this: ${lhs}): true } ? K : never;
1202
- }[keyof ${payload.defType}];
1203
- type DiagObj = {
1204
- [K in Expected]: K extends Collected ? never : \`'\${K}' is a required attribute but not provided\`;
1205
- }
1206
- export type DiagMsg = DiagObj[Expected];
1207
- };
1208
- ((_: ${requiredAttrsNs}.Expected extends ${requiredAttrsNs}.Collected ? string : ${requiredAttrsNs}.Expected) => 0)(${needleString});
1190
+ type ${requiredAttrsNs} = ${state.utilNsId.name}.RequiredAttrs<${state.utilNsId.name}.WithMeta<${payload.defType}, ${payload.metaType}>>;
1191
+ ${state.utilNsId.name}.checkRequired<${requiredAttrsNs}, ${collectedAttrsExpr}>(${needleString});
1209
1192
  `;
1210
1193
  } else if (payload.type === "enterAttr") {
1211
1194
  const uniqueKeyLhs = `${payload.lhs}_uniqueKey_lhs`;
@@ -1214,39 +1197,20 @@ function applyReplacements(state, code, mappings) {
1214
1197
  const uniqueKeyHelperIntf = `${payload.defType}_uniqueKeyProbeHelper`;
1215
1198
  const omittedKeys = `${payload.lhs}_omittedKeys`;
1216
1199
  replacement = dedent`
1217
- type ${uniqueKeyLhs} = {
1218
- ${Meta}: ${payload.metaType};
1219
- uniqueKey: ${payload.defType} extends { [${payload.attrName}]: { uniqueKey: infer UniqueKey } } ? UniqueKey : () => 0;
1220
- };
1221
-
1222
- let ${uniqueKeyLhs}!: ${uniqueKeyLhs};
1200
+ declare const ${uniqueKeyLhs}: { ${Meta}: ${payload.metaType}; uniqueKey: ${state.utilNsId.name}.Member<${payload.defType}, ${payload.attrName}, "uniqueKey", () => 0> };
1223
1201
  let ${uniqueKey} = ${uniqueKeyLhs}.uniqueKey();
1224
1202
  type ${uniqueKey} = typeof ${uniqueKey};
1225
1203
  let ${uniqueKeyForThis}!: \`\${${uniqueKey}}\${${state.utilNsId.name}.UniqueKeyProbSegment}${payload.lhs}\`;
1226
1204
  interface ${uniqueKeyHelperIntf} {
1227
1205
  [${uniqueKeyForThis}]: 1;
1228
1206
  }
1229
- type ${omittedKeys} = ${Meta} | (
1230
- ${uniqueKey} extends 0
1231
- ? never /* no unique requirement */
1232
- : string extends keyof ${uniqueKeyHelperIntf}
1233
- ? keyof ${payload.defType} /* too loose, disable all */
1234
- : ${state.utilNsId.name}.UnionToIntersection<
1235
- keyof ${uniqueKeyHelperIntf} & \`\${${uniqueKey}}\${${state.utilNsId.name}.UniqueKeyProbSegment}\${string}\`
1236
- > extends never
1237
- ? ${payload.attrName} /* have duplicate, disable this */
1238
- : never
1239
- );
1207
+ type ${omittedKeys} = ${Meta} | (${uniqueKey} extends 0 ? never : string extends keyof ${uniqueKeyHelperIntf} ? keyof ${payload.defType} : ${state.utilNsId.name}.UnionToIntersection<keyof ${uniqueKeyHelperIntf} & \`\${${uniqueKey}}\${${state.utilNsId.name}.UniqueKeyProbSegment}\${string}\`> extends never ? ${payload.attrName} : never);
1240
1208
  let ${payload.lhs}!: ${payload.hintOnly ? `{}` : `{ ${Meta}: ${payload.metaType} }`} & Omit<${payload.defType}, ${omittedKeys}>;
1241
1209
  `;
1242
1210
  } else if (payload.type === "createBindingTyping") {
1243
1211
  const typingIdLhs = `${payload.typingId}_lhs`;
1244
1212
  replacement = dedent`
1245
- type ${typingIdLhs} = {
1246
- ${Meta}: ${payload.finalMetaType};
1247
- as: ${payload.defType} extends { [${payload.attrName}]: { as: infer As } } ? As : unknown;
1248
- };
1249
- let ${typingIdLhs}!: ${typingIdLhs};
1213
+ declare const ${typingIdLhs}: { ${Meta}: ${payload.finalMetaType}; as: ${state.utilNsId.name}.Member<${payload.defType}, ${payload.attrName}, "as", unknown> };
1250
1214
  let ${payload.typingId} = ${typingIdLhs}.as();
1251
1215
  type ${payload.typingId} = typeof ${payload.typingId};
1252
1216
  `;
@@ -1257,24 +1221,28 @@ function applyReplacements(state, code, mappings) {
1257
1221
  replacement = dedent`
1258
1222
  type ${payload.returnType} = typeof ${payload.returnType};
1259
1223
  type ${rewrittenMeta} = ${payload.returnType} extends { rewriteMeta: infer NewMeta extends {} } ? NewMeta : ${payload.oldMetaType};
1260
- let ${mergeFn}!: ${payload.defType} extends {
1261
- [${payload.attrName}]: { mergeMeta: infer M }
1262
- } ? M : <const T>(x: T, y: unknown) => T;
1224
+ declare const ${mergeFn}: ${state.utilNsId.name}.MergeMeta<${payload.defType}, ${payload.attrName}>;
1263
1225
  let ${mergeFnRet} = ${mergeFn}(null! as ${rewrittenMeta}, null! as ${payload.innerMetaType});
1264
1226
  type ${payload.newMetaType} = [typeof ${mergeFn}] extends [null] ? ${rewrittenMeta} : typeof ${mergeFnRet};
1265
1227
  `;
1266
1228
  } else replacement = "";
1229
+ cumulativeOffset += replacement.length - match.length;
1267
1230
  matchInfos.push({
1268
1231
  sourceEnd: offset + match.length,
1269
- lengthOffset: replacement.length - match.length
1232
+ lengthOffset: cumulativeOffset
1270
1233
  });
1271
1234
  return replacement;
1272
1235
  });
1273
1236
  for (const mapping of mappings) for (let i = 0; i < mapping.generatedOffsets.length; i++) {
1274
1237
  const orig = mapping.generatedOffsets[i];
1275
- let shift = 0;
1276
- for (const info of matchInfos) if (orig >= info.sourceEnd) shift += info.lengthOffset;
1277
- mapping.generatedOffsets[i] = orig + shift;
1238
+ let low = 0;
1239
+ let high = matchInfos.length;
1240
+ while (low < high) {
1241
+ const mid = low + high >>> 1;
1242
+ if (matchInfos[mid].sourceEnd <= orig) low = mid + 1;
1243
+ else high = mid;
1244
+ }
1245
+ mapping.generatedOffsets[i] = orig + (matchInfos[low - 1]?.lengthOffset ?? 0);
1278
1246
  }
1279
1247
  return result;
1280
1248
  }
@@ -1398,6 +1366,7 @@ const exitAttr = (state, attrName, innerVMFinalMetaId, returningId) => {
1398
1366
  }));
1399
1367
  };
1400
1368
  const insertHintStatement = (state, whiteSpaceStart, whiteSpaceEnd) => {
1369
+ if (state.typeCheckingOnly) return;
1401
1370
  const { lhsId } = enterAttr(state, ATTR_HINT_ATTR_NAME);
1402
1371
  state.typingPendingStatements.push({
1403
1372
  type: "GTSAttributeNameHintStatement",
@@ -1465,21 +1434,32 @@ const gtsToTypingsWalker = {
1465
1434
  attributes: []
1466
1435
  }, {
1467
1436
  type: "ImportDeclaration",
1468
- specifiers: [{
1469
- type: "ImportSpecifier",
1470
- imported: {
1471
- type: "Identifier",
1472
- name: "createDefine"
1437
+ specifiers: [
1438
+ {
1439
+ type: "ImportSpecifier",
1440
+ imported: {
1441
+ type: "Identifier",
1442
+ name: "createDefine"
1443
+ },
1444
+ local: state.createDefineFnId
1473
1445
  },
1474
- local: state.createDefineFnId
1475
- }, {
1476
- type: "ImportSpecifier",
1477
- imported: {
1478
- type: "Identifier",
1479
- name: "createBinding"
1446
+ {
1447
+ type: "ImportSpecifier",
1448
+ imported: {
1449
+ type: "Identifier",
1450
+ name: "createBinding"
1451
+ },
1452
+ local: state.createBindingFnId
1480
1453
  },
1481
- local: state.createBindingFnId
1482
- }],
1454
+ {
1455
+ type: "ImportSpecifier",
1456
+ imported: {
1457
+ type: "Identifier",
1458
+ name: "TypingUtils"
1459
+ },
1460
+ local: state.utilNsId
1461
+ }
1462
+ ],
1483
1463
  source: {
1484
1464
  type: "Literal",
1485
1465
  value: state.runtimeImportSource
@@ -1505,7 +1485,7 @@ const gtsToTypingsWalker = {
1505
1485
  type: "Literal",
1506
1486
  value: 0
1507
1487
  }
1508
- }, lastImportDecl, createReplacementHolder(state, { type: "preface" }));
1488
+ }, lastImportDecl);
1509
1489
  return {
1510
1490
  ...node,
1511
1491
  body
@@ -1911,6 +1891,7 @@ function transformForVolar(ast, option, sourceInfo) {
1911
1891
  metaTypeIdStack: [],
1912
1892
  finalMetaTypeIdStack: [],
1913
1893
  attrsOfCurrentVm: [],
1894
+ typeCheckingOnly: option.typeCheckingOnly,
1914
1895
  sourceNodes: /* @__PURE__ */ new WeakSet(),
1915
1896
  attributeNameNodes: /* @__PURE__ */ new WeakSet(),
1916
1897
  namedAttributeCalleeLParenRange: /* @__PURE__ */ new WeakMap(),
@@ -1948,10 +1929,9 @@ function transformForVolar(ast, option, sourceInfo) {
1948
1929
  for (const extraMapping of state.extraMappings) {
1949
1930
  const genOffset = code.indexOf(extraMapping.generatedNeedle);
1950
1931
  mappings.push({
1951
- sourceOffsets: [extraMapping.sourceOffset],
1952
- lengths: [extraMapping.length],
1953
- generatedOffsets: [genOffset],
1954
- generatedLengths: [extraMapping.generatedNeedle.length],
1932
+ sourceOffsets: [extraMapping.sourceOffset, extraMapping.sourceOffset + extraMapping.length],
1933
+ lengths: [0, 0],
1934
+ generatedOffsets: [genOffset, genOffset + extraMapping.generatedNeedle.length],
1955
1935
  data: VERIFICATION_ONLY_MAPPING_DATA
1956
1936
  });
1957
1937
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gi-tcg/gts-transpiler",
3
- "version": "0.7.7",
3
+ "version": "0.8.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/piovium/gts.git"
package/src/index.ts CHANGED
@@ -10,7 +10,10 @@ import {
10
10
  type TranspileResult,
11
11
  } from "./transform/index.ts";
12
12
  import type { TranspileOption } from "./transform/gts.ts";
13
- import type { VolarMappingResult } from "./transform/volar/index.ts";
13
+ import type {
14
+ VolarMappingResult,
15
+ VolarTranspileOption,
16
+ } from "./transform/volar/index.ts";
14
17
  export { GtsTranspilerError } from "./error.ts";
15
18
  export type { AST } from "./types.ts";
16
19
 
@@ -29,7 +32,7 @@ export function transpile(
29
32
  export function transpileForVolar(
30
33
  source: string,
31
34
  filename: string,
32
- option: TranspileOption,
35
+ option: VolarTranspileOption,
33
36
  ): VolarMappingResult {
34
37
  const ast = parseLoose(source, {
35
38
  recordCallLParens: true,
@@ -47,6 +50,7 @@ export type {
47
50
  TranspileOption,
48
51
  TranspileResult,
49
52
  VolarMappingResult,
53
+ VolarTranspileOption,
50
54
  };
51
55
  export {
52
56
  resolveGtsConfig,
@@ -17,9 +17,14 @@ import {
17
17
  import { getPrintOptions } from "./printer.ts";
18
18
  import { getContentStartOffset } from "./content_start.ts";
19
19
 
20
+ export interface VolarTranspileOption extends TranspileOption {
21
+ /** Omit editor-only expressions, retaining all type validation. */
22
+ typeCheckingOnly: boolean;
23
+ }
24
+
20
25
  export function transformForVolar(
21
26
  ast: Program,
22
- option: TranspileOption,
27
+ option: VolarTranspileOption,
23
28
  sourceInfo: Required<SourceInfo>,
24
29
  ): VolarMappingResult {
25
30
  const state: TypingTranspileState = {
@@ -39,6 +44,7 @@ export function transformForVolar(
39
44
  metaTypeIdStack: [],
40
45
  finalMetaTypeIdStack: [],
41
46
  attrsOfCurrentVm: [],
47
+ typeCheckingOnly: option.typeCheckingOnly,
42
48
 
43
49
  sourceNodes: new WeakSet(),
44
50
  attributeNameNodes: new WeakSet(),
@@ -92,10 +98,15 @@ export function transformForVolar(
92
98
  for (const extraMapping of state.extraMappings) {
93
99
  const genOffset = code.indexOf(extraMapping.generatedNeedle);
94
100
  mappings.push({
95
- sourceOffsets: [extraMapping.sourceOffset],
96
- lengths: [extraMapping.length],
97
- generatedOffsets: [genOffset],
98
- generatedLengths: [extraMapping.generatedNeedle.length],
101
+ sourceOffsets: [
102
+ extraMapping.sourceOffset,
103
+ extraMapping.sourceOffset + extraMapping.length,
104
+ ],
105
+ lengths: [0, 0],
106
+ generatedOffsets: [
107
+ genOffset,
108
+ genOffset + extraMapping.generatedNeedle.length,
109
+ ],
99
110
  data: VERIFICATION_ONLY_MAPPING_DATA,
100
111
  });
101
112
  }
@@ -9,9 +9,6 @@ interface MatchInfo {
9
9
  }
10
10
 
11
11
  type ReplacementPayload =
12
- | {
13
- type: "preface";
14
- }
15
12
  | {
16
13
  type: "enterVMFromRoot";
17
14
  vm: string;
@@ -95,6 +92,7 @@ export function applyReplacements(
95
92
  const NamedDefinition = JSON.stringify(NamedDefinitionLit.value);
96
93
  const Meta = JSON.stringify(MetaLit.value);
97
94
  const matchInfos: MatchInfo[] = [];
95
+ let cumulativeOffset = 0;
98
96
 
99
97
  const result = code.replace(
100
98
  replacementRegex,
@@ -103,14 +101,7 @@ export function applyReplacements(
103
101
  rawPayload.replace(/\\`/g, "`"),
104
102
  );
105
103
  let replacement: string;
106
- if (payload.type === "preface") {
107
- replacement = dedent`
108
- namespace ${state.utilNsId.name} {
109
- export type UniqueKeyProbSegment = "__gts_unique_prob_seg__";
110
- export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
111
- }
112
- `;
113
- } else if (payload.type === "enterVMFromRoot") {
104
+ if (payload.type === "enterVMFromRoot") {
114
105
  replacement = dedent`
115
106
  type ${payload.defType} = (typeof ${payload.vm})[${NamedDefinition}];
116
107
  type ${payload.metaType} = ${payload.defType}[${Meta}];
@@ -121,14 +112,15 @@ export function applyReplacements(
121
112
  type ${payload.metaType} = ${payload.defType}[${Meta}];
122
113
  `;
123
114
  } else if (payload.type === "exitVM") {
124
- const lhs = `${payload.finalMetaType}_lhs`;
125
115
  const requiredAttrsNs = `${payload.finalMetaType}_rans`;
126
- const collectedAttrsExpr = `${payload.collectedAttrs.join(" | ") || "never"}`;
116
+ const collectedAttrsExpr =
117
+ [...new Set(payload.collectedAttrs)].join(" | ") || "never";
127
118
  const length = payload.errorRange
128
119
  ? payload.errorRange[1] - payload.errorRange[0]
129
120
  : 0;
130
- // Ensure that generated needle string is longer than error range so that error squiggle can cover all
131
- const needleString = `"${requiredAttrsNs}_NeedleString${"0".repeat(length)}" as string as ${requiredAttrsNs}.DiagMsg`;
121
+ // Map both ends explicitly; nested blocks no longer need a copy of
122
+ // their entire source length as padding in every diagnostic string.
123
+ const needleString = `"${requiredAttrsNs}" as string as ${state.utilNsId.name}.RequiredMessage<${requiredAttrsNs}, ${collectedAttrsExpr}>`;
132
124
  if (payload.errorRange) {
133
125
  state.extraMappings.push({
134
126
  sourceOffset: payload.errorRange[0],
@@ -138,19 +130,8 @@ export function applyReplacements(
138
130
  }
139
131
  replacement = dedent`
140
132
  type ${payload.finalMetaType} = ${payload.metaType};
141
- let ${lhs}!: { ${Meta}: ${payload.metaType} } & Omit<${payload.defType}, ${Meta}>;
142
- type ${lhs} = typeof ${lhs};
143
- namespace ${requiredAttrsNs} {
144
- export type Collected = ${collectedAttrsExpr};
145
- export type Expected = {
146
- [K in keyof ${payload.defType}]: ${lhs}[K] extends { required(this: ${lhs}): true } ? K : never;
147
- }[keyof ${payload.defType}];
148
- type DiagObj = {
149
- [K in Expected]: K extends Collected ? never : \`'\${K}' is a required attribute but not provided\`;
150
- }
151
- export type DiagMsg = DiagObj[Expected];
152
- };
153
- ((_: ${requiredAttrsNs}.Expected extends ${requiredAttrsNs}.Collected ? string : ${requiredAttrsNs}.Expected) => 0)(${needleString});
133
+ type ${requiredAttrsNs} = ${state.utilNsId.name}.RequiredAttrs<${state.utilNsId.name}.WithMeta<${payload.defType}, ${payload.metaType}>>;
134
+ ${state.utilNsId.name}.checkRequired<${requiredAttrsNs}, ${collectedAttrsExpr}>(${needleString});
154
135
  `;
155
136
  } else if (payload.type === "enterAttr") {
156
137
  const uniqueKeyLhs = `${payload.lhs}_uniqueKey_lhs`;
@@ -158,40 +139,25 @@ export function applyReplacements(
158
139
  const uniqueKeyForThis = `${payload.lhs}_uniqueKeyFor_${payload.lhs}`;
159
140
  const uniqueKeyHelperIntf = `${payload.defType}_uniqueKeyProbeHelper`;
160
141
  const omittedKeys = `${payload.lhs}_omittedKeys`;
142
+ // Keep Meta inside the receiver's property. Passing it to a generic
143
+ // receiver alias eagerly resolves later probes and can form a cycle.
161
144
  replacement = dedent`
162
- type ${uniqueKeyLhs} = {
163
- ${Meta}: ${payload.metaType};
164
- uniqueKey: ${payload.defType} extends { [${payload.attrName}]: { uniqueKey: infer UniqueKey } } ? UniqueKey : () => 0;
165
- };
166
-
167
- let ${uniqueKeyLhs}!: ${uniqueKeyLhs};
145
+ declare const ${uniqueKeyLhs}: { ${Meta}: ${payload.metaType}; uniqueKey: ${state.utilNsId.name}.Member<${payload.defType}, ${payload.attrName}, "uniqueKey", () => 0> };
168
146
  let ${uniqueKey} = ${uniqueKeyLhs}.uniqueKey();
169
147
  type ${uniqueKey} = typeof ${uniqueKey};
170
148
  let ${uniqueKeyForThis}!: \`\${${uniqueKey}}\${${state.utilNsId.name}.UniqueKeyProbSegment}${payload.lhs}\`;
171
149
  interface ${uniqueKeyHelperIntf} {
172
150
  [${uniqueKeyForThis}]: 1;
173
151
  }
174
- type ${omittedKeys} = ${Meta} | (
175
- ${uniqueKey} extends 0
176
- ? never /* no unique requirement */
177
- : string extends keyof ${uniqueKeyHelperIntf}
178
- ? keyof ${payload.defType} /* too loose, disable all */
179
- : ${state.utilNsId.name}.UnionToIntersection<
180
- keyof ${uniqueKeyHelperIntf} & \`\${${uniqueKey}}\${${state.utilNsId.name}.UniqueKeyProbSegment}\${string}\`
181
- > extends never
182
- ? ${payload.attrName} /* have duplicate, disable this */
183
- : never
184
- );
152
+ type ${omittedKeys} = ${Meta} | (${uniqueKey} extends 0 ? never : string extends keyof ${uniqueKeyHelperIntf} ? keyof ${payload.defType} : ${state.utilNsId.name}.UnionToIntersection<keyof ${uniqueKeyHelperIntf} & \`\${${uniqueKey}}\${${state.utilNsId.name}.UniqueKeyProbSegment}\${string}\`> extends never ? ${payload.attrName} : never);
185
153
  let ${payload.lhs}!: ${payload.hintOnly ? `{}` : `{ ${Meta}: ${payload.metaType} }`} & Omit<${payload.defType}, ${omittedKeys}>;
186
154
  `;
187
155
  } else if (payload.type === "createBindingTyping") {
188
156
  const typingIdLhs = `${payload.typingId}_lhs`;
157
+ // As with uniqueKey, an as() without a Meta-aware this parameter must
158
+ // not force final Meta (which may itself depend on this binding).
189
159
  replacement = dedent`
190
- type ${typingIdLhs} = {
191
- ${Meta}: ${payload.finalMetaType};
192
- as: ${payload.defType} extends { [${payload.attrName}]: { as: infer As } } ? As : unknown;
193
- };
194
- let ${typingIdLhs}!: ${typingIdLhs};
160
+ declare const ${typingIdLhs}: { ${Meta}: ${payload.finalMetaType}; as: ${state.utilNsId.name}.Member<${payload.defType}, ${payload.attrName}, "as", unknown> };
195
161
  let ${payload.typingId} = ${typingIdLhs}.as();
196
162
  type ${payload.typingId} = typeof ${payload.typingId};
197
163
  `;
@@ -202,33 +168,39 @@ export function applyReplacements(
202
168
  replacement = dedent`
203
169
  type ${payload.returnType} = typeof ${payload.returnType};
204
170
  type ${rewrittenMeta} = ${payload.returnType} extends { rewriteMeta: infer NewMeta extends {} } ? NewMeta : ${payload.oldMetaType};
205
- let ${mergeFn}!: ${payload.defType} extends {
206
- [${payload.attrName}]: { mergeMeta: infer M }
207
- } ? M : <const T>(x: T, y: unknown) => T;
171
+ declare const ${mergeFn}: ${state.utilNsId.name}.MergeMeta<${payload.defType}, ${payload.attrName}>;
208
172
  let ${mergeFnRet} = ${mergeFn}(null! as ${rewrittenMeta}, null! as ${payload.innerMetaType});
209
173
  type ${payload.newMetaType} = [typeof ${mergeFn}] extends [null] ? ${rewrittenMeta} : typeof ${mergeFnRet};
210
174
  `;
211
175
  } else {
212
176
  replacement = "";
213
177
  }
178
+ cumulativeOffset += replacement.length - match.length;
214
179
  matchInfos.push({
215
180
  sourceEnd: offset + match.length,
216
- lengthOffset: replacement.length - match.length,
181
+ lengthOffset: cumulativeOffset,
217
182
  });
218
183
  return replacement;
219
184
  },
220
185
  );
221
186
 
187
+ // 调整替换后 mapping 的 generatedOffset
188
+ // 由于替换信息 matchInfos 的 sourceEnd 是有序的,可以二分查找到对应的 lengthOffset
222
189
  for (const mapping of mappings) {
223
190
  for (let i = 0; i < mapping.generatedOffsets.length; i++) {
224
191
  const orig = mapping.generatedOffsets[i];
225
- let shift = 0;
226
- for (const info of matchInfos) {
227
- if (orig >= info.sourceEnd) {
228
- shift += info.lengthOffset;
192
+ let low = 0;
193
+ let high = matchInfos.length;
194
+ while (low < high) {
195
+ const mid = (low + high) >>> 1;
196
+ if (matchInfos[mid].sourceEnd <= orig) {
197
+ low = mid + 1;
198
+ } else {
199
+ high = mid;
229
200
  }
230
201
  }
231
- mapping.generatedOffsets[i] = orig + shift;
202
+ mapping.generatedOffsets[i] =
203
+ orig + (matchInfos[low - 1]?.lengthOffset ?? 0);
232
204
  }
233
205
  }
234
206
 
@@ -32,6 +32,7 @@ interface ExternalizedTypedBinding extends ExternalizedBinding {
32
32
  }
33
33
 
34
34
  export interface TypingTranspileState extends TranspileState {
35
+ typeCheckingOnly: boolean;
35
36
  externalizedBindings: ExternalizedTypedBinding[];
36
37
  idCounter: number;
37
38
  rootVmId: Identifier;
@@ -283,6 +284,10 @@ const insertHintStatement = (
283
284
  whiteSpaceStart: number,
284
285
  whiteSpaceEnd: number,
285
286
  ) => {
287
+ if (state.typeCheckingOnly) {
288
+ // type-checking do not need insert hint statement
289
+ return;
290
+ }
286
291
  const { lhsId } = enterAttr(state, ATTR_HINT_ATTR_NAME);
287
292
  state.typingPendingStatements.push({
288
293
  type: "GTSAttributeNameHintStatement",
@@ -377,6 +382,11 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
377
382
  imported: { type: "Identifier", name: "createBinding" },
378
383
  local: state.createBindingFnId,
379
384
  },
385
+ {
386
+ type: "ImportSpecifier",
387
+ imported: { type: "Identifier", name: "TypingUtils" },
388
+ local: state.utilNsId,
389
+ },
380
390
  ],
381
391
  source: { type: "Literal", value: state.runtimeImportSource },
382
392
  attributes: [],
@@ -410,9 +420,6 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
410
420
  },
411
421
  },
412
422
  lastImportDecl,
413
- createReplacementHolder(state, {
414
- type: "preface",
415
- }),
416
423
  );
417
424
  return {
418
425
  ...node,