@soda-gql/common 0.14.1 → 0.14.2
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/template-extraction.cjs +331 -3
- package/dist/template-extraction.cjs.map +1 -1
- package/dist/template-extraction.d.cts +101 -6
- package/dist/template-extraction.d.cts.map +1 -1
- package/dist/template-extraction.d.mts +101 -6
- package/dist/template-extraction.d.mts.map +1 -1
- package/dist/template-extraction.mjs +329 -4
- package/dist/template-extraction.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -30,8 +30,105 @@ const getGqlCallSchemaName = (identifiers, call) => {
|
|
|
30
30
|
return member.property.value;
|
|
31
31
|
};
|
|
32
32
|
/**
|
|
33
|
+
* Find the curried name call (e.g., `query("Name")`) from a callback builder expression.
|
|
34
|
+
* Unwraps trailing call expressions like `({})` or `({ metadata: ... })`.
|
|
35
|
+
*
|
|
36
|
+
* Returns `{ curriedCall, configCall }` where:
|
|
37
|
+
* - curriedCall: the `query("Name")` CallExpression
|
|
38
|
+
* - configCall: the `({ variables, fields })` CallExpression
|
|
39
|
+
*
|
|
40
|
+
* Returns null if the expression doesn't match the callback builder pattern.
|
|
41
|
+
*/
|
|
42
|
+
const findCallbackBuilderCalls = (expr) => {
|
|
43
|
+
if (expr.type !== "CallExpression") return null;
|
|
44
|
+
const call = expr;
|
|
45
|
+
if (call.callee.type === "CallExpression") {
|
|
46
|
+
const maybeConfigCall = call;
|
|
47
|
+
const maybeCurriedCall = call.callee;
|
|
48
|
+
if (maybeCurriedCall.callee.type === "Identifier" && isOperationKind(maybeCurriedCall.callee.value)) {
|
|
49
|
+
const configArg = maybeConfigCall.arguments[0]?.expression;
|
|
50
|
+
if (configArg?.type === "ObjectExpression") {
|
|
51
|
+
return {
|
|
52
|
+
curriedCall: maybeCurriedCall,
|
|
53
|
+
configCall: maybeConfigCall
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (maybeCurriedCall.callee.type === "CallExpression") {
|
|
58
|
+
const innerCurriedCall = maybeCurriedCall.callee;
|
|
59
|
+
if (innerCurriedCall.callee.type === "Identifier" && isOperationKind(innerCurriedCall.callee.value)) {
|
|
60
|
+
const configArg = maybeCurriedCall.arguments[0]?.expression;
|
|
61
|
+
if (configArg?.type === "ObjectExpression") {
|
|
62
|
+
return {
|
|
63
|
+
curriedCall: innerCurriedCall,
|
|
64
|
+
configCall: maybeCurriedCall
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Extract variables template from a callback builder options object.
|
|
74
|
+
* Handles patterns like:
|
|
75
|
+
* - `query("Name")({ variables: \`($id: ID!)\`, fields: ... })({})`
|
|
76
|
+
* - `query("Name")({ variables: "($id: ID!)", fields: ... })`
|
|
77
|
+
*
|
|
78
|
+
* Returns true if a callback builder pattern was detected (even if no variables property found).
|
|
79
|
+
*/
|
|
80
|
+
const extractVariablesFromCallbackBuilder = (expr, schemaName, templates, positionCtx) => {
|
|
81
|
+
const result = findCallbackBuilderCalls(expr);
|
|
82
|
+
if (!result) return false;
|
|
83
|
+
const { curriedCall, configCall } = result;
|
|
84
|
+
const nameArg = curriedCall.arguments[0]?.expression;
|
|
85
|
+
const elementName = nameArg?.type === "StringLiteral" ? nameArg.value : undefined;
|
|
86
|
+
const kind = curriedCall.callee.value;
|
|
87
|
+
const configObj = configCall.arguments[0]?.expression;
|
|
88
|
+
for (const prop of configObj.properties) {
|
|
89
|
+
if (prop.type !== "KeyValueProperty") continue;
|
|
90
|
+
if (prop.key.type !== "Identifier" || prop.key.value !== "variables") continue;
|
|
91
|
+
const value = prop.value;
|
|
92
|
+
let content;
|
|
93
|
+
let contentStart = -1;
|
|
94
|
+
let contentEnd = -1;
|
|
95
|
+
if (value.type === "TemplateLiteral") {
|
|
96
|
+
const tpl = value;
|
|
97
|
+
if (tpl.quasis.length > 0) {
|
|
98
|
+
content = tpl.quasis[0].cooked ?? tpl.quasis[0].raw;
|
|
99
|
+
if (positionCtx) {
|
|
100
|
+
contentStart = positionCtx.converter.byteOffsetToCharIndex(tpl.quasis[0].span.start - positionCtx.spanOffset);
|
|
101
|
+
contentEnd = positionCtx.converter.byteOffsetToCharIndex(tpl.quasis[0].span.end - positionCtx.spanOffset);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} else if (value.type === "StringLiteral") {
|
|
105
|
+
const strLit = value;
|
|
106
|
+
content = strLit.value;
|
|
107
|
+
if (positionCtx) {
|
|
108
|
+
contentStart = positionCtx.converter.byteOffsetToCharIndex(strLit.span.start + 1 - positionCtx.spanOffset);
|
|
109
|
+
contentEnd = positionCtx.converter.byteOffsetToCharIndex(strLit.span.end - 1 - positionCtx.spanOffset);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (content !== undefined) {
|
|
113
|
+
templates.push({
|
|
114
|
+
schemaName,
|
|
115
|
+
kind,
|
|
116
|
+
content,
|
|
117
|
+
source: "callback-variables",
|
|
118
|
+
...elementName !== undefined ? { elementName } : {},
|
|
119
|
+
...positionCtx && contentStart !== -1 && contentEnd !== -1 ? { contentRange: {
|
|
120
|
+
start: contentStart,
|
|
121
|
+
end: contentEnd
|
|
122
|
+
} } : {}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
33
130
|
* Extract templates from a gql callback's arrow function body.
|
|
34
|
-
* Handles
|
|
131
|
+
* Handles tagged templates, metadata chaining, and callback builder variables.
|
|
35
132
|
*/
|
|
36
133
|
const extractTemplatesFromCallback = (arrow, schemaName, positionCtx) => {
|
|
37
134
|
const templates = [];
|
|
@@ -45,7 +142,9 @@ const extractTemplatesFromCallback = (arrow, schemaName, positionCtx) => {
|
|
|
45
142
|
const call = expr;
|
|
46
143
|
if (call.callee.type === "TaggedTemplateExpression") {
|
|
47
144
|
extractFromTaggedTemplate(call.callee, schemaName, templates, positionCtx);
|
|
145
|
+
return;
|
|
48
146
|
}
|
|
147
|
+
extractVariablesFromCallbackBuilder(expr, schemaName, templates, positionCtx);
|
|
49
148
|
}
|
|
50
149
|
};
|
|
51
150
|
if (arrow.body.type !== "BlockStatement") {
|
|
@@ -67,6 +166,7 @@ const extractFromTaggedTemplate = (tagged, schemaName, templates, positionCtx) =
|
|
|
67
166
|
let kind;
|
|
68
167
|
let elementName;
|
|
69
168
|
let typeName;
|
|
169
|
+
let typeNameSpan;
|
|
70
170
|
if (tagged.tag.type === "Identifier") {
|
|
71
171
|
kind = tagged.tag.value;
|
|
72
172
|
} else if (tagged.tag.type === "CallExpression") {
|
|
@@ -83,6 +183,12 @@ const extractFromTaggedTemplate = (tagged, schemaName, templates, positionCtx) =
|
|
|
83
183
|
const secondArg = tagCall.arguments[1]?.expression;
|
|
84
184
|
if (secondArg?.type === "StringLiteral") {
|
|
85
185
|
typeName = secondArg.value;
|
|
186
|
+
if (positionCtx) {
|
|
187
|
+
typeNameSpan = {
|
|
188
|
+
start: positionCtx.converter.byteOffsetToCharIndex(secondArg.span.start + 1 - positionCtx.spanOffset),
|
|
189
|
+
end: positionCtx.converter.byteOffsetToCharIndex(secondArg.span.end - 1 - positionCtx.spanOffset)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
86
192
|
}
|
|
87
193
|
} else {
|
|
88
194
|
return;
|
|
@@ -131,6 +237,7 @@ const extractFromTaggedTemplate = (tagged, schemaName, templates, positionCtx) =
|
|
|
131
237
|
content,
|
|
132
238
|
...elementName !== undefined ? { elementName } : {},
|
|
133
239
|
...typeName !== undefined ? { typeName } : {},
|
|
240
|
+
...typeNameSpan !== undefined ? { typeNameSpan } : {},
|
|
134
241
|
...positionCtx && contentStart !== -1 && contentEnd !== -1 ? { contentRange: {
|
|
135
242
|
start: contentStart,
|
|
136
243
|
end: contentEnd
|
|
@@ -192,6 +299,224 @@ function walkAndExtract(node, identifiers, positionCtx) {
|
|
|
192
299
|
visit(node);
|
|
193
300
|
return templates;
|
|
194
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Walk AST to find gql callback builder calls and extract field call trees.
|
|
304
|
+
* Companion to walkAndExtract — collects ExtractedFieldTree instead of ExtractedTemplate.
|
|
305
|
+
*/
|
|
306
|
+
const walkAndExtractFieldTrees = (node, identifiers, positionCtx) => {
|
|
307
|
+
const trees = [];
|
|
308
|
+
const visit = (n) => {
|
|
309
|
+
if (!n || typeof n !== "object") return;
|
|
310
|
+
if ("type" in n && n.type === "CallExpression") {
|
|
311
|
+
const gqlCall = findGqlCall(identifiers, n);
|
|
312
|
+
if (gqlCall) {
|
|
313
|
+
const schemaName = getGqlCallSchemaName(identifiers, gqlCall);
|
|
314
|
+
if (schemaName) {
|
|
315
|
+
const arrow = gqlCall.arguments[0]?.expression;
|
|
316
|
+
const processExpr = (expr) => {
|
|
317
|
+
if (expr.type === "CallExpression") {
|
|
318
|
+
const tree = extractFieldCallTree(expr, schemaName, positionCtx);
|
|
319
|
+
if (tree) trees.push(tree);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
if (arrow.body.type !== "BlockStatement") {
|
|
323
|
+
processExpr(arrow.body);
|
|
324
|
+
} else {
|
|
325
|
+
for (const stmt of arrow.body.stmts) {
|
|
326
|
+
if (stmt.type === "ReturnStatement" && stmt.argument) {
|
|
327
|
+
processExpr(stmt.argument);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (Array.isArray(n)) {
|
|
336
|
+
for (const item of n) visit(item);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
for (const key of Object.keys(n)) {
|
|
340
|
+
if (key === "span" || key === "type") continue;
|
|
341
|
+
const value = n[key];
|
|
342
|
+
if (value && typeof value === "object") visit(value);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
visit(node);
|
|
346
|
+
return trees;
|
|
347
|
+
};
|
|
348
|
+
/**
|
|
349
|
+
* Convert a byte-space span to a character-index span using a position context.
|
|
350
|
+
* Used for tracking field name and call positions within a callback builder.
|
|
351
|
+
*/
|
|
352
|
+
const convertSpan = (span, positionCtx) => ({
|
|
353
|
+
start: positionCtx.converter.byteOffsetToCharIndex(span.start - positionCtx.spanOffset),
|
|
354
|
+
end: positionCtx.converter.byteOffsetToCharIndex(span.end - positionCtx.spanOffset)
|
|
355
|
+
});
|
|
356
|
+
/**
|
|
357
|
+
* Walk an arrow function body to extract FieldCallNode children.
|
|
358
|
+
* The arrow body should return an ObjectExpression where each property
|
|
359
|
+
* is a SpreadElement wrapping an outer CallExpression:
|
|
360
|
+
* `({ f }) => ({ ...f("fieldName")(...) })`
|
|
361
|
+
*/
|
|
362
|
+
const extractFieldCallChildren = (arrow, positionCtx) => {
|
|
363
|
+
let bodyExpr;
|
|
364
|
+
if (arrow.body.type === "BlockStatement") {
|
|
365
|
+
for (const stmt of arrow.body.stmts) {
|
|
366
|
+
if (stmt.type === "ReturnStatement" && stmt.argument) {
|
|
367
|
+
bodyExpr = stmt.argument;
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
bodyExpr = arrow.body;
|
|
373
|
+
}
|
|
374
|
+
if (bodyExpr?.type === "ParenthesisExpression") {
|
|
375
|
+
bodyExpr = bodyExpr.expression;
|
|
376
|
+
}
|
|
377
|
+
if (!bodyExpr || bodyExpr.type !== "ObjectExpression") {
|
|
378
|
+
return [];
|
|
379
|
+
}
|
|
380
|
+
const objExpr = bodyExpr;
|
|
381
|
+
const children = [];
|
|
382
|
+
for (const prop of objExpr.properties) {
|
|
383
|
+
if (prop.type !== "SpreadElement") continue;
|
|
384
|
+
const outerCall = prop.arguments;
|
|
385
|
+
if (!outerCall || outerCall.type !== "CallExpression") continue;
|
|
386
|
+
const outer = outerCall;
|
|
387
|
+
if (outer.callee.type !== "CallExpression") continue;
|
|
388
|
+
const innerCall = outer.callee;
|
|
389
|
+
if (innerCall.callee.type !== "Identifier") continue;
|
|
390
|
+
const fieldNameArg = innerCall.arguments[0]?.expression;
|
|
391
|
+
if (!fieldNameArg || fieldNameArg.type !== "StringLiteral") continue;
|
|
392
|
+
const strLit = fieldNameArg;
|
|
393
|
+
const fieldName = strLit.value;
|
|
394
|
+
const outerCallSpan = outer.span;
|
|
395
|
+
const strLitSpan = strLit.span;
|
|
396
|
+
const callSpan = positionCtx ? convertSpan(outerCallSpan, positionCtx) : {
|
|
397
|
+
start: outerCallSpan.start,
|
|
398
|
+
end: outerCallSpan.end
|
|
399
|
+
};
|
|
400
|
+
const fieldNameSpan = positionCtx ? {
|
|
401
|
+
start: positionCtx.converter.byteOffsetToCharIndex(strLitSpan.start + 1 - positionCtx.spanOffset),
|
|
402
|
+
end: positionCtx.converter.byteOffsetToCharIndex(strLitSpan.end - 1 - positionCtx.spanOffset)
|
|
403
|
+
} : {
|
|
404
|
+
start: strLitSpan.start + 1,
|
|
405
|
+
end: strLitSpan.end - 1
|
|
406
|
+
};
|
|
407
|
+
const nested = extractFieldCallNested(outer, positionCtx);
|
|
408
|
+
children.push({
|
|
409
|
+
fieldName,
|
|
410
|
+
fieldNameSpan,
|
|
411
|
+
callSpan,
|
|
412
|
+
nested
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return children;
|
|
416
|
+
};
|
|
417
|
+
/**
|
|
418
|
+
* Extract the nested structure from an outer field call.
|
|
419
|
+
* The outer call is: f("fieldName")(arg) where arg determines nesting.
|
|
420
|
+
*/
|
|
421
|
+
const extractFieldCallNested = (outer, positionCtx) => {
|
|
422
|
+
const outerArg = outer.arguments[0]?.expression;
|
|
423
|
+
if (!outerArg) return null;
|
|
424
|
+
if (outerArg.type === "ArrowFunctionExpression") {
|
|
425
|
+
const nestedArrow = outerArg;
|
|
426
|
+
const argSpan = outerArg.span;
|
|
427
|
+
const span = positionCtx ? convertSpan(argSpan, positionCtx) : {
|
|
428
|
+
start: argSpan.start,
|
|
429
|
+
end: argSpan.end
|
|
430
|
+
};
|
|
431
|
+
const children = extractFieldCallChildren(nestedArrow, positionCtx);
|
|
432
|
+
return {
|
|
433
|
+
kind: "object",
|
|
434
|
+
span,
|
|
435
|
+
children
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
if (outerArg.type === "ObjectExpression") {
|
|
439
|
+
const unionObj = outerArg;
|
|
440
|
+
const argSpan = outerArg.span;
|
|
441
|
+
const span = positionCtx ? convertSpan(argSpan, positionCtx) : {
|
|
442
|
+
start: argSpan.start,
|
|
443
|
+
end: argSpan.end
|
|
444
|
+
};
|
|
445
|
+
const branches = [];
|
|
446
|
+
for (const prop of unionObj.properties) {
|
|
447
|
+
if (prop.type !== "KeyValueProperty") continue;
|
|
448
|
+
const kvProp = prop;
|
|
449
|
+
if (kvProp.key.value === "__typename" && kvProp.value.type === "BooleanLiteral") continue;
|
|
450
|
+
if (kvProp.key.type !== "Identifier") continue;
|
|
451
|
+
if (kvProp.value.type !== "ArrowFunctionExpression") continue;
|
|
452
|
+
const typeName = kvProp.key.value;
|
|
453
|
+
const typeNameSpan = positionCtx ? convertSpan(kvProp.key.span, positionCtx) : {
|
|
454
|
+
start: kvProp.key.span.start,
|
|
455
|
+
end: kvProp.key.span.end
|
|
456
|
+
};
|
|
457
|
+
const branchSpan = positionCtx ? convertSpan(kvProp.value.span, positionCtx) : {
|
|
458
|
+
start: kvProp.value.span.start,
|
|
459
|
+
end: kvProp.value.span.end
|
|
460
|
+
};
|
|
461
|
+
const branchArrow = kvProp.value;
|
|
462
|
+
const children = extractFieldCallChildren(branchArrow, positionCtx);
|
|
463
|
+
branches.push({
|
|
464
|
+
typeName,
|
|
465
|
+
typeNameSpan,
|
|
466
|
+
branchSpan,
|
|
467
|
+
children
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
kind: "union",
|
|
472
|
+
span,
|
|
473
|
+
branches
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
return null;
|
|
477
|
+
};
|
|
478
|
+
/**
|
|
479
|
+
* Extract a field call tree from a callback builder expression.
|
|
480
|
+
*
|
|
481
|
+
* Expects a top-level expression of the callback builder pattern:
|
|
482
|
+
* `query("Name")({ fields: ({ f }) => ({ ...f("id")(), ...f("name")() }) })({})`
|
|
483
|
+
*
|
|
484
|
+
* Returns an `ExtractedFieldTree` describing the root children (fields on the root selection set),
|
|
485
|
+
* or `null` if the expression is not a callback builder or has no `fields` property.
|
|
486
|
+
*
|
|
487
|
+
* The `positionCtx` is optional — when provided, all span values will be character offsets
|
|
488
|
+
* in the original TypeScript source. When omitted, spans are raw SWC byte positions.
|
|
489
|
+
*/
|
|
490
|
+
const extractFieldCallTree = (expr, schemaName, positionCtx) => {
|
|
491
|
+
const result = findCallbackBuilderCalls(expr);
|
|
492
|
+
if (!result) return null;
|
|
493
|
+
const { curriedCall, configCall } = result;
|
|
494
|
+
const kind = curriedCall.callee.value;
|
|
495
|
+
const nameArg = curriedCall.arguments[0]?.expression;
|
|
496
|
+
const elementName = nameArg?.type === "StringLiteral" ? nameArg.value : undefined;
|
|
497
|
+
const configObj = configCall.arguments[0]?.expression;
|
|
498
|
+
for (const prop of configObj.properties) {
|
|
499
|
+
if (prop.type !== "KeyValueProperty") continue;
|
|
500
|
+
const kvProp = prop;
|
|
501
|
+
if (kvProp.key.type !== "Identifier" || kvProp.key.value !== "fields") continue;
|
|
502
|
+
if (kvProp.value.type !== "ArrowFunctionExpression") continue;
|
|
503
|
+
const fieldsArrow = kvProp.value;
|
|
504
|
+
const fieldsSpan = kvProp.value.span;
|
|
505
|
+
const rootSpan = positionCtx ? convertSpan(fieldsSpan, positionCtx) : {
|
|
506
|
+
start: fieldsSpan.start,
|
|
507
|
+
end: fieldsSpan.end
|
|
508
|
+
};
|
|
509
|
+
const children = extractFieldCallChildren(fieldsArrow, positionCtx);
|
|
510
|
+
return {
|
|
511
|
+
schemaName,
|
|
512
|
+
kind,
|
|
513
|
+
...elementName !== undefined ? { elementName } : {},
|
|
514
|
+
rootSpan,
|
|
515
|
+
children
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
return null;
|
|
519
|
+
};
|
|
195
520
|
|
|
196
521
|
//#endregion
|
|
197
522
|
//#region packages/common/src/template-extraction/format.ts
|
|
@@ -354,6 +679,7 @@ const formatTemplatesInSource = (templates, tsSource, formatGraphql) => {
|
|
|
354
679
|
const edits = [];
|
|
355
680
|
for (const template of templates) {
|
|
356
681
|
if (!template.contentRange) continue;
|
|
682
|
+
if (template.source === "callback-variables") continue;
|
|
357
683
|
const { wrapped, prefixPattern } = buildGraphqlWrapper(template);
|
|
358
684
|
let formatted;
|
|
359
685
|
try {
|
|
@@ -363,8 +689,7 @@ const formatTemplatesInSource = (templates, tsSource, formatGraphql) => {
|
|
|
363
689
|
}
|
|
364
690
|
let unwrapped = unwrapFormattedContent(formatted, prefixPattern);
|
|
365
691
|
if (template.expressionRanges && template.expressionRanges.length > 0) {
|
|
366
|
-
for (
|
|
367
|
-
const range = template.expressionRanges[i];
|
|
692
|
+
for (const [i, range] of template.expressionRanges.entries()) {
|
|
368
693
|
const exprText = tsSource.slice(range.start, range.end);
|
|
369
694
|
unwrapped = unwrapped.replace(`__FRAG_SPREAD_${i}__`, `\${${exprText}}`);
|
|
370
695
|
}
|
|
@@ -391,8 +716,10 @@ exports.OPERATION_KINDS = OPERATION_KINDS;
|
|
|
391
716
|
exports.buildGraphqlWrapper = buildGraphqlWrapper;
|
|
392
717
|
exports.detectBaseIndent = detectBaseIndent;
|
|
393
718
|
exports.detectIndentUnit = detectIndentUnit;
|
|
719
|
+
exports.extractFieldCallTree = extractFieldCallTree;
|
|
394
720
|
exports.extractFromTaggedTemplate = extractFromTaggedTemplate;
|
|
395
721
|
exports.extractTemplatesFromCallback = extractTemplatesFromCallback;
|
|
722
|
+
exports.extractVariablesFromCallbackBuilder = extractVariablesFromCallbackBuilder;
|
|
396
723
|
exports.findGqlCall = findGqlCall;
|
|
397
724
|
exports.formatTemplatesInSource = formatTemplatesInSource;
|
|
398
725
|
exports.getGqlCallSchemaName = getGqlCallSchemaName;
|
|
@@ -400,4 +727,5 @@ exports.isOperationKind = isOperationKind;
|
|
|
400
727
|
exports.reindent = reindent;
|
|
401
728
|
exports.unwrapFormattedContent = unwrapFormattedContent;
|
|
402
729
|
exports.walkAndExtract = walkAndExtract;
|
|
730
|
+
exports.walkAndExtractFieldTrees = walkAndExtractFieldTrees;
|
|
403
731
|
//# sourceMappingURL=template-extraction.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template-extraction.cjs","names":["templates: ExtractedTemplate[]","kind: string","elementName: string | undefined","typeName: string | undefined","expressionRanges: { start: number; end: number }[]","parts: string[]","template: ExtractedTemplate","prefix","prefixPattern","edits: TemplateFormatEdit[]","formatted: string"],"sources":["../src/template-extraction/extract.ts","../src/template-extraction/format.ts"],"sourcesContent":["/**\n * Template extraction from TypeScript source using SWC AST.\n *\n * Based on the typegen extractor (superset): supports both bare-tag\n * (`query\\`...\\``) and curried (`query(\"Name\")\\`...\\``) syntax.\n *\n * Position tracking is optional — pass spanOffset + converter to\n * populate contentRange on extracted templates.\n *\n * @module\n */\n\n// Re-export for convenience — SWC types are type-only imports\nimport type { ArrowFunctionExpression, CallExpression, MemberExpression, Node, TaggedTemplateExpression } from \"@swc/types\";\nimport type { SwcSpanConverter } from \"../utils/swc-span\";\nimport type { ExtractedTemplate, ExtractedTemplateWithPosition, OperationKind } from \"./types\";\n\nexport const OPERATION_KINDS = new Set<string>([\"query\", \"mutation\", \"subscription\", \"fragment\"]);\n\nexport const isOperationKind = (value: string): value is OperationKind => OPERATION_KINDS.has(value);\n\n/** Optional position tracking context for extraction. */\nexport type PositionTrackingContext = {\n readonly spanOffset: number;\n readonly converter: SwcSpanConverter;\n};\n\n/**\n * Check if a call expression is a gql.{schemaName}(...) call.\n * Returns the schema name if it is, null otherwise.\n */\nexport const getGqlCallSchemaName = (identifiers: ReadonlySet<string>, call: CallExpression): string | null => {\n const callee = call.callee;\n if (callee.type !== \"MemberExpression\") {\n return null;\n }\n\n const member = callee as MemberExpression;\n if (member.object.type !== \"Identifier\" || !identifiers.has(member.object.value)) {\n return null;\n }\n\n if (member.property.type !== \"Identifier\") {\n return null;\n }\n\n const firstArg = call.arguments[0];\n if (!firstArg?.expression || firstArg.expression.type !== \"ArrowFunctionExpression\") {\n return null;\n }\n\n return member.property.value;\n};\n\n/**\n * Extract templates from a gql callback's arrow function body.\n * Handles both expression bodies and block bodies with return statements.\n */\nexport const extractTemplatesFromCallback = (\n arrow: ArrowFunctionExpression,\n schemaName: string,\n positionCtx?: PositionTrackingContext,\n): ExtractedTemplate[] => {\n const templates: ExtractedTemplate[] = [];\n\n const processExpression = (expr: Node): void => {\n // Direct tagged template: query(\"Name\")`...`\n if (expr.type === \"TaggedTemplateExpression\") {\n const tagged = expr as unknown as TaggedTemplateExpression;\n extractFromTaggedTemplate(tagged, schemaName, templates, positionCtx);\n return;\n }\n\n // Metadata chaining: query(\"Name\")`...`({ metadata: {} })\n if (expr.type === \"CallExpression\") {\n const call = expr as unknown as CallExpression;\n if (call.callee.type === \"TaggedTemplateExpression\") {\n extractFromTaggedTemplate(call.callee as TaggedTemplateExpression, schemaName, templates, positionCtx);\n }\n }\n };\n\n // Expression body: ({ query }) => query(\"Name\")`...`\n if (arrow.body.type !== \"BlockStatement\") {\n processExpression(arrow.body);\n return templates;\n }\n\n // Block body: ({ query }) => { return query(\"Name\")`...`; }\n for (const stmt of arrow.body.stmts) {\n if (stmt.type === \"ReturnStatement\" && stmt.argument) {\n processExpression(stmt.argument);\n }\n }\n\n return templates;\n};\n\n/**\n * Extract a single template from a tagged template expression.\n * Supports both bare-tag (Identifier) and curried (CallExpression) tag forms.\n */\nexport const extractFromTaggedTemplate = (\n tagged: TaggedTemplateExpression,\n schemaName: string,\n templates: ExtractedTemplate[],\n positionCtx?: PositionTrackingContext,\n): void => {\n // Tag can be:\n // - CallExpression: query(\"name\")`...` or fragment(\"name\", \"type\")`...` (curried syntax)\n // - Identifier: legacy bare-tag form (skipped if it contains interpolations)\n let kind: string;\n let elementName: string | undefined;\n let typeName: string | undefined;\n\n if (tagged.tag.type === \"Identifier\") {\n kind = tagged.tag.value;\n } else if (tagged.tag.type === \"CallExpression\") {\n const tagCall = tagged.tag as CallExpression;\n if (tagCall.callee.type === \"Identifier\") {\n kind = tagCall.callee.value;\n } else {\n return;\n }\n // Extract elementName and typeName from call arguments\n const firstArg = tagCall.arguments[0]?.expression;\n if (firstArg?.type === \"StringLiteral\") {\n elementName = (firstArg as { value: string }).value;\n }\n const secondArg = tagCall.arguments[1]?.expression;\n if (secondArg?.type === \"StringLiteral\") {\n typeName = (secondArg as { value: string }).value;\n }\n } else {\n return;\n }\n\n if (!isOperationKind(kind)) {\n return;\n }\n\n const { quasis, expressions } = tagged.template;\n\n // For legacy Identifier tag, skip templates with interpolations\n if (tagged.tag.type === \"Identifier\" && expressions.length > 0) {\n return;\n }\n\n if (quasis.length === 0) {\n return;\n }\n\n // Build content and optionally track position\n let contentStart = -1;\n let contentEnd = -1;\n const expressionRanges: { start: number; end: number }[] = [];\n\n const parts: string[] = [];\n for (let i = 0; i < quasis.length; i++) {\n const quasi = quasis[i];\n if (!quasi) continue;\n\n if (positionCtx) {\n const quasiStart = positionCtx.converter.byteOffsetToCharIndex(quasi.span.start - positionCtx.spanOffset);\n const quasiEnd = positionCtx.converter.byteOffsetToCharIndex(quasi.span.end - positionCtx.spanOffset);\n if (contentStart === -1) contentStart = quasiStart;\n contentEnd = quasiEnd;\n }\n\n parts.push(quasi.cooked ?? quasi.raw);\n if (i < expressions.length) {\n parts.push(`__FRAG_SPREAD_${i}__`);\n if (positionCtx) {\n // All SWC AST nodes have span; cast needed because Expression union type doesn't expose it uniformly\n const expr = expressions[i] as unknown as { span: { start: number; end: number } };\n const exprStart = positionCtx.converter.byteOffsetToCharIndex(expr.span.start - positionCtx.spanOffset);\n const exprEnd = positionCtx.converter.byteOffsetToCharIndex(expr.span.end - positionCtx.spanOffset);\n expressionRanges.push({ start: exprStart, end: exprEnd });\n }\n }\n }\n const content = parts.join(\"\");\n\n const template: ExtractedTemplate = {\n schemaName,\n kind,\n content,\n ...(elementName !== undefined ? { elementName } : {}),\n ...(typeName !== undefined ? { typeName } : {}),\n ...(positionCtx && contentStart !== -1 && contentEnd !== -1\n ? { contentRange: { start: contentStart, end: contentEnd } }\n : {}),\n ...(expressionRanges.length > 0 ? { expressionRanges } : {}),\n };\n\n templates.push(template);\n};\n\n/**\n * Find the innermost gql call, unwrapping method chains like .attach().\n */\nexport const findGqlCall = (identifiers: ReadonlySet<string>, node: Node): CallExpression | null => {\n if (!node || node.type !== \"CallExpression\") {\n return null;\n }\n\n const call = node as unknown as CallExpression;\n if (getGqlCallSchemaName(identifiers, call) !== null) {\n return call;\n }\n\n const callee = call.callee;\n if (callee.type !== \"MemberExpression\") {\n return null;\n }\n\n return findGqlCall(identifiers, callee.object as unknown as Node);\n};\n\n/**\n * Walk AST to find gql calls and extract templates.\n */\nexport function walkAndExtract(\n node: Node,\n identifiers: ReadonlySet<string>,\n positionCtx: PositionTrackingContext,\n): ExtractedTemplateWithPosition[];\nexport function walkAndExtract(\n node: Node,\n identifiers: ReadonlySet<string>,\n positionCtx?: PositionTrackingContext,\n): ExtractedTemplate[];\nexport function walkAndExtract(\n node: Node,\n identifiers: ReadonlySet<string>,\n positionCtx?: PositionTrackingContext,\n): ExtractedTemplate[] {\n const templates: ExtractedTemplate[] = [];\n\n const visit = (n: Node | ReadonlyArray<Node> | Record<string, unknown>): void => {\n if (!n || typeof n !== \"object\") {\n return;\n }\n\n if (\"type\" in n && n.type === \"CallExpression\") {\n const gqlCall = findGqlCall(identifiers, n as Node);\n if (gqlCall) {\n const schemaName = getGqlCallSchemaName(identifiers, gqlCall);\n if (schemaName) {\n const arrow = gqlCall.arguments[0]?.expression as ArrowFunctionExpression;\n templates.push(...extractTemplatesFromCallback(arrow, schemaName, positionCtx));\n }\n return; // Don't recurse into gql calls\n }\n }\n\n // Recurse into all array and object properties\n if (Array.isArray(n)) {\n for (const item of n) {\n visit(item as Node);\n }\n return;\n }\n\n for (const key of Object.keys(n)) {\n if (key === \"span\" || key === \"type\") {\n continue;\n }\n const value = (n as Record<string, unknown>)[key];\n if (value && typeof value === \"object\") {\n visit(value as Node);\n }\n }\n };\n\n visit(node);\n return templates;\n}\n","/**\n * GraphQL template formatting utilities.\n *\n * Pure string operations — no dependency on `graphql` package.\n * Consumers provide their own format function (e.g., graphql-js parse/print).\n *\n * @module\n */\n\nimport type { ExtractedTemplate, TemplateFormatEdit } from \"./types\";\n\n/** A function that formats GraphQL source text. */\nexport type FormatGraphqlFn = (source: string) => string;\n\n/**\n * Detect the base indentation for a template by looking at the line\n * containing the opening backtick.\n */\nexport const detectBaseIndent = (tsSource: string, contentStartOffset: number): string => {\n // Find the start of the line containing contentStartOffset\n let lineStart = contentStartOffset;\n while (lineStart > 0 && tsSource.charCodeAt(lineStart - 1) !== 10) {\n lineStart--;\n }\n\n // Extract leading whitespace from that line\n let i = lineStart;\n while (i < tsSource.length && (tsSource.charCodeAt(i) === 32 || tsSource.charCodeAt(i) === 9)) {\n i++;\n }\n\n return tsSource.slice(lineStart, i);\n};\n\n/**\n * Convert graphql-js 2-space indentation to the target indent unit.\n * Strips leading 2-space groups and replaces with equivalent indent units.\n */\nconst convertGraphqlIndent = (line: string, indentUnit: string): string => {\n if (indentUnit === \" \") return line; // No conversion needed\n let level = 0;\n let pos = 0;\n while (pos + 1 < line.length && line[pos] === \" \" && line[pos + 1] === \" \") {\n level++;\n pos += 2;\n }\n if (level === 0) return line;\n return indentUnit.repeat(level) + line.slice(pos);\n};\n\n/**\n * Re-indent formatted GraphQL to match the embedding context.\n *\n * **Inline templates** (content does NOT start with `\\n`):\n * - Line 0 (`{`): no prefix — appears right after backtick\n * - Lines 1..N: graphql-js indentation only (converted to target unit)\n *\n * **Block templates** (content starts with `\\n`):\n * - All lines: `baseIndent + indentUnit` prefix + converted graphql indent\n * - Trailing: `\\n` + `baseIndent`\n */\nexport const reindent = (formatted: string, baseIndent: string, originalContent: string, indentUnit: string = \" \"): string => {\n const trimmedFormatted = formatted.trim();\n\n // If original was single-line and formatted is also single-line, keep it\n if (!originalContent.includes(\"\\n\") && !trimmedFormatted.includes(\"\\n\")) {\n return trimmedFormatted;\n }\n\n const lines = trimmedFormatted.split(\"\\n\");\n\n // Match original leading/trailing newline pattern\n const startsWithNewline = originalContent.startsWith(\"\\n\");\n const endsWithNewline = /\\n\\s*$/.test(originalContent);\n\n const indentedLines = lines.map((line, i) => {\n if (line.trim() === \"\") return \"\";\n const converted = convertGraphqlIndent(line, indentUnit);\n\n if (!startsWithNewline) {\n // Inline template: first line has no prefix (appears after backtick);\n // body lines get baseIndent + converted graphql indent to align with TS context.\n if (i === 0) return converted;\n return baseIndent + converted;\n }\n\n // Block template: every line gets baseIndent + indentUnit prefix\n return `${baseIndent}${indentUnit}${converted}`;\n });\n\n let result = indentedLines.join(\"\\n\");\n if (startsWithNewline) {\n result = `\\n${result}`;\n }\n if (endsWithNewline) {\n result = `${result}\\n${baseIndent}`;\n }\n\n return result;\n};\n\n/**\n * Detect the indentation unit used in a TypeScript source file.\n *\n * Uses the smallest indentation width found as the indent unit.\n * In files with embedded templates (e.g., graphql in backticks),\n * template content lines are at `baseIndent + graphqlIndent`,\n * so their absolute indent is always >= the file's indent unit.\n */\nexport const detectIndentUnit = (tsSource: string): string => {\n let minSpaceIndent = Infinity;\n for (const line of tsSource.split(\"\\n\")) {\n if (line.length === 0 || line.trimStart().length === 0) continue;\n if (line[0] === \"\\t\") return \"\\t\";\n const match = line.match(/^( +)\\S/);\n if (match) minSpaceIndent = Math.min(minSpaceIndent, match[1]!.length);\n }\n if (minSpaceIndent === Infinity || minSpaceIndent <= 1) return \" \";\n return \" \".repeat(minSpaceIndent);\n};\n\nconst GRAPHQL_KEYWORDS = new Set([\"query\", \"mutation\", \"subscription\", \"fragment\"]);\n\n/**\n * Reconstruct a full GraphQL document from template content + metadata.\n *\n * For curried syntax, the template `content` is only the body (e.g., `{ user { id } }`).\n * GraphQL parsers need a full document (e.g., `query GetUser { user { id } }`).\n *\n * For bare-tag syntax, the content already starts with a keyword and is a full document.\n *\n * @returns The wrapped source and a regex pattern to strip the synthetic prefix after formatting\n */\nexport const buildGraphqlWrapper = (template: ExtractedTemplate): { wrapped: string; prefixPattern: RegExp | null } => {\n const content = template.content.trimStart();\n const firstWord = content.split(/[\\s({]/)[0] ?? \"\";\n\n // If content already starts with a GraphQL keyword, it's a full document (bare-tag)\n if (GRAPHQL_KEYWORDS.has(firstWord)) {\n return { wrapped: template.content, prefixPattern: null };\n }\n\n // Curried syntax — reconstruct the header\n if (template.elementName) {\n if (template.kind === \"fragment\" && template.typeName) {\n const prefix = `fragment ${template.elementName} on ${template.typeName} `;\n const prefixPattern = new RegExp(`^fragment\\\\s+${template.elementName}\\\\s+on\\\\s+${template.typeName}\\\\s*`);\n return { wrapped: prefix + template.content, prefixPattern };\n }\n const prefix = `${template.kind} ${template.elementName} `;\n const prefixPattern = new RegExp(`^${template.kind}\\\\s+${template.elementName}\\\\s*`);\n return { wrapped: prefix + template.content, prefixPattern };\n }\n\n // No elementName — try wrapping with just the kind\n const prefix = `${template.kind} `;\n const prefixPattern = new RegExp(`^${template.kind}\\\\s*`);\n return { wrapped: prefix + template.content, prefixPattern };\n};\n\n/**\n * Strip the reconstructed prefix from formatted output to get back the template body.\n * Uses regex pattern matching to handle whitespace normalization by the formatter\n * (e.g., `query Foo ($id: ID!)` → `query Foo($id: ID!)`).\n */\nexport const unwrapFormattedContent = (formatted: string, prefixPattern: RegExp | null): string => {\n if (!prefixPattern) return formatted;\n const match = formatted.match(prefixPattern);\n if (!match) return formatted;\n return formatted.slice(match[0].length);\n};\n\n/**\n * Format GraphQL templates within TypeScript source.\n *\n * Applies the given format function to each template's content, handles\n * wrapper reconstruction for curried syntax, and re-indents the result\n * to match the TypeScript embedding context.\n *\n * @returns Array of edits to apply to the source (sorted by position, not yet applied)\n */\nexport const formatTemplatesInSource = (\n templates: readonly ExtractedTemplate[],\n tsSource: string,\n formatGraphql: FormatGraphqlFn,\n): readonly TemplateFormatEdit[] => {\n const indentUnit = detectIndentUnit(tsSource);\n const edits: TemplateFormatEdit[] = [];\n\n for (const template of templates) {\n if (!template.contentRange) continue;\n\n // Wrap the content for formatting\n const { wrapped, prefixPattern } = buildGraphqlWrapper(template);\n\n let formatted: string;\n try {\n formatted = formatGraphql(wrapped);\n } catch {\n continue;\n }\n\n // Unwrap the prefix\n let unwrapped = unwrapFormattedContent(formatted, prefixPattern);\n\n // Restore interpolation expressions: replace __FRAG_SPREAD_N__ with original ${...} syntax\n if (template.expressionRanges && template.expressionRanges.length > 0) {\n for (let i = 0; i < template.expressionRanges.length; i++) {\n const range = template.expressionRanges[i]!;\n const exprText = tsSource.slice(range.start, range.end);\n unwrapped = unwrapped.replace(`__FRAG_SPREAD_${i}__`, `\\${${exprText}}`);\n }\n }\n\n // Fast path: skip if formatter produces identical output\n if (unwrapped === template.content) {\n continue;\n }\n\n // Detect base indentation from the TS source\n const baseIndent = detectBaseIndent(tsSource, template.contentRange.start);\n\n // Re-indent the formatted output\n const reindented = reindent(unwrapped, baseIndent, template.content, indentUnit);\n\n // Skip if no changes after re-indentation\n if (reindented === template.content) {\n continue;\n }\n\n edits.push({\n start: template.contentRange.start,\n end: template.contentRange.end,\n newText: reindented,\n });\n }\n\n return edits;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"template-extraction.cjs","names":["content: string | undefined","templates: ExtractedTemplate[]","kind: string","elementName: string | undefined","typeName: string | undefined","typeNameSpan: { start: number; end: number } | undefined","expressionRanges: { start: number; end: number }[]","parts: string[]","template: ExtractedTemplate","trees: ExtractedFieldTree[]","bodyExpr: { type: string } | undefined","children: FieldCallNode[]","branches: UnionBranchNode[]","prefix","prefixPattern","edits: TemplateFormatEdit[]","formatted: string"],"sources":["../src/template-extraction/extract.ts","../src/template-extraction/format.ts"],"sourcesContent":["/**\n * Template extraction from TypeScript source using SWC AST.\n *\n * Based on the typegen extractor (superset): supports both bare-tag\n * (`query\\`...\\``) and curried (`query(\"Name\")\\`...\\``) syntax.\n *\n * Position tracking is optional — pass spanOffset + converter to\n * populate contentRange on extracted templates.\n *\n * @module\n */\n\n// Re-export for convenience — SWC types are type-only imports\nimport type {\n ArrowFunctionExpression,\n CallExpression,\n MemberExpression,\n Node,\n ObjectExpression,\n TaggedTemplateExpression,\n} from \"@swc/types\";\nimport type { SwcSpanConverter } from \"../utils/swc-span\";\nimport type {\n ExtractedFieldTree,\n ExtractedTemplate,\n ExtractedTemplateWithPosition,\n FieldCallNested,\n FieldCallNode,\n OperationKind,\n UnionBranchNode,\n} from \"./types\";\n\nexport const OPERATION_KINDS = new Set<string>([\"query\", \"mutation\", \"subscription\", \"fragment\"]);\n\nexport const isOperationKind = (value: string): value is OperationKind => OPERATION_KINDS.has(value);\n\n/** Optional position tracking context for extraction. */\nexport type PositionTrackingContext = {\n readonly spanOffset: number;\n readonly converter: SwcSpanConverter;\n};\n\n/**\n * Check if a call expression is a gql.{schemaName}(...) call.\n * Returns the schema name if it is, null otherwise.\n */\nexport const getGqlCallSchemaName = (identifiers: ReadonlySet<string>, call: CallExpression): string | null => {\n const callee = call.callee;\n if (callee.type !== \"MemberExpression\") {\n return null;\n }\n\n const member = callee as MemberExpression;\n if (member.object.type !== \"Identifier\" || !identifiers.has(member.object.value)) {\n return null;\n }\n\n if (member.property.type !== \"Identifier\") {\n return null;\n }\n\n const firstArg = call.arguments[0];\n if (!firstArg?.expression || firstArg.expression.type !== \"ArrowFunctionExpression\") {\n return null;\n }\n\n return member.property.value;\n};\n\n/**\n * Find the curried name call (e.g., `query(\"Name\")`) from a callback builder expression.\n * Unwraps trailing call expressions like `({})` or `({ metadata: ... })`.\n *\n * Returns `{ curriedCall, configCall }` where:\n * - curriedCall: the `query(\"Name\")` CallExpression\n * - configCall: the `({ variables, fields })` CallExpression\n *\n * Returns null if the expression doesn't match the callback builder pattern.\n */\nconst findCallbackBuilderCalls = (expr: Node): { curriedCall: CallExpression; configCall: CallExpression } | null => {\n if (expr.type !== \"CallExpression\") return null;\n const call = expr as unknown as CallExpression;\n\n // Unwrap trailing call: query(\"Name\")({ ... })({}) → get to query(\"Name\")({ ... })\n // The trailing call's callee is the config call, whose callee is the curried name call.\n // We need to find the config call that has an ObjectExpression argument\n // and whose callee is a curried name call (CallExpression with Identifier callee).\n\n // Try current level as config call first (no trailing call case)\n if (call.callee.type === \"CallExpression\") {\n const maybeConfigCall = call;\n const maybeCurriedCall = call.callee as CallExpression;\n\n // Check if maybeCurriedCall is a curried name call: query(\"Name\")\n if (maybeCurriedCall.callee.type === \"Identifier\" && isOperationKind(maybeCurriedCall.callee.value)) {\n // This is: curriedCall({ ... }) — no trailing call, expr IS the config call\n const configArg = maybeConfigCall.arguments[0]?.expression;\n if (configArg?.type === \"ObjectExpression\") {\n return { curriedCall: maybeCurriedCall, configCall: maybeConfigCall };\n }\n }\n\n // Check if maybeCurriedCall is the config call (trailing call case)\n // expr is: trailing({}) where trailing.callee = configCall\n if (maybeCurriedCall.callee.type === \"CallExpression\") {\n const innerCurriedCall = maybeCurriedCall.callee as CallExpression;\n if (innerCurriedCall.callee.type === \"Identifier\" && isOperationKind(innerCurriedCall.callee.value)) {\n const configArg = maybeCurriedCall.arguments[0]?.expression;\n if (configArg?.type === \"ObjectExpression\") {\n return { curriedCall: innerCurriedCall, configCall: maybeCurriedCall };\n }\n }\n }\n }\n\n return null;\n};\n\n/**\n * Extract variables template from a callback builder options object.\n * Handles patterns like:\n * - `query(\"Name\")({ variables: \\`($id: ID!)\\`, fields: ... })({})`\n * - `query(\"Name\")({ variables: \"($id: ID!)\", fields: ... })`\n *\n * Returns true if a callback builder pattern was detected (even if no variables property found).\n */\nexport const extractVariablesFromCallbackBuilder = (\n expr: Node,\n schemaName: string,\n templates: ExtractedTemplate[],\n positionCtx?: PositionTrackingContext,\n): boolean => {\n const result = findCallbackBuilderCalls(expr);\n if (!result) return false;\n\n const { curriedCall, configCall } = result;\n\n // Extract elementName from curried call\n const nameArg = curriedCall.arguments[0]?.expression;\n const elementName = nameArg?.type === \"StringLiteral\" ? (nameArg as { value: string }).value : undefined;\n\n // Extract kind from curried call\n const kind = (curriedCall.callee as { value: string }).value;\n\n // Find variables property in config object\n const configObj = configCall.arguments[0]?.expression as ObjectExpression;\n for (const prop of configObj.properties) {\n if (prop.type !== \"KeyValueProperty\") continue;\n if (prop.key.type !== \"Identifier\" || (prop.key as { value: string }).value !== \"variables\") continue;\n\n const value = prop.value;\n let content: string | undefined;\n let contentStart = -1;\n let contentEnd = -1;\n\n if (value.type === \"TemplateLiteral\") {\n // Template literal: `($id: ID!)`\n const tpl = value as unknown as { quasis: { raw: string; cooked?: string; span: { start: number; end: number } }[] };\n if (tpl.quasis.length > 0) {\n content = tpl.quasis[0]!.cooked ?? tpl.quasis[0]!.raw;\n if (positionCtx) {\n contentStart = positionCtx.converter.byteOffsetToCharIndex(tpl.quasis[0]!.span.start - positionCtx.spanOffset);\n contentEnd = positionCtx.converter.byteOffsetToCharIndex(tpl.quasis[0]!.span.end - positionCtx.spanOffset);\n }\n }\n } else if (value.type === \"StringLiteral\") {\n // StringLiteral span is in byte space and includes quote delimiters.\n // Adjust in byte space before byteOffsetToCharIndex conversion.\n // Safe because quote characters (\", ') are ASCII (1 byte in UTF-8).\n const strLit = value as unknown as { value: string; span: { start: number; end: number } };\n content = strLit.value;\n if (positionCtx) {\n contentStart = positionCtx.converter.byteOffsetToCharIndex(strLit.span.start + 1 - positionCtx.spanOffset);\n contentEnd = positionCtx.converter.byteOffsetToCharIndex(strLit.span.end - 1 - positionCtx.spanOffset);\n }\n }\n\n if (content !== undefined) {\n templates.push({\n schemaName,\n kind: kind as OperationKind,\n content,\n source: \"callback-variables\",\n ...(elementName !== undefined ? { elementName } : {}),\n ...(positionCtx && contentStart !== -1 && contentEnd !== -1\n ? { contentRange: { start: contentStart, end: contentEnd } }\n : {}),\n });\n }\n\n break;\n }\n\n return true;\n};\n\n/**\n * Extract templates from a gql callback's arrow function body.\n * Handles tagged templates, metadata chaining, and callback builder variables.\n */\nexport const extractTemplatesFromCallback = (\n arrow: ArrowFunctionExpression,\n schemaName: string,\n positionCtx?: PositionTrackingContext,\n): ExtractedTemplate[] => {\n const templates: ExtractedTemplate[] = [];\n\n const processExpression = (expr: Node): void => {\n // Direct tagged template: query(\"Name\")`...`\n if (expr.type === \"TaggedTemplateExpression\") {\n const tagged = expr as unknown as TaggedTemplateExpression;\n extractFromTaggedTemplate(tagged, schemaName, templates, positionCtx);\n return;\n }\n\n // CallExpression paths: metadata chaining or callback builder\n if (expr.type === \"CallExpression\") {\n const call = expr as unknown as CallExpression;\n\n // Metadata chaining: query(\"Name\")`...`({ metadata: {} })\n if (call.callee.type === \"TaggedTemplateExpression\") {\n extractFromTaggedTemplate(call.callee as TaggedTemplateExpression, schemaName, templates, positionCtx);\n return;\n }\n\n // Callback builder: query(\"Name\")({ variables: `...`, fields: ... })({})\n extractVariablesFromCallbackBuilder(expr, schemaName, templates, positionCtx);\n }\n };\n\n // Expression body: ({ query }) => query(\"Name\")`...`\n if (arrow.body.type !== \"BlockStatement\") {\n processExpression(arrow.body);\n return templates;\n }\n\n // Block body: ({ query }) => { return query(\"Name\")`...`; }\n for (const stmt of arrow.body.stmts) {\n if (stmt.type === \"ReturnStatement\" && stmt.argument) {\n processExpression(stmt.argument);\n }\n }\n\n return templates;\n};\n\n/**\n * Extract a single template from a tagged template expression.\n * Supports both bare-tag (Identifier) and curried (CallExpression) tag forms.\n */\nexport const extractFromTaggedTemplate = (\n tagged: TaggedTemplateExpression,\n schemaName: string,\n templates: ExtractedTemplate[],\n positionCtx?: PositionTrackingContext,\n): void => {\n // Tag can be:\n // - CallExpression: query(\"name\")`...` or fragment(\"name\", \"type\")`...` (curried syntax)\n // - Identifier: legacy bare-tag form (skipped if it contains interpolations)\n let kind: string;\n let elementName: string | undefined;\n let typeName: string | undefined;\n let typeNameSpan: { start: number; end: number } | undefined;\n\n if (tagged.tag.type === \"Identifier\") {\n kind = tagged.tag.value;\n } else if (tagged.tag.type === \"CallExpression\") {\n const tagCall = tagged.tag as CallExpression;\n if (tagCall.callee.type === \"Identifier\") {\n kind = tagCall.callee.value;\n } else {\n return;\n }\n // Extract elementName and typeName from call arguments\n const firstArg = tagCall.arguments[0]?.expression;\n if (firstArg?.type === \"StringLiteral\") {\n elementName = (firstArg as { value: string }).value;\n }\n const secondArg = tagCall.arguments[1]?.expression;\n if (secondArg?.type === \"StringLiteral\") {\n typeName = (secondArg as { value: string }).value;\n if (positionCtx) {\n typeNameSpan = {\n start: positionCtx.converter.byteOffsetToCharIndex(secondArg.span.start + 1 - positionCtx.spanOffset),\n end: positionCtx.converter.byteOffsetToCharIndex(secondArg.span.end - 1 - positionCtx.spanOffset),\n };\n }\n }\n } else {\n return;\n }\n\n if (!isOperationKind(kind)) {\n return;\n }\n\n const { quasis, expressions } = tagged.template;\n\n // For legacy Identifier tag, skip templates with interpolations\n if (tagged.tag.type === \"Identifier\" && expressions.length > 0) {\n return;\n }\n\n if (quasis.length === 0) {\n return;\n }\n\n // Build content and optionally track position\n let contentStart = -1;\n let contentEnd = -1;\n const expressionRanges: { start: number; end: number }[] = [];\n\n const parts: string[] = [];\n for (let i = 0; i < quasis.length; i++) {\n const quasi = quasis[i];\n if (!quasi) continue;\n\n if (positionCtx) {\n const quasiStart = positionCtx.converter.byteOffsetToCharIndex(quasi.span.start - positionCtx.spanOffset);\n const quasiEnd = positionCtx.converter.byteOffsetToCharIndex(quasi.span.end - positionCtx.spanOffset);\n if (contentStart === -1) contentStart = quasiStart;\n contentEnd = quasiEnd;\n }\n\n parts.push(quasi.cooked ?? quasi.raw);\n if (i < expressions.length) {\n parts.push(`__FRAG_SPREAD_${i}__`);\n if (positionCtx) {\n // All SWC AST nodes have span; cast needed because Expression union type doesn't expose it uniformly\n const expr = expressions[i] as unknown as { span: { start: number; end: number } };\n const exprStart = positionCtx.converter.byteOffsetToCharIndex(expr.span.start - positionCtx.spanOffset);\n const exprEnd = positionCtx.converter.byteOffsetToCharIndex(expr.span.end - positionCtx.spanOffset);\n expressionRanges.push({ start: exprStart, end: exprEnd });\n }\n }\n }\n const content = parts.join(\"\");\n\n const template: ExtractedTemplate = {\n schemaName,\n kind,\n content,\n ...(elementName !== undefined ? { elementName } : {}),\n ...(typeName !== undefined ? { typeName } : {}),\n ...(typeNameSpan !== undefined ? { typeNameSpan } : {}),\n ...(positionCtx && contentStart !== -1 && contentEnd !== -1\n ? { contentRange: { start: contentStart, end: contentEnd } }\n : {}),\n ...(expressionRanges.length > 0 ? { expressionRanges } : {}),\n };\n\n templates.push(template);\n};\n\n/**\n * Find the innermost gql call, unwrapping method chains like .attach().\n */\nexport const findGqlCall = (identifiers: ReadonlySet<string>, node: Node): CallExpression | null => {\n if (!node || node.type !== \"CallExpression\") {\n return null;\n }\n\n const call = node as unknown as CallExpression;\n if (getGqlCallSchemaName(identifiers, call) !== null) {\n return call;\n }\n\n const callee = call.callee;\n if (callee.type !== \"MemberExpression\") {\n return null;\n }\n\n return findGqlCall(identifiers, callee.object as unknown as Node);\n};\n\n/**\n * Walk AST to find gql calls and extract templates.\n */\nexport function walkAndExtract(\n node: Node,\n identifiers: ReadonlySet<string>,\n positionCtx: PositionTrackingContext,\n): ExtractedTemplateWithPosition[];\nexport function walkAndExtract(\n node: Node,\n identifiers: ReadonlySet<string>,\n positionCtx?: PositionTrackingContext,\n): ExtractedTemplate[];\nexport function walkAndExtract(\n node: Node,\n identifiers: ReadonlySet<string>,\n positionCtx?: PositionTrackingContext,\n): ExtractedTemplate[] {\n const templates: ExtractedTemplate[] = [];\n\n const visit = (n: Node | ReadonlyArray<Node> | Record<string, unknown>): void => {\n if (!n || typeof n !== \"object\") {\n return;\n }\n\n if (\"type\" in n && n.type === \"CallExpression\") {\n const gqlCall = findGqlCall(identifiers, n as Node);\n if (gqlCall) {\n const schemaName = getGqlCallSchemaName(identifiers, gqlCall);\n if (schemaName) {\n const arrow = gqlCall.arguments[0]?.expression as ArrowFunctionExpression;\n templates.push(...extractTemplatesFromCallback(arrow, schemaName, positionCtx));\n }\n return; // Don't recurse into gql calls\n }\n }\n\n // Recurse into all array and object properties\n if (Array.isArray(n)) {\n for (const item of n) {\n visit(item as Node);\n }\n return;\n }\n\n for (const key of Object.keys(n)) {\n if (key === \"span\" || key === \"type\") {\n continue;\n }\n const value = (n as Record<string, unknown>)[key];\n if (value && typeof value === \"object\") {\n visit(value as Node);\n }\n }\n };\n\n visit(node);\n return templates;\n}\n\n/**\n * Walk AST to find gql callback builder calls and extract field call trees.\n * Companion to walkAndExtract — collects ExtractedFieldTree instead of ExtractedTemplate.\n */\nexport const walkAndExtractFieldTrees = (\n node: Node,\n identifiers: ReadonlySet<string>,\n positionCtx?: PositionTrackingContext,\n): ExtractedFieldTree[] => {\n const trees: ExtractedFieldTree[] = [];\n\n const visit = (n: Node | ReadonlyArray<Node> | Record<string, unknown>): void => {\n if (!n || typeof n !== \"object\") return;\n\n if (\"type\" in n && n.type === \"CallExpression\") {\n const gqlCall = findGqlCall(identifiers, n as Node);\n if (gqlCall) {\n const schemaName = getGqlCallSchemaName(identifiers, gqlCall);\n if (schemaName) {\n const arrow = gqlCall.arguments[0]?.expression as ArrowFunctionExpression;\n // Process each expression in the arrow body for field trees\n const processExpr = (expr: Node): void => {\n if (expr.type === \"CallExpression\") {\n const tree = extractFieldCallTree(expr, schemaName, positionCtx);\n if (tree) trees.push(tree);\n }\n };\n if (arrow.body.type !== \"BlockStatement\") {\n processExpr(arrow.body as Node);\n } else {\n for (const stmt of arrow.body.stmts) {\n if (stmt.type === \"ReturnStatement\" && stmt.argument) {\n processExpr(stmt.argument as Node);\n }\n }\n }\n }\n return;\n }\n }\n\n if (Array.isArray(n)) {\n for (const item of n) visit(item as Node);\n return;\n }\n\n for (const key of Object.keys(n)) {\n if (key === \"span\" || key === \"type\") continue;\n const value = (n as Record<string, unknown>)[key];\n if (value && typeof value === \"object\") visit(value as Node);\n }\n };\n\n visit(node);\n return trees;\n};\n\n/**\n * Convert a byte-space span to a character-index span using a position context.\n * Used for tracking field name and call positions within a callback builder.\n */\nconst convertSpan = (\n span: { start: number; end: number },\n positionCtx: PositionTrackingContext,\n): { start: number; end: number } => ({\n start: positionCtx.converter.byteOffsetToCharIndex(span.start - positionCtx.spanOffset),\n end: positionCtx.converter.byteOffsetToCharIndex(span.end - positionCtx.spanOffset),\n});\n\n/**\n * Walk an arrow function body to extract FieldCallNode children.\n * The arrow body should return an ObjectExpression where each property\n * is a SpreadElement wrapping an outer CallExpression:\n * `({ f }) => ({ ...f(\"fieldName\")(...) })`\n */\nconst extractFieldCallChildren = (\n arrow: ArrowFunctionExpression,\n positionCtx?: PositionTrackingContext,\n): readonly FieldCallNode[] => {\n // Unwrap the body to get the ObjectExpression\n let bodyExpr: { type: string } | undefined;\n\n if (arrow.body.type === \"BlockStatement\") {\n for (const stmt of (arrow.body as { stmts: { type: string; argument?: { type: string } }[] }).stmts) {\n if (stmt.type === \"ReturnStatement\" && stmt.argument) {\n bodyExpr = stmt.argument as { type: string };\n break;\n }\n }\n } else {\n bodyExpr = arrow.body as { type: string };\n }\n\n // Unwrap ParenthesisExpression wrapping ObjectExpression\n if (bodyExpr?.type === \"ParenthesisExpression\") {\n bodyExpr = (bodyExpr as unknown as { expression: { type: string } }).expression;\n }\n\n if (!bodyExpr || bodyExpr.type !== \"ObjectExpression\") {\n return [];\n }\n\n const objExpr = bodyExpr as unknown as ObjectExpression;\n const children: FieldCallNode[] = [];\n\n for (const prop of objExpr.properties) {\n if (prop.type !== \"SpreadElement\") continue;\n\n // SpreadElement.arguments is the spread expression: f(\"fieldName\")(...)\n const outerCall = (prop as unknown as { arguments: { type: string } }).arguments;\n if (!outerCall || outerCall.type !== \"CallExpression\") continue;\n\n const outer = outerCall as unknown as CallExpression;\n\n // The callee of the outer call should itself be a CallExpression: f(\"fieldName\")\n if (outer.callee.type !== \"CallExpression\") continue;\n const innerCall = outer.callee as CallExpression;\n\n // innerCall.callee should be Identifier (the \"f\" function)\n if (innerCall.callee.type !== \"Identifier\") continue;\n\n // innerCall.arguments[0] should be a StringLiteral — the field name\n const fieldNameArg = innerCall.arguments[0]?.expression;\n if (!fieldNameArg || fieldNameArg.type !== \"StringLiteral\") continue;\n\n const strLit = fieldNameArg as unknown as { value: string; span: { start: number; end: number } };\n const fieldName = strLit.value;\n\n // Compute spans\n const outerCallSpan = (outer as unknown as { span: { start: number; end: number } }).span;\n const strLitSpan = strLit.span;\n\n // callSpan covers the full outer expression; fieldNameSpan is inside quotes (adjust by +1/-1 for ASCII quote)\n const callSpan = positionCtx\n ? convertSpan(outerCallSpan, positionCtx)\n : { start: outerCallSpan.start, end: outerCallSpan.end };\n const fieldNameSpan = positionCtx\n ? {\n start: positionCtx.converter.byteOffsetToCharIndex(strLitSpan.start + 1 - positionCtx.spanOffset),\n end: positionCtx.converter.byteOffsetToCharIndex(strLitSpan.end - 1 - positionCtx.spanOffset),\n }\n : { start: strLitSpan.start + 1, end: strLitSpan.end - 1 };\n\n // Discriminate nested kind based on outer call arguments\n const nested = extractFieldCallNested(outer, positionCtx);\n\n children.push({ fieldName, fieldNameSpan, callSpan, nested });\n }\n\n return children;\n};\n\n/**\n * Extract the nested structure from an outer field call.\n * The outer call is: f(\"fieldName\")(arg) where arg determines nesting.\n */\nconst extractFieldCallNested = (outer: CallExpression, positionCtx?: PositionTrackingContext): FieldCallNested | null => {\n const outerArg = outer.arguments[0]?.expression;\n\n // No argument or absent argument → scalar field\n if (!outerArg) return null;\n\n // Arrow function argument → object field (recurse into nested callback body)\n if (outerArg.type === \"ArrowFunctionExpression\") {\n const nestedArrow = outerArg as unknown as ArrowFunctionExpression;\n const argSpan = (outerArg as unknown as { span: { start: number; end: number } }).span;\n const span = positionCtx ? convertSpan(argSpan, positionCtx) : { start: argSpan.start, end: argSpan.end };\n const children = extractFieldCallChildren(nestedArrow, positionCtx);\n return { kind: \"object\", span, children };\n }\n\n // Object argument → union field\n if (outerArg.type === \"ObjectExpression\") {\n const unionObj = outerArg as unknown as ObjectExpression;\n const argSpan = (outerArg as unknown as { span: { start: number; end: number } }).span;\n const span = positionCtx ? convertSpan(argSpan, positionCtx) : { start: argSpan.start, end: argSpan.end };\n const branches: UnionBranchNode[] = [];\n\n for (const prop of unionObj.properties) {\n if (prop.type !== \"KeyValueProperty\") continue;\n\n const kvProp = prop as unknown as {\n key: { type: string; value: string; span: { start: number; end: number } };\n value: { type: string; span: { start: number; end: number } };\n };\n\n // Skip __typename: true (BooleanLiteral value)\n if (kvProp.key.value === \"__typename\" && kvProp.value.type === \"BooleanLiteral\") continue;\n\n // Key must be Identifier (type name)\n if (kvProp.key.type !== \"Identifier\") continue;\n\n // Value must be ArrowFunctionExpression\n if (kvProp.value.type !== \"ArrowFunctionExpression\") continue;\n\n const typeName = kvProp.key.value;\n const typeNameSpan = positionCtx\n ? convertSpan(kvProp.key.span, positionCtx)\n : { start: kvProp.key.span.start, end: kvProp.key.span.end };\n const branchSpan = positionCtx\n ? convertSpan(kvProp.value.span, positionCtx)\n : { start: kvProp.value.span.start, end: kvProp.value.span.end };\n\n const branchArrow = kvProp.value as unknown as ArrowFunctionExpression;\n const children = extractFieldCallChildren(branchArrow, positionCtx);\n\n branches.push({ typeName, typeNameSpan, branchSpan, children });\n }\n\n return { kind: \"union\", span, branches };\n }\n\n return null;\n};\n\n/**\n * Extract a field call tree from a callback builder expression.\n *\n * Expects a top-level expression of the callback builder pattern:\n * `query(\"Name\")({ fields: ({ f }) => ({ ...f(\"id\")(), ...f(\"name\")() }) })({})`\n *\n * Returns an `ExtractedFieldTree` describing the root children (fields on the root selection set),\n * or `null` if the expression is not a callback builder or has no `fields` property.\n *\n * The `positionCtx` is optional — when provided, all span values will be character offsets\n * in the original TypeScript source. When omitted, spans are raw SWC byte positions.\n */\nexport const extractFieldCallTree = (\n expr: Node,\n schemaName: string,\n positionCtx?: PositionTrackingContext,\n): ExtractedFieldTree | null => {\n const result = findCallbackBuilderCalls(expr);\n if (!result) return null;\n\n const { curriedCall, configCall } = result;\n\n // Extract kind and elementName from the curried call: query(\"Name\")\n const kind = (curriedCall.callee as { value: string }).value as OperationKind;\n const nameArg = curriedCall.arguments[0]?.expression;\n const elementName = nameArg?.type === \"StringLiteral\" ? (nameArg as { value: string }).value : undefined;\n\n // Find the `fields` property in the config object\n const configObj = configCall.arguments[0]?.expression as ObjectExpression;\n for (const prop of configObj.properties) {\n if (prop.type !== \"KeyValueProperty\") continue;\n\n const kvProp = prop as unknown as {\n key: { type: string; value: string };\n value: { type: string; span: { start: number; end: number } };\n };\n if (kvProp.key.type !== \"Identifier\" || kvProp.key.value !== \"fields\") continue;\n if (kvProp.value.type !== \"ArrowFunctionExpression\") continue;\n\n const fieldsArrow = kvProp.value as unknown as ArrowFunctionExpression;\n const fieldsSpan = kvProp.value.span;\n const rootSpan = positionCtx ? convertSpan(fieldsSpan, positionCtx) : { start: fieldsSpan.start, end: fieldsSpan.end };\n\n const children = extractFieldCallChildren(fieldsArrow, positionCtx);\n\n return {\n schemaName,\n kind,\n ...(elementName !== undefined ? { elementName } : {}),\n rootSpan,\n children,\n };\n }\n\n return null;\n};\n","/**\n * GraphQL template formatting utilities.\n *\n * Pure string operations — no dependency on `graphql` package.\n * Consumers provide their own format function (e.g., graphql-js parse/print).\n *\n * @module\n */\n\nimport type { ExtractedTemplate, TemplateFormatEdit } from \"./types\";\n\n/** A function that formats GraphQL source text. */\nexport type FormatGraphqlFn = (source: string) => string;\n\n/**\n * Detect the base indentation for a template by looking at the line\n * containing the opening backtick.\n */\nexport const detectBaseIndent = (tsSource: string, contentStartOffset: number): string => {\n // Find the start of the line containing contentStartOffset\n let lineStart = contentStartOffset;\n while (lineStart > 0 && tsSource.charCodeAt(lineStart - 1) !== 10) {\n lineStart--;\n }\n\n // Extract leading whitespace from that line\n let i = lineStart;\n while (i < tsSource.length && (tsSource.charCodeAt(i) === 32 || tsSource.charCodeAt(i) === 9)) {\n i++;\n }\n\n return tsSource.slice(lineStart, i);\n};\n\n/**\n * Convert graphql-js 2-space indentation to the target indent unit.\n * Strips leading 2-space groups and replaces with equivalent indent units.\n */\nconst convertGraphqlIndent = (line: string, indentUnit: string): string => {\n if (indentUnit === \" \") return line; // No conversion needed\n let level = 0;\n let pos = 0;\n while (pos + 1 < line.length && line[pos] === \" \" && line[pos + 1] === \" \") {\n level++;\n pos += 2;\n }\n if (level === 0) return line;\n return indentUnit.repeat(level) + line.slice(pos);\n};\n\n/**\n * Re-indent formatted GraphQL to match the embedding context.\n *\n * **Inline templates** (content does NOT start with `\\n`):\n * - Line 0 (`{`): no prefix — appears right after backtick\n * - Lines 1..N: graphql-js indentation only (converted to target unit)\n *\n * **Block templates** (content starts with `\\n`):\n * - All lines: `baseIndent + indentUnit` prefix + converted graphql indent\n * - Trailing: `\\n` + `baseIndent`\n */\nexport const reindent = (formatted: string, baseIndent: string, originalContent: string, indentUnit: string = \" \"): string => {\n const trimmedFormatted = formatted.trim();\n\n // If original was single-line and formatted is also single-line, keep it\n if (!originalContent.includes(\"\\n\") && !trimmedFormatted.includes(\"\\n\")) {\n return trimmedFormatted;\n }\n\n const lines = trimmedFormatted.split(\"\\n\");\n\n // Match original leading/trailing newline pattern\n const startsWithNewline = originalContent.startsWith(\"\\n\");\n const endsWithNewline = /\\n\\s*$/.test(originalContent);\n\n const indentedLines = lines.map((line, i) => {\n if (line.trim() === \"\") return \"\";\n const converted = convertGraphqlIndent(line, indentUnit);\n\n if (!startsWithNewline) {\n // Inline template: first line has no prefix (appears after backtick);\n // body lines get baseIndent + converted graphql indent to align with TS context.\n if (i === 0) return converted;\n return baseIndent + converted;\n }\n\n // Block template: every line gets baseIndent + indentUnit prefix\n return `${baseIndent}${indentUnit}${converted}`;\n });\n\n let result = indentedLines.join(\"\\n\");\n if (startsWithNewline) {\n result = `\\n${result}`;\n }\n if (endsWithNewline) {\n result = `${result}\\n${baseIndent}`;\n }\n\n return result;\n};\n\n/**\n * Detect the indentation unit used in a TypeScript source file.\n *\n * Uses the smallest indentation width found as the indent unit.\n * In files with embedded templates (e.g., graphql in backticks),\n * template content lines are at `baseIndent + graphqlIndent`,\n * so their absolute indent is always >= the file's indent unit.\n */\nexport const detectIndentUnit = (tsSource: string): string => {\n let minSpaceIndent = Infinity;\n for (const line of tsSource.split(\"\\n\")) {\n if (line.length === 0 || line.trimStart().length === 0) continue;\n if (line[0] === \"\\t\") return \"\\t\";\n const match = line.match(/^( +)\\S/);\n if (match) minSpaceIndent = Math.min(minSpaceIndent, match[1]!.length);\n }\n if (minSpaceIndent === Infinity || minSpaceIndent <= 1) return \" \";\n return \" \".repeat(minSpaceIndent);\n};\n\nconst GRAPHQL_KEYWORDS = new Set([\"query\", \"mutation\", \"subscription\", \"fragment\"]);\n\n/**\n * Reconstruct a full GraphQL document from template content + metadata.\n *\n * For curried syntax, the template `content` is only the body (e.g., `{ user { id } }`).\n * GraphQL parsers need a full document (e.g., `query GetUser { user { id } }`).\n *\n * For bare-tag syntax, the content already starts with a keyword and is a full document.\n *\n * @returns The wrapped source and a regex pattern to strip the synthetic prefix after formatting\n */\nexport const buildGraphqlWrapper = (template: ExtractedTemplate): { wrapped: string; prefixPattern: RegExp | null } => {\n const content = template.content.trimStart();\n const firstWord = content.split(/[\\s({]/)[0] ?? \"\";\n\n // If content already starts with a GraphQL keyword, it's a full document (bare-tag)\n if (GRAPHQL_KEYWORDS.has(firstWord)) {\n return { wrapped: template.content, prefixPattern: null };\n }\n\n // Curried syntax — reconstruct the header\n if (template.elementName) {\n if (template.kind === \"fragment\" && template.typeName) {\n const prefix = `fragment ${template.elementName} on ${template.typeName} `;\n const prefixPattern = new RegExp(`^fragment\\\\s+${template.elementName}\\\\s+on\\\\s+${template.typeName}\\\\s*`);\n return { wrapped: prefix + template.content, prefixPattern };\n }\n const prefix = `${template.kind} ${template.elementName} `;\n const prefixPattern = new RegExp(`^${template.kind}\\\\s+${template.elementName}\\\\s*`);\n return { wrapped: prefix + template.content, prefixPattern };\n }\n\n // No elementName — try wrapping with just the kind\n const prefix = `${template.kind} `;\n const prefixPattern = new RegExp(`^${template.kind}\\\\s*`);\n return { wrapped: prefix + template.content, prefixPattern };\n};\n\n/**\n * Strip the reconstructed prefix from formatted output to get back the template body.\n * Uses regex pattern matching to handle whitespace normalization by the formatter\n * (e.g., `query Foo ($id: ID!)` → `query Foo($id: ID!)`).\n */\nexport const unwrapFormattedContent = (formatted: string, prefixPattern: RegExp | null): string => {\n if (!prefixPattern) return formatted;\n const match = formatted.match(prefixPattern);\n if (!match) return formatted;\n return formatted.slice(match[0].length);\n};\n\n/**\n * Format GraphQL templates within TypeScript source.\n *\n * Applies the given format function to each template's content, handles\n * wrapper reconstruction for curried syntax, and re-indents the result\n * to match the TypeScript embedding context.\n *\n * @returns Array of edits to apply to the source (sorted by position, not yet applied)\n */\nexport const formatTemplatesInSource = (\n templates: readonly ExtractedTemplate[],\n tsSource: string,\n formatGraphql: FormatGraphqlFn,\n): readonly TemplateFormatEdit[] => {\n const indentUnit = detectIndentUnit(tsSource);\n const edits: TemplateFormatEdit[] = [];\n\n for (const template of templates) {\n if (!template.contentRange) continue;\n // Callback-variables contain only variable definitions (e.g., \"($id: ID!)\"),\n // not valid standalone GraphQL — skip formatting.\n if (template.source === \"callback-variables\") continue;\n\n // Wrap the content for formatting\n const { wrapped, prefixPattern } = buildGraphqlWrapper(template);\n\n let formatted: string;\n try {\n formatted = formatGraphql(wrapped);\n } catch {\n continue;\n }\n\n // Unwrap the prefix\n let unwrapped = unwrapFormattedContent(formatted, prefixPattern);\n\n // Restore interpolation expressions: replace __FRAG_SPREAD_N__ with original ${...} syntax\n if (template.expressionRanges && template.expressionRanges.length > 0) {\n for (const [i, range] of template.expressionRanges.entries()) {\n const exprText = tsSource.slice(range.start, range.end);\n unwrapped = unwrapped.replace(`__FRAG_SPREAD_${i}__`, `\\${${exprText}}`);\n }\n }\n\n // Fast path: skip if formatter produces identical output\n if (unwrapped === template.content) {\n continue;\n }\n\n // Detect base indentation from the TS source\n const baseIndent = detectBaseIndent(tsSource, template.contentRange.start);\n\n // Re-indent the formatted output\n const reindented = reindent(unwrapped, baseIndent, template.content, indentUnit);\n\n // Skip if no changes after re-indentation\n if (reindented === template.content) {\n continue;\n }\n\n edits.push({\n start: template.contentRange.start,\n end: template.contentRange.end,\n newText: reindented,\n });\n }\n\n return edits;\n};\n"],"mappings":"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"}
|