@soda-gql/lsp 0.14.0 → 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.
@@ -25,23 +25,158 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  }) : target, mod));
26
26
 
27
27
  //#endregion
28
+ let node_fs = require("node:fs");
29
+ let node_path = require("node:path");
30
+ let __soda_gql_builder = require("@soda-gql/builder");
31
+ let __soda_gql_config = require("@soda-gql/config");
32
+ let graphql = require("graphql");
33
+ let graphql_language_service = require("graphql-language-service");
28
34
  let node_module = require("node:module");
29
35
  let node_url = require("node:url");
30
36
  let __soda_gql_common = require("@soda-gql/common");
31
37
  let __soda_gql_common_template_extraction = require("@soda-gql/common/template-extraction");
32
- let graphql = require("graphql");
33
- let node_fs = require("node:fs");
34
- let node_path = require("node:path");
35
- let __soda_gql_tools_codegen = require("@soda-gql/tools/codegen");
36
38
  let neverthrow = require("neverthrow");
37
- let __soda_gql_config = require("@soda-gql/config");
39
+ let __soda_gql_tools_codegen = require("@soda-gql/tools/codegen");
38
40
  let vscode_languageserver_node = require("vscode-languageserver/node");
39
41
  let vscode_languageserver_textdocument = require("vscode-languageserver-textdocument");
40
- let __soda_gql_builder = require("@soda-gql/builder");
41
42
  let typescript = require("typescript");
42
43
  typescript = __toESM(typescript);
43
- let graphql_language_service = require("graphql-language-service");
44
44
 
45
+ //#region packages/lsp/src/field-tree-resolver.ts
46
+ /** Get the root type name for an operation kind from the schema. */
47
+ const getRootTypeName = (schema, kind) => {
48
+ switch (kind) {
49
+ case "query": return schema.getQueryType()?.name ?? null;
50
+ case "mutation": return schema.getMutationType()?.name ?? null;
51
+ case "subscription": return schema.getSubscriptionType()?.name ?? null;
52
+ default: return null;
53
+ }
54
+ };
55
+ /** Classify a GraphQL named type into a kind string. */
56
+ const classifyType = (namedType) => {
57
+ if ((0, graphql.isObjectType)(namedType)) return "object";
58
+ if (namedType instanceof graphql.GraphQLUnionType) return "union";
59
+ if ("getValues" in namedType) return "enum";
60
+ return "scalar";
61
+ };
62
+ /** Resolve a single field from a parent object type. */
63
+ const resolveField = (schema, parentTypeName, fieldName) => {
64
+ const parentType = schema.getType(parentTypeName);
65
+ if (!parentType || !(0, graphql.isObjectType)(parentType)) return null;
66
+ const fields = parentType.getFields();
67
+ const fieldDef = fields[fieldName];
68
+ if (!fieldDef) return null;
69
+ const namedType = (0, graphql.getNamedType)(fieldDef.type);
70
+ if (!namedType) return null;
71
+ return {
72
+ fieldDef,
73
+ namedType
74
+ };
75
+ };
76
+ /** Resolve untyped FieldCallNode children into typed nodes. */
77
+ const resolveChildren = (schema, parentTypeName, children) => {
78
+ return children.map((child) => resolveNode(schema, parentTypeName, child));
79
+ };
80
+ /** Resolve a single FieldCallNode into a TypedFieldNode. */
81
+ const resolveNode = (schema, parentTypeName, node) => {
82
+ const resolved = resolveField(schema, parentTypeName, node.fieldName);
83
+ const fieldTypeName = resolved?.namedType.name ?? null;
84
+ const fieldTypeKind = resolved ? classifyType(resolved.namedType) : null;
85
+ let nested = null;
86
+ if (node.nested) {
87
+ if (node.nested.kind === "object" && fieldTypeName) {
88
+ nested = {
89
+ kind: "object",
90
+ span: node.nested.span,
91
+ children: resolveChildren(schema, fieldTypeName, node.nested.children)
92
+ };
93
+ } else if (node.nested.kind === "union") {
94
+ nested = resolveUnionNested(schema, resolved?.namedType ?? null, node.nested);
95
+ }
96
+ }
97
+ return {
98
+ fieldName: node.fieldName,
99
+ fieldNameSpan: node.fieldNameSpan,
100
+ callSpan: node.callSpan,
101
+ parentTypeName,
102
+ fieldTypeName,
103
+ fieldTypeKind,
104
+ nested
105
+ };
106
+ };
107
+ /** Resolve union branches against a union type. */
108
+ const resolveUnionNested = (schema, unionType, nested) => {
109
+ const memberNames = new Set();
110
+ if (unionType instanceof graphql.GraphQLUnionType) {
111
+ for (const member of unionType.getTypes()) {
112
+ memberNames.add(member.name);
113
+ }
114
+ }
115
+ const branches = nested.branches.map((branch) => ({
116
+ typeName: branch.typeName,
117
+ typeNameSpan: branch.typeNameSpan,
118
+ branchSpan: branch.branchSpan,
119
+ valid: memberNames.has(branch.typeName),
120
+ children: resolveChildren(schema, branch.typeName, branch.children)
121
+ }));
122
+ return {
123
+ kind: "union",
124
+ span: nested.span,
125
+ branches
126
+ };
127
+ };
128
+ /**
129
+ * Resolve an ExtractedFieldTree against a GraphQL schema.
130
+ * Returns null if the operation kind has no corresponding root type.
131
+ */
132
+ const resolveFieldTree = (tree, schema) => {
133
+ const rootTypeName = getRootTypeName(schema, tree.kind);
134
+ if (!rootTypeName) return null;
135
+ return {
136
+ schemaName: tree.schemaName,
137
+ rootTypeName,
138
+ rootSpan: tree.rootSpan,
139
+ children: resolveChildren(schema, rootTypeName, tree.children)
140
+ };
141
+ };
142
+ /**
143
+ * Find the TypedFieldNode or TypedUnionBranch at a given offset.
144
+ * Searches fieldNameSpan for field matches and typeNameSpan for union member matches.
145
+ */
146
+ const findNodeAtOffset = (tree, offset) => {
147
+ return findInChildren(tree.children, offset, null);
148
+ };
149
+ const findInChildren = (children, offset, _parentForUnion) => {
150
+ for (const node of children) {
151
+ if (offset >= node.fieldNameSpan.start && offset <= node.fieldNameSpan.end) {
152
+ return {
153
+ kind: "field",
154
+ node
155
+ };
156
+ }
157
+ if (node.nested) {
158
+ if (node.nested.kind === "object") {
159
+ const result = findInChildren(node.nested.children, offset, null);
160
+ if (result) return result;
161
+ } else if (node.nested.kind === "union") {
162
+ for (const branch of node.nested.branches) {
163
+ if (offset >= branch.typeNameSpan.start && offset <= branch.typeNameSpan.end) {
164
+ return {
165
+ kind: "unionMember",
166
+ branch,
167
+ parentNode: node
168
+ };
169
+ }
170
+ const result = findInChildren(branch.children, offset, node);
171
+ if (result) return result;
172
+ }
173
+ }
174
+ }
175
+ }
176
+ return null;
177
+ };
178
+
179
+ //#endregion
45
180
  //#region packages/lsp/src/fragment-args-preprocessor.ts
46
181
  /**
47
182
  * Find the matching closing parenthesis for a balanced group.
@@ -199,9 +334,17 @@ const collectGqlIdentifiers = (module$1, filePath, helper) => {
199
334
  /**
200
335
  * Reconstruct full GraphQL source from an extracted template.
201
336
  * Prepends the definition header from curried tag call arguments.
337
+ *
338
+ * For callback-variables templates (source === "callback-variables"), wraps the
339
+ * partial variables string in a dummy operation to produce valid GraphQL that
340
+ * graphql-language-service can parse.
202
341
  */
203
342
  const reconstructGraphql = (template) => {
204
343
  const content = template.content;
344
+ if (template.source === "callback-variables") {
345
+ const name = template.elementName ?? "__variables__";
346
+ return `${template.kind} ${name} ${content} { __typename }`;
347
+ }
205
348
  if (template.elementName) {
206
349
  if (template.kind === "fragment" && template.typeName) {
207
350
  return `fragment ${template.elementName} on ${template.typeName} ${content}`;
@@ -210,6 +353,21 @@ const reconstructGraphql = (template) => {
210
353
  }
211
354
  return content;
212
355
  };
356
+ /**
357
+ * Compute the length of the synthesized prefix before the template content
358
+ * in the reconstructed GraphQL string.
359
+ *
360
+ * For tagged templates, headerLen = reconstructed.length - content.length (content is at the end).
361
+ * For callback-variables, content is in the MIDDLE (prefix + content + suffix), so we must
362
+ * compute the prefix length explicitly.
363
+ */
364
+ const computeHeaderLen = (template, reconstructed) => {
365
+ if (template.source === "callback-variables") {
366
+ const name = template.elementName ?? "__variables__";
367
+ return `${template.kind} ${name} `.length;
368
+ }
369
+ return reconstructed.length - template.content.length;
370
+ };
213
371
  const indexFragments = (uri, templates, source) => {
214
372
  const fragments = [];
215
373
  for (const template of templates) {
@@ -217,7 +375,7 @@ const indexFragments = (uri, templates, source) => {
217
375
  continue;
218
376
  }
219
377
  const reconstructed = reconstructGraphql(template);
220
- const headerLen = reconstructed.length - template.content.length;
378
+ const headerLen = computeHeaderLen(template, reconstructed);
221
379
  const { preprocessed } = preprocessFragmentArgs(reconstructed);
222
380
  try {
223
381
  const ast = (0, graphql.parse)(preprocessed, { noLocation: false });
@@ -291,33 +449,45 @@ const createDocumentManager = (helper, swcOptions) => {
291
449
  };
292
450
  const cache = new Map();
293
451
  const fragmentIndex = new Map();
294
- const extractTemplates = (uri, source) => {
452
+ const extractAll = (uri, source) => {
295
453
  const isTsx = uri.endsWith(".tsx");
296
454
  const program = safeParseSync(source, isTsx);
297
455
  if (!program) {
298
- return [];
456
+ return {
457
+ templates: [],
458
+ fieldTrees: []
459
+ };
299
460
  }
300
461
  const converter = (0, __soda_gql_common.createSwcSpanConverter)(source);
301
462
  const spanOffset = program.span.end - converter.byteLength + 1;
302
463
  const filePath = uri.startsWith("file://") ? (0, node_url.fileURLToPath)(uri) : uri;
303
464
  const gqlIdentifiers = collectGqlIdentifiers(program, filePath, helper);
304
465
  if (gqlIdentifiers.size === 0) {
305
- return [];
466
+ return {
467
+ templates: [],
468
+ fieldTrees: []
469
+ };
306
470
  }
307
471
  const positionCtx = {
308
472
  spanOffset,
309
473
  converter
310
474
  };
311
- return (0, __soda_gql_common_template_extraction.walkAndExtract)(program, gqlIdentifiers, positionCtx);
475
+ const templates = (0, __soda_gql_common_template_extraction.walkAndExtract)(program, gqlIdentifiers, positionCtx);
476
+ const fieldTrees = (0, __soda_gql_common_template_extraction.walkAndExtractFieldTrees)(program, gqlIdentifiers, positionCtx);
477
+ return {
478
+ templates,
479
+ fieldTrees
480
+ };
312
481
  };
313
482
  return {
314
483
  update: (uri, version, source) => {
315
- const templates = extractTemplates(uri, source);
484
+ const { templates, fieldTrees } = extractAll(uri, source);
316
485
  const state = {
317
486
  uri,
318
487
  version,
319
488
  source,
320
489
  templates,
490
+ fieldTrees,
321
491
  ...swcUnavailable ? { swcUnavailable: true } : {}
322
492
  };
323
493
  cache.set(uri, state);
@@ -340,6 +510,20 @@ const createDocumentManager = (helper, swcOptions) => {
340
510
  }
341
511
  return state.templates.find((t) => offset >= t.contentRange.start && offset <= t.contentRange.end);
342
512
  },
513
+ findFieldTreeAtOffset: (uri, offset) => {
514
+ const state = cache.get(uri);
515
+ if (!state) {
516
+ return undefined;
517
+ }
518
+ return state.fieldTrees.find((t) => offset >= t.rootSpan.start && offset <= t.rootSpan.end);
519
+ },
520
+ findTemplateByTypeNameOffset: (uri, offset) => {
521
+ const state = cache.get(uri);
522
+ if (!state) {
523
+ return undefined;
524
+ }
525
+ return state.templates.find((t) => t.typeNameSpan && offset >= t.typeNameSpan.start && offset <= t.typeNameSpan.end);
526
+ },
343
527
  getExternalFragments: (uri, schemaName) => {
344
528
  const result = [];
345
529
  for (const [fragmentUri, fragments] of fragmentIndex) {
@@ -373,7 +557,7 @@ const createDocumentManager = (helper, swcOptions) => {
373
557
  continue;
374
558
  }
375
559
  const reconstructed = reconstructGraphql(template);
376
- const headerLen = reconstructed.length - template.content.length;
560
+ const headerLen = computeHeaderLen(template, reconstructed);
377
561
  const { preprocessed } = preprocessFragmentArgs(reconstructed);
378
562
  try {
379
563
  const ast = (0, graphql.parse)(preprocessed, { noLocation: false });
@@ -408,50 +592,6 @@ const createDocumentManager = (helper, swcOptions) => {
408
592
  };
409
593
  };
410
594
 
411
- //#endregion
412
- //#region packages/lsp/src/errors.ts
413
- /** Error constructor helpers for concise error creation. */
414
- const lspErrors = {
415
- configLoadFailed: (message, cause) => ({
416
- code: "CONFIG_LOAD_FAILED",
417
- message,
418
- cause
419
- }),
420
- schemaLoadFailed: (schemaName, message, cause) => ({
421
- code: "SCHEMA_LOAD_FAILED",
422
- message: message ?? `Failed to load schema: ${schemaName}`,
423
- schemaName,
424
- cause
425
- }),
426
- schemaBuildFailed: (schemaName, message, cause) => ({
427
- code: "SCHEMA_BUILD_FAILED",
428
- message: message ?? `Failed to build schema: ${schemaName}`,
429
- schemaName,
430
- cause
431
- }),
432
- schemaNotConfigured: (schemaName) => ({
433
- code: "SCHEMA_NOT_CONFIGURED",
434
- message: `Schema "${schemaName}" is not configured in soda-gql.config`,
435
- schemaName
436
- }),
437
- parseFailed: (uri, message, cause) => ({
438
- code: "PARSE_FAILED",
439
- message: message ?? `Failed to parse: ${uri}`,
440
- uri,
441
- cause
442
- }),
443
- internalInvariant: (message, context, cause) => ({
444
- code: "INTERNAL_INVARIANT",
445
- message,
446
- context,
447
- cause
448
- }),
449
- swcResolutionFailed: (message) => ({
450
- code: "SWC_RESOLUTION_FAILED",
451
- message: message ?? "@swc/core not found. Install @soda-gql/builder (which provides @swc/core) in your project and restart the LSP server to enable template extraction."
452
- })
453
- };
454
-
455
595
  //#endregion
456
596
  //#region packages/lsp/src/position-mapping.ts
457
597
  /** Compute byte offsets for the start of each line in the source text. */
@@ -529,164 +669,67 @@ const createPositionMapper = (input) => {
529
669
  };
530
670
 
531
671
  //#endregion
532
- //#region packages/lsp/src/schema-resolver.ts
533
- /**
534
- * Schema resolver: maps schema names to GraphQLSchema objects.
535
- * @module
536
- */
537
- /** Wrap buildASTSchema (which throws) in a Result. */
538
- const safeBuildASTSchema = (schemaName, documentNode) => {
539
- try {
540
- return (0, neverthrow.ok)((0, graphql.buildASTSchema)(documentNode));
541
- } catch (e) {
542
- return (0, neverthrow.err)(lspErrors.schemaBuildFailed(schemaName, e instanceof Error ? e.message : String(e), e));
543
- }
544
- };
545
- const loadAndBuildSchema = (schemaName, schemaPaths) => {
546
- const documents = [];
547
- const files = [];
548
- for (const schemaPath of schemaPaths) {
549
- const resolvedPath = (0, node_path.resolve)(schemaPath);
550
- try {
551
- const content = (0, node_fs.readFileSync)(resolvedPath, "utf8");
552
- documents.push((0, graphql.parse)(content));
553
- files.push({
554
- filePath: resolvedPath,
555
- content
556
- });
557
- } catch (e) {
558
- return (0, neverthrow.err)(lspErrors.schemaLoadFailed(schemaName, e instanceof Error ? e.message : String(e)));
559
- }
560
- }
561
- const documentNode = (0, graphql.concatAST)(documents);
562
- const hash = (0, __soda_gql_tools_codegen.hashSchema)(documentNode);
563
- const buildResult = safeBuildASTSchema(schemaName, documentNode);
564
- if (buildResult.isErr()) {
565
- return (0, neverthrow.err)(buildResult.error);
566
- }
567
- return (0, neverthrow.ok)({
568
- name: schemaName,
569
- schema: buildResult.value,
570
- documentNode,
571
- hash,
572
- files
672
+ //#region packages/lsp/src/handlers/diagnostics.ts
673
+ /** Compute LSP diagnostics for a single GraphQL template. */
674
+ const computeTemplateDiagnostics = (input) => {
675
+ const { template, schema, tsSource } = input;
676
+ const reconstructed = reconstructGraphql(template);
677
+ const headerLen = computeHeaderLen(template, reconstructed);
678
+ const { preprocessed } = preprocessFragmentArgs(reconstructed);
679
+ const mapper = createPositionMapper({
680
+ tsSource,
681
+ contentStartOffset: template.contentRange.start,
682
+ graphqlContent: template.content
573
683
  });
574
- };
575
- /** Create a schema resolver from config. Loads all schemas eagerly. */
576
- const createSchemaResolver = (config) => {
577
- const cache = new Map();
578
- for (const [name, schemaConfig] of Object.entries(config.schemas)) {
579
- const result = loadAndBuildSchema(name, schemaConfig.schema);
580
- if (result.isErr()) {
581
- return (0, neverthrow.err)(result.error);
684
+ const gqlDiagnostics = (0, graphql_language_service.getDiagnostics)(preprocessed, schema, undefined, undefined, input.externalFragments);
685
+ const placeholderPattern = /__FRAG_SPREAD_\d+__/;
686
+ const isCallbackVariables = template.source === "callback-variables";
687
+ const reconstructedLineOffsets = computeLineOffsets(preprocessed);
688
+ const contentLineOffsets = computeLineOffsets(template.content);
689
+ const toContentPosition = (pos) => {
690
+ const offset = positionToOffset$1(reconstructedLineOffsets, pos);
691
+ const contentOffset = Math.max(0, offset - headerLen);
692
+ return offsetToPosition(contentLineOffsets, contentOffset);
693
+ };
694
+ return gqlDiagnostics.filter((diag) => {
695
+ if (placeholderPattern.test(diag.message)) {
696
+ return false;
582
697
  }
583
- cache.set(name, result.value);
584
- }
585
- const resolver = {
586
- getSchema: (schemaName) => cache.get(schemaName),
587
- getSchemaNames: () => [...cache.keys()],
588
- reloadSchema: (schemaName) => {
589
- const schemaConfig = config.schemas[schemaName];
590
- if (!schemaConfig) {
591
- return (0, neverthrow.err)(lspErrors.schemaNotConfigured(schemaName));
592
- }
593
- const result = loadAndBuildSchema(schemaName, schemaConfig.schema);
594
- if (result.isErr()) {
595
- return (0, neverthrow.err)(result.error);
596
- }
597
- cache.set(schemaName, result.value);
598
- return (0, neverthrow.ok)(result.value);
599
- },
600
- reloadAll: () => {
601
- const errors = [];
602
- for (const [name, schemaConfig] of Object.entries(config.schemas)) {
603
- const result = loadAndBuildSchema(name, schemaConfig.schema);
604
- if (result.isErr()) {
605
- errors.push(result.error);
606
- } else {
607
- cache.set(name, result.value);
608
- }
698
+ if (isCallbackVariables && diag.message.includes("is never used")) {
699
+ return false;
700
+ }
701
+ const offset = positionToOffset$1(reconstructedLineOffsets, diag.range.start);
702
+ if (offset < headerLen) {
703
+ return false;
704
+ }
705
+ if (isCallbackVariables) {
706
+ const contentEnd = headerLen + template.content.length;
707
+ if (offset >= contentEnd) {
708
+ return false;
609
709
  }
610
- return errors.length > 0 ? (0, neverthrow.err)(errors) : (0, neverthrow.ok)(undefined);
611
710
  }
612
- };
613
- return (0, neverthrow.ok)(resolver);
614
- };
615
-
616
- //#endregion
617
- //#region packages/lsp/src/config-registry.ts
618
- /**
619
- * Config registry: maps document URIs to their nearest config context.
620
- * Supports multiple soda-gql configs in a monorepo workspace.
621
- * @module
622
- */
623
- const createConfigRegistry = (configPaths) => {
624
- const sortedPaths = [...configPaths].sort((a, b) => b.length - a.length);
625
- const contexts = new Map();
626
- for (const configPath of sortedPaths) {
627
- const configResult = (0, __soda_gql_config.loadConfig)(configPath);
628
- if (configResult.isErr()) {
629
- return (0, neverthrow.err)(lspErrors.configLoadFailed(`Failed to load config ${configPath}: ${configResult.error.message}`, configResult.error));
630
- }
631
- const config = configResult.value;
632
- const helper = (0, __soda_gql_builder.createGraphqlSystemIdentifyHelper)(config);
633
- const resolverResult = createSchemaResolver(config);
634
- if (resolverResult.isErr()) {
635
- return (0, neverthrow.err)(resolverResult.error);
636
- }
637
- contexts.set(configPath, {
638
- configPath,
639
- config,
640
- helper,
641
- schemaResolver: resolverResult.value,
642
- documentManager: createDocumentManager(helper, { resolveFrom: configPath })
643
- });
644
- }
645
- const uriCache = new Map();
646
- const resolveConfigPath = (dirPath) => {
647
- const cached = uriCache.get(dirPath);
648
- if (cached !== undefined) {
649
- return cached;
650
- }
651
- for (const configPath of sortedPaths) {
652
- const configDir = (0, node_path.dirname)(configPath);
653
- if (dirPath === configDir || dirPath.startsWith(`${configDir}${node_path.sep}`)) {
654
- uriCache.set(dirPath, configPath);
655
- return configPath;
656
- }
657
- }
658
- uriCache.set(dirPath, null);
659
- return null;
660
- };
661
- return (0, neverthrow.ok)({
662
- resolveForUri: (uri) => {
663
- if (!uri.startsWith("file://") && !uri.startsWith("/")) {
664
- return undefined;
665
- }
666
- const filePath = uri.startsWith("file://") ? (0, node_url.fileURLToPath)(uri) : uri;
667
- const dirPath = (0, node_path.dirname)(filePath);
668
- const configPath = resolveConfigPath(dirPath);
669
- return configPath ? contexts.get(configPath) : undefined;
670
- },
671
- getAllContexts: () => [...contexts.values()],
672
- reloadSchemas: (configPath) => {
673
- const ctx = contexts.get(configPath);
674
- if (!ctx) {
675
- return (0, neverthrow.err)([lspErrors.configLoadFailed(`Config not found: ${configPath}`)]);
676
- }
677
- return ctx.schemaResolver.reloadAll();
678
- },
679
- reloadAllSchemas: () => {
680
- const errors = [];
681
- for (const ctx of contexts.values()) {
682
- const result = ctx.schemaResolver.reloadAll();
683
- if (result.isErr()) {
684
- errors.push(...result.error);
685
- }
686
- }
687
- return errors.length > 0 ? (0, neverthrow.err)(errors) : (0, neverthrow.ok)(undefined);
688
- }
689
- });
711
+ return true;
712
+ }).map((diag) => {
713
+ const startContent = toContentPosition(diag.range.start);
714
+ const endContent = toContentPosition(diag.range.end);
715
+ const startTs = mapper.graphqlToTs(startContent);
716
+ const endTs = mapper.graphqlToTs(endContent);
717
+ return {
718
+ range: {
719
+ start: {
720
+ line: startTs.line,
721
+ character: startTs.character
722
+ },
723
+ end: {
724
+ line: endTs.line,
725
+ character: endTs.character
726
+ }
727
+ },
728
+ message: diag.message,
729
+ severity: diag.severity,
730
+ source: "soda-gql"
731
+ };
732
+ });
690
733
  };
691
734
 
692
735
  //#endregion
@@ -2949,6 +2992,303 @@ var Is;
2949
2992
  Is$1.typedArray = typedArray;
2950
2993
  })(Is || (Is = {}));
2951
2994
 
2995
+ //#endregion
2996
+ //#region packages/lsp/src/handlers/field-tree-diagnostics.ts
2997
+ /**
2998
+ * Diagnostics handler for callback builder field tree nodes.
2999
+ * Reports unknown fields and invalid union member types.
3000
+ * @module
3001
+ */
3002
+ /** Compute LSP diagnostics for a callback builder field tree. */
3003
+ const computeFieldTreeDiagnostics = (input) => {
3004
+ const { fieldTree, tsSource } = input;
3005
+ const lineOffsets = computeLineOffsets(tsSource);
3006
+ const diagnostics = [];
3007
+ const walkNodes = (nodes) => {
3008
+ for (const node of nodes) {
3009
+ if (node.fieldTypeName === null) {
3010
+ const start = offsetToPosition(lineOffsets, node.fieldNameSpan.start);
3011
+ const end = offsetToPosition(lineOffsets, node.fieldNameSpan.end);
3012
+ diagnostics.push({
3013
+ range: {
3014
+ start,
3015
+ end
3016
+ },
3017
+ message: `Unknown field "${node.fieldName}" on type "${node.parentTypeName}"`,
3018
+ severity: DiagnosticSeverity.Error,
3019
+ source: "soda-gql"
3020
+ });
3021
+ }
3022
+ if (node.nested) {
3023
+ walkNested(node.nested);
3024
+ }
3025
+ }
3026
+ };
3027
+ const walkNested = (nested) => {
3028
+ if (nested.kind === "object") {
3029
+ walkNodes(nested.children);
3030
+ } else {
3031
+ for (const branch of nested.branches) {
3032
+ if (!branch.valid) {
3033
+ const start = offsetToPosition(lineOffsets, branch.typeNameSpan.start);
3034
+ const end = offsetToPosition(lineOffsets, branch.typeNameSpan.end);
3035
+ diagnostics.push({
3036
+ range: {
3037
+ start,
3038
+ end
3039
+ },
3040
+ message: `Type "${branch.typeName}" is not a member of union type`,
3041
+ severity: DiagnosticSeverity.Error,
3042
+ source: "soda-gql"
3043
+ });
3044
+ }
3045
+ walkNodes(branch.children);
3046
+ }
3047
+ }
3048
+ };
3049
+ walkNodes(fieldTree.children);
3050
+ return diagnostics;
3051
+ };
3052
+
3053
+ //#endregion
3054
+ //#region packages/lsp/src/diagnostics-collector.ts
3055
+ /** Collect all diagnostics (template + field tree) for a document state. */
3056
+ const collectRawDiagnostics = (state, ctx) => {
3057
+ const templateDiagnostics = state.templates.flatMap((template) => {
3058
+ const entry = ctx.schemaResolver.getSchema(template.schemaName);
3059
+ if (!entry) {
3060
+ return [];
3061
+ }
3062
+ const externalFragments = ctx.documentManager.getExternalFragments(state.uri, template.schemaName).map((f) => f.definition);
3063
+ return [...computeTemplateDiagnostics({
3064
+ template,
3065
+ schema: entry.schema,
3066
+ tsSource: state.source,
3067
+ externalFragments
3068
+ })];
3069
+ });
3070
+ const fieldTreeDiagnostics = state.fieldTrees.flatMap((tree) => {
3071
+ const entry = ctx.schemaResolver.getSchema(tree.schemaName);
3072
+ if (!entry) {
3073
+ return [];
3074
+ }
3075
+ const typedTree = resolveFieldTree(tree, entry.schema);
3076
+ if (!typedTree) {
3077
+ return [];
3078
+ }
3079
+ return [...computeFieldTreeDiagnostics({
3080
+ fieldTree: typedTree,
3081
+ tsSource: state.source
3082
+ })];
3083
+ });
3084
+ return [...templateDiagnostics, ...fieldTreeDiagnostics];
3085
+ };
3086
+
3087
+ //#endregion
3088
+ //#region packages/lsp/src/errors.ts
3089
+ /** Error constructor helpers for concise error creation. */
3090
+ const lspErrors = {
3091
+ configLoadFailed: (message, cause) => ({
3092
+ code: "CONFIG_LOAD_FAILED",
3093
+ message,
3094
+ cause
3095
+ }),
3096
+ schemaLoadFailed: (schemaName, message, cause) => ({
3097
+ code: "SCHEMA_LOAD_FAILED",
3098
+ message: message ?? `Failed to load schema: ${schemaName}`,
3099
+ schemaName,
3100
+ cause
3101
+ }),
3102
+ schemaBuildFailed: (schemaName, message, cause) => ({
3103
+ code: "SCHEMA_BUILD_FAILED",
3104
+ message: message ?? `Failed to build schema: ${schemaName}`,
3105
+ schemaName,
3106
+ cause
3107
+ }),
3108
+ schemaNotConfigured: (schemaName) => ({
3109
+ code: "SCHEMA_NOT_CONFIGURED",
3110
+ message: `Schema "${schemaName}" is not configured in soda-gql.config`,
3111
+ schemaName
3112
+ }),
3113
+ parseFailed: (uri, message, cause) => ({
3114
+ code: "PARSE_FAILED",
3115
+ message: message ?? `Failed to parse: ${uri}`,
3116
+ uri,
3117
+ cause
3118
+ }),
3119
+ internalInvariant: (message, context, cause) => ({
3120
+ code: "INTERNAL_INVARIANT",
3121
+ message,
3122
+ context,
3123
+ cause
3124
+ }),
3125
+ swcResolutionFailed: (message) => ({
3126
+ code: "SWC_RESOLUTION_FAILED",
3127
+ message: message ?? "@swc/core not found. Install @soda-gql/builder (which provides @swc/core) in your project and restart the LSP server to enable template extraction."
3128
+ })
3129
+ };
3130
+
3131
+ //#endregion
3132
+ //#region packages/lsp/src/schema-resolver.ts
3133
+ /**
3134
+ * Schema resolver: maps schema names to GraphQLSchema objects.
3135
+ * @module
3136
+ */
3137
+ /** Wrap buildASTSchema (which throws) in a Result. */
3138
+ const safeBuildASTSchema = (schemaName, documentNode) => {
3139
+ try {
3140
+ return (0, neverthrow.ok)((0, graphql.buildASTSchema)(documentNode));
3141
+ } catch (e) {
3142
+ return (0, neverthrow.err)(lspErrors.schemaBuildFailed(schemaName, e instanceof Error ? e.message : String(e), e));
3143
+ }
3144
+ };
3145
+ const loadAndBuildSchema = (schemaName, schemaPaths) => {
3146
+ const documents = [];
3147
+ const files = [];
3148
+ for (const schemaPath of schemaPaths) {
3149
+ const resolvedPath = (0, node_path.resolve)(schemaPath);
3150
+ try {
3151
+ const content = (0, node_fs.readFileSync)(resolvedPath, "utf8");
3152
+ documents.push((0, graphql.parse)(content));
3153
+ files.push({
3154
+ filePath: resolvedPath,
3155
+ content
3156
+ });
3157
+ } catch (e) {
3158
+ return (0, neverthrow.err)(lspErrors.schemaLoadFailed(schemaName, e instanceof Error ? e.message : String(e)));
3159
+ }
3160
+ }
3161
+ const documentNode = (0, graphql.concatAST)(documents);
3162
+ const hash = (0, __soda_gql_tools_codegen.hashSchema)(documentNode);
3163
+ const buildResult = safeBuildASTSchema(schemaName, documentNode);
3164
+ if (buildResult.isErr()) {
3165
+ return (0, neverthrow.err)(buildResult.error);
3166
+ }
3167
+ return (0, neverthrow.ok)({
3168
+ name: schemaName,
3169
+ schema: buildResult.value,
3170
+ documentNode,
3171
+ hash,
3172
+ files
3173
+ });
3174
+ };
3175
+ /** Create a schema resolver from config. Loads all schemas eagerly. */
3176
+ const createSchemaResolver = (config) => {
3177
+ const cache = new Map();
3178
+ for (const [name, schemaConfig] of Object.entries(config.schemas)) {
3179
+ const result = loadAndBuildSchema(name, schemaConfig.schema);
3180
+ if (result.isErr()) {
3181
+ return (0, neverthrow.err)(result.error);
3182
+ }
3183
+ cache.set(name, result.value);
3184
+ }
3185
+ const resolver = {
3186
+ getSchema: (schemaName) => cache.get(schemaName),
3187
+ getSchemaNames: () => [...cache.keys()],
3188
+ reloadSchema: (schemaName) => {
3189
+ const schemaConfig = config.schemas[schemaName];
3190
+ if (!schemaConfig) {
3191
+ return (0, neverthrow.err)(lspErrors.schemaNotConfigured(schemaName));
3192
+ }
3193
+ const result = loadAndBuildSchema(schemaName, schemaConfig.schema);
3194
+ if (result.isErr()) {
3195
+ return (0, neverthrow.err)(result.error);
3196
+ }
3197
+ cache.set(schemaName, result.value);
3198
+ return (0, neverthrow.ok)(result.value);
3199
+ },
3200
+ reloadAll: () => {
3201
+ const errors = [];
3202
+ for (const [name, schemaConfig] of Object.entries(config.schemas)) {
3203
+ const result = loadAndBuildSchema(name, schemaConfig.schema);
3204
+ if (result.isErr()) {
3205
+ errors.push(result.error);
3206
+ } else {
3207
+ cache.set(name, result.value);
3208
+ }
3209
+ }
3210
+ return errors.length > 0 ? (0, neverthrow.err)(errors) : (0, neverthrow.ok)(undefined);
3211
+ }
3212
+ };
3213
+ return (0, neverthrow.ok)(resolver);
3214
+ };
3215
+
3216
+ //#endregion
3217
+ //#region packages/lsp/src/config-registry.ts
3218
+ /**
3219
+ * Config registry: maps document URIs to their nearest config context.
3220
+ * Supports multiple soda-gql configs in a monorepo workspace.
3221
+ * @module
3222
+ */
3223
+ const createConfigRegistry = (configPaths) => {
3224
+ const sortedPaths = [...configPaths].sort((a, b) => b.length - a.length);
3225
+ const contexts = new Map();
3226
+ for (const configPath of sortedPaths) {
3227
+ const configResult = (0, __soda_gql_config.loadConfig)(configPath);
3228
+ if (configResult.isErr()) {
3229
+ return (0, neverthrow.err)(lspErrors.configLoadFailed(`Failed to load config ${configPath}: ${configResult.error.message}`, configResult.error));
3230
+ }
3231
+ const config = configResult.value;
3232
+ const helper = (0, __soda_gql_builder.createGraphqlSystemIdentifyHelper)(config);
3233
+ const resolverResult = createSchemaResolver(config);
3234
+ if (resolverResult.isErr()) {
3235
+ return (0, neverthrow.err)(resolverResult.error);
3236
+ }
3237
+ contexts.set(configPath, {
3238
+ configPath,
3239
+ config,
3240
+ helper,
3241
+ schemaResolver: resolverResult.value,
3242
+ documentManager: createDocumentManager(helper, { resolveFrom: configPath })
3243
+ });
3244
+ }
3245
+ const uriCache = new Map();
3246
+ const resolveConfigPath = (dirPath) => {
3247
+ const cached = uriCache.get(dirPath);
3248
+ if (cached !== undefined) {
3249
+ return cached;
3250
+ }
3251
+ for (const configPath of sortedPaths) {
3252
+ const configDir = (0, node_path.dirname)(configPath);
3253
+ if (dirPath === configDir || dirPath.startsWith(`${configDir}${node_path.sep}`)) {
3254
+ uriCache.set(dirPath, configPath);
3255
+ return configPath;
3256
+ }
3257
+ }
3258
+ uriCache.set(dirPath, null);
3259
+ return null;
3260
+ };
3261
+ return (0, neverthrow.ok)({
3262
+ resolveForUri: (uri) => {
3263
+ if (!uri.startsWith("file://") && !uri.startsWith("/")) {
3264
+ return undefined;
3265
+ }
3266
+ const filePath = uri.startsWith("file://") ? (0, node_url.fileURLToPath)(uri) : uri;
3267
+ const dirPath = (0, node_path.dirname)(filePath);
3268
+ const configPath = resolveConfigPath(dirPath);
3269
+ return configPath ? contexts.get(configPath) : undefined;
3270
+ },
3271
+ getAllContexts: () => [...contexts.values()],
3272
+ reloadSchemas: (configPath) => {
3273
+ const ctx = contexts.get(configPath);
3274
+ if (!ctx) {
3275
+ return (0, neverthrow.err)([lspErrors.configLoadFailed(`Config not found: ${configPath}`)]);
3276
+ }
3277
+ return ctx.schemaResolver.reloadAll();
3278
+ },
3279
+ reloadAllSchemas: () => {
3280
+ const errors = [];
3281
+ for (const ctx of contexts.values()) {
3282
+ const result = ctx.schemaResolver.reloadAll();
3283
+ if (result.isErr()) {
3284
+ errors.push(...result.error);
3285
+ }
3286
+ }
3287
+ return errors.length > 0 ? (0, neverthrow.err)(errors) : (0, neverthrow.ok)(undefined);
3288
+ }
3289
+ });
3290
+ };
3291
+
2952
3292
  //#endregion
2953
3293
  //#region packages/lsp/src/handlers/code-action.ts
2954
3294
  /** Handle a code action request for a GraphQL template. */
@@ -3106,7 +3446,7 @@ const findStatementStart = (source, offset) => {
3106
3446
  const handleCompletion = (input) => {
3107
3447
  const { template, schema, tsSource, tsPosition } = input;
3108
3448
  const reconstructed = reconstructGraphql(template);
3109
- const headerLen = reconstructed.length - template.content.length;
3449
+ const headerLen = computeHeaderLen(template, reconstructed);
3110
3450
  const { preprocessed } = preprocessFragmentArgs(reconstructed);
3111
3451
  const mapper = createPositionMapper({
3112
3452
  tsSource,
@@ -3129,6 +3469,27 @@ const handleCompletion = (input) => {
3129
3469
  //#endregion
3130
3470
  //#region packages/lsp/src/handlers/_utils.ts
3131
3471
  /**
3472
+ * Shared handler utilities for fragment spread and definition lookup.
3473
+ * @module
3474
+ */
3475
+ /** Build ObjectTypeInfo[] from schema file info for graphql-language-service definition APIs. */
3476
+ const buildObjectTypeInfos = (files) => {
3477
+ const result = [];
3478
+ for (const file of files) {
3479
+ const doc = (0, graphql.parse)(file.content);
3480
+ for (const def of doc.definitions) {
3481
+ if ((0, graphql.isTypeDefinitionNode)(def)) {
3482
+ result.push({
3483
+ filePath: (0, node_url.pathToFileURL)(file.filePath).href,
3484
+ content: file.content,
3485
+ definition: def
3486
+ });
3487
+ }
3488
+ }
3489
+ }
3490
+ return result;
3491
+ };
3492
+ /**
3132
3493
  * Find the fragment spread node at the given GraphQL offset using AST.
3133
3494
  * Returns the FragmentSpreadNode if cursor is on a `...FragmentName` pattern.
3134
3495
  */
@@ -3258,35 +3619,41 @@ const computeSpreadLocationRanges = (spreadLocations) => {
3258
3619
  }
3259
3620
  return ranges;
3260
3621
  };
3261
-
3262
- //#endregion
3263
- //#region packages/lsp/src/handlers/definition.ts
3264
3622
  /**
3265
- * Definition handler: provides go-to-definition for fragment spreads and schema fields/types.
3266
- * @module
3623
+ * Resolve a directive name to its definition in schema files.
3624
+ * Parses each schema file and finds DirectiveDefinitionNode matching the name.
3267
3625
  */
3268
- /** Build ObjectTypeInfo[] from schema file info for graphql-language-service definition APIs. */
3269
- const buildObjectTypeInfos = (files) => {
3270
- const result = [];
3271
- for (const file of files) {
3272
- const doc = (0, graphql.parse)(file.content);
3273
- for (const def of doc.definitions) {
3274
- if ((0, graphql.isTypeDefinitionNode)(def)) {
3275
- result.push({
3276
- filePath: (0, node_url.pathToFileURL)(file.filePath).href,
3277
- content: file.content,
3278
- definition: def
3279
- });
3626
+ const resolveDirectiveDefinition = (directiveName, schemaFiles) => {
3627
+ const locations = [];
3628
+ for (const file of schemaFiles) {
3629
+ try {
3630
+ const doc = (0, graphql.parse)(file.content, { noLocation: false });
3631
+ const lineOffsets = computeLineOffsets(file.content);
3632
+ for (const def of doc.definitions) {
3633
+ if (def.kind === "DirectiveDefinition" && def.name.value === directiveName && def.name.loc) {
3634
+ const start = offsetToPosition(lineOffsets, def.name.loc.start);
3635
+ const end = offsetToPosition(lineOffsets, def.name.loc.end);
3636
+ locations.push({
3637
+ uri: (0, node_url.pathToFileURL)(file.filePath).href,
3638
+ range: {
3639
+ start,
3640
+ end
3641
+ }
3642
+ });
3643
+ }
3280
3644
  }
3281
- }
3645
+ } catch {}
3282
3646
  }
3283
- return result;
3647
+ return locations;
3284
3648
  };
3649
+
3650
+ //#endregion
3651
+ //#region packages/lsp/src/handlers/definition.ts
3285
3652
  /** Handle a definition request for a GraphQL template. */
3286
3653
  const handleDefinition = async (input) => {
3287
3654
  const { template, tsSource, tsPosition, externalFragments } = input;
3288
3655
  const reconstructed = reconstructGraphql(template);
3289
- const headerLen = reconstructed.length - template.content.length;
3656
+ const headerLen = computeHeaderLen(template, reconstructed);
3290
3657
  const { preprocessed } = preprocessFragmentArgs(reconstructed);
3291
3658
  const mapper = createPositionMapper({
3292
3659
  tsSource,
@@ -3304,10 +3671,16 @@ const handleDefinition = async (input) => {
3304
3671
  if (fragmentSpread) {
3305
3672
  return resolveFragmentSpreadDefinition(preprocessed, fragmentSpread, externalFragments);
3306
3673
  }
3674
+ if (input.schemaFiles && input.schemaFiles.length > 0) {
3675
+ const varTypeResult = await resolveVariableTypeDefinition(preprocessed, reconstructedOffset, input.schemaFiles);
3676
+ if (varTypeResult.length > 0) {
3677
+ return varTypeResult;
3678
+ }
3679
+ }
3307
3680
  if (input.schema && input.schemaFiles && input.schemaFiles.length > 0) {
3308
3681
  const reconstructedLineOffsets = computeLineOffsets(preprocessed);
3309
3682
  const reconstructedPosition = offsetToPosition(reconstructedLineOffsets, reconstructedOffset);
3310
- return resolveSchemaDefinition(preprocessed, reconstructedPosition, input.schema, input.schemaFiles);
3683
+ return resolveSchemaDefinition(preprocessed, reconstructedPosition, reconstructedOffset, input.schema, input.schemaFiles);
3311
3684
  }
3312
3685
  return [];
3313
3686
  };
@@ -3372,22 +3745,78 @@ const resolveFragmentSpreadDefinition = async (preprocessed, fragmentSpread, ext
3372
3745
  return [];
3373
3746
  }
3374
3747
  };
3748
+ /** Unwrap NonNullType/ListType wrappers to get the inner NamedType node. */
3749
+ const unwrapToNamedType = (typeNode) => {
3750
+ if (typeNode.kind === "NamedType") return typeNode;
3751
+ if (typeNode.kind === "NonNullType" || typeNode.kind === "ListType") {
3752
+ return unwrapToNamedType(typeNode.type);
3753
+ }
3754
+ return null;
3755
+ };
3756
+ /**
3757
+ * Resolve variable type reference to its definition in a schema file.
3758
+ * Parses the GraphQL source to find VariableDefinition nodes, checks if the
3759
+ * cursor offset falls within a type name, and resolves via getDefinitionQueryResultForNamedType.
3760
+ */
3761
+ const resolveVariableTypeDefinition = async (preprocessed, reconstructedOffset, schemaFiles) => {
3762
+ let ast;
3763
+ try {
3764
+ ast = (0, graphql.parse)(preprocessed, { noLocation: false });
3765
+ } catch {
3766
+ return [];
3767
+ }
3768
+ let matchedNode = null;
3769
+ (0, graphql.visit)(ast, { VariableDefinition(node) {
3770
+ if (matchedNode) return;
3771
+ const namedType = unwrapToNamedType(node.type);
3772
+ if (!namedType?.name.loc) return;
3773
+ const { start, end } = namedType.name.loc;
3774
+ if (reconstructedOffset >= start && reconstructedOffset < end) {
3775
+ matchedNode = namedType;
3776
+ }
3777
+ } });
3778
+ if (!matchedNode) return [];
3779
+ const objectTypeInfos = buildObjectTypeInfos(schemaFiles);
3780
+ try {
3781
+ const result = await (0, graphql_language_service.getDefinitionQueryResultForNamedType)(preprocessed, matchedNode, objectTypeInfos);
3782
+ return result.definitions.map((def) => ({
3783
+ uri: def.path ?? "",
3784
+ range: {
3785
+ start: {
3786
+ line: def.position.line,
3787
+ character: def.position.character
3788
+ },
3789
+ end: {
3790
+ line: def.range?.end?.line ?? def.position.line,
3791
+ character: def.range?.end?.character ?? def.position.character
3792
+ }
3793
+ }
3794
+ }));
3795
+ } catch {
3796
+ return [];
3797
+ }
3798
+ };
3375
3799
  /** Resolve field or type name to its definition in a schema .graphql file. */
3376
- const resolveSchemaDefinition = (preprocessed, position, schema, schemaFiles) => {
3800
+ const resolveSchemaDefinition = async (preprocessed, position, reconstructedOffset, schema, schemaFiles) => {
3801
+ const astResult = await tryResolveTypeConditionOrDirective(preprocessed, reconstructedOffset, schemaFiles);
3802
+ if (astResult.matched) {
3803
+ return astResult.locations;
3804
+ }
3377
3805
  const context = (0, graphql_language_service.getContextAtPosition)(preprocessed, toIPosition(position), schema);
3378
3806
  if (!context) {
3379
- return Promise.resolve([]);
3807
+ return [];
3380
3808
  }
3381
3809
  const { typeInfo } = context;
3382
3810
  if (typeInfo.fieldDef && typeInfo.parentType) {
3383
3811
  const fieldName = typeInfo.fieldDef.name;
3384
3812
  const namedParentType = (0, graphql.getNamedType)(typeInfo.parentType);
3385
3813
  if (!namedParentType) {
3386
- return Promise.resolve([]);
3814
+ return [];
3387
3815
  }
3388
3816
  const parentTypeName = namedParentType.name;
3389
3817
  const objectTypeInfos = buildObjectTypeInfos(schemaFiles);
3390
- return (0, graphql_language_service.getDefinitionQueryResultForField)(fieldName, parentTypeName, objectTypeInfos).then((result) => result.definitions.map((def) => ({
3818
+ const result = await (0, graphql_language_service.getDefinitionQueryResultForField)(fieldName, parentTypeName, objectTypeInfos);
3819
+ return result.definitions.map((def) => ({
3391
3820
  uri: def.path ?? "",
3392
3821
  range: {
3393
3822
  start: {
@@ -3399,63 +3828,99 @@ const resolveSchemaDefinition = (preprocessed, position, schema, schemaFiles) =>
3399
3828
  character: def.range?.end?.character ?? def.position.character
3400
3829
  }
3401
3830
  }
3402
- })));
3831
+ }));
3403
3832
  }
3404
- return Promise.resolve([]);
3833
+ return [];
3405
3834
  };
3406
-
3407
- //#endregion
3408
- //#region packages/lsp/src/handlers/diagnostics.ts
3409
- /** Compute LSP diagnostics for a single GraphQL template. */
3410
- const computeTemplateDiagnostics = (input) => {
3411
- const { template, schema, tsSource } = input;
3412
- const reconstructed = reconstructGraphql(template);
3413
- const headerLen = reconstructed.length - template.content.length;
3414
- const { preprocessed } = preprocessFragmentArgs(reconstructed);
3415
- const mapper = createPositionMapper({
3416
- tsSource,
3417
- contentStartOffset: template.contentRange.start,
3418
- graphqlContent: template.content
3419
- });
3420
- const gqlDiagnostics = (0, graphql_language_service.getDiagnostics)(preprocessed, schema, undefined, undefined, input.externalFragments);
3421
- const placeholderPattern = /__FRAG_SPREAD_\d+__/;
3422
- const reconstructedLineOffsets = computeLineOffsets(preprocessed);
3423
- const contentLineOffsets = computeLineOffsets(template.content);
3424
- const toContentPosition = (pos) => {
3425
- const offset = positionToOffset$1(reconstructedLineOffsets, pos);
3426
- const contentOffset = Math.max(0, offset - headerLen);
3427
- return offsetToPosition(contentLineOffsets, contentOffset);
3428
- };
3429
- return gqlDiagnostics.filter((diag) => {
3430
- if (placeholderPattern.test(diag.message)) {
3431
- return false;
3432
- }
3433
- const offset = positionToOffset$1(reconstructedLineOffsets, diag.range.start);
3434
- if (offset < headerLen) {
3435
- return false;
3835
+ /**
3836
+ * Try to resolve inline fragment type conditions and directive names via AST walking.
3837
+ * Returns { matched: true, locations } when cursor is on a type condition or directive,
3838
+ * or { matched: false } when cursor is on neither (caller should try field resolution).
3839
+ */
3840
+ const tryResolveTypeConditionOrDirective = async (preprocessed, reconstructedOffset, schemaFiles) => {
3841
+ let ast;
3842
+ try {
3843
+ ast = (0, graphql.parse)(preprocessed, { noLocation: false });
3844
+ } catch {
3845
+ return { matched: false };
3846
+ }
3847
+ let matchedTypeName = null;
3848
+ let matchedDirectiveName = null;
3849
+ (0, graphql.visit)(ast, {
3850
+ InlineFragment(node) {
3851
+ const loc = node.typeCondition?.name.loc;
3852
+ if (loc && reconstructedOffset >= loc.start && reconstructedOffset < loc.end) {
3853
+ matchedTypeName = node.typeCondition.name.value;
3854
+ }
3855
+ },
3856
+ FragmentDefinition(node) {
3857
+ const loc = node.typeCondition.name.loc;
3858
+ if (loc && reconstructedOffset >= loc.start && reconstructedOffset < loc.end) {
3859
+ matchedTypeName = node.typeCondition.name.value;
3860
+ }
3861
+ },
3862
+ Directive(node) {
3863
+ const loc = node.name.loc;
3864
+ if (loc && reconstructedOffset >= loc.start && reconstructedOffset < loc.end) {
3865
+ matchedDirectiveName = node.name.value;
3866
+ }
3436
3867
  }
3437
- return true;
3438
- }).map((diag) => {
3439
- const startContent = toContentPosition(diag.range.start);
3440
- const endContent = toContentPosition(diag.range.end);
3441
- const startTs = mapper.graphqlToTs(startContent);
3442
- const endTs = mapper.graphqlToTs(endContent);
3868
+ });
3869
+ if (matchedTypeName) {
3870
+ return {
3871
+ matched: true,
3872
+ locations: await resolveTypeNameToSchemaDefinition(matchedTypeName, schemaFiles)
3873
+ };
3874
+ }
3875
+ if (matchedDirectiveName) {
3443
3876
  return {
3877
+ matched: true,
3878
+ locations: resolveDirectiveDefinition(matchedDirectiveName, schemaFiles)
3879
+ };
3880
+ }
3881
+ return { matched: false };
3882
+ };
3883
+ /**
3884
+ * Resolve a type name to its definition in a schema file.
3885
+ * Used for fragment type names and inline fragment type conditions.
3886
+ */
3887
+ const resolveTypeNameToSchemaDefinition = async (typeName, schemaFiles) => {
3888
+ const typeNameLen = typeName.length;
3889
+ const dummyLoc = {
3890
+ start: 0,
3891
+ end: typeNameLen,
3892
+ startToken: null,
3893
+ endToken: null,
3894
+ source: null
3895
+ };
3896
+ const namedTypeNode = {
3897
+ kind: graphql.Kind.NAMED_TYPE,
3898
+ name: {
3899
+ kind: graphql.Kind.NAME,
3900
+ value: typeName,
3901
+ loc: dummyLoc
3902
+ },
3903
+ loc: dummyLoc
3904
+ };
3905
+ const objectTypeInfos = buildObjectTypeInfos(schemaFiles);
3906
+ try {
3907
+ const result = await (0, graphql_language_service.getDefinitionQueryResultForNamedType)("", namedTypeNode, objectTypeInfos);
3908
+ return result.definitions.map((def) => ({
3909
+ uri: def.path ?? "",
3444
3910
  range: {
3445
3911
  start: {
3446
- line: startTs.line,
3447
- character: startTs.character
3912
+ line: def.position.line,
3913
+ character: def.position.character
3448
3914
  },
3449
3915
  end: {
3450
- line: endTs.line,
3451
- character: endTs.character
3916
+ line: def.range?.end?.line ?? def.position.line,
3917
+ character: def.range?.end?.character ?? def.position.character
3452
3918
  }
3453
- },
3454
- message: diag.message,
3455
- severity: diag.severity,
3456
- source: "soda-gql"
3457
- };
3458
- });
3919
+ }
3920
+ }));
3921
+ } catch {
3922
+ return [];
3923
+ }
3459
3924
  };
3460
3925
 
3461
3926
  //#endregion
@@ -3523,7 +3988,7 @@ const handleDocumentSymbol = (input) => {
3523
3988
  const symbols = [];
3524
3989
  for (const template of templates) {
3525
3990
  const reconstructed = reconstructGraphql(template);
3526
- const headerLen = reconstructed.length - template.content.length;
3991
+ const headerLen = computeHeaderLen(template, reconstructed);
3527
3992
  const { preprocessed } = preprocessFragmentArgs(reconstructed);
3528
3993
  const outline = (0, graphql_language_service.getOutline)(preprocessed);
3529
3994
  if (!outline) {
@@ -3544,6 +4009,11 @@ const handleDocumentSymbol = (input) => {
3544
4009
  for (const tree of outline.outlineTrees) {
3545
4010
  const symbol = convertTree(tree, toContentPos, mapper);
3546
4011
  if (symbol) {
4012
+ if (template.kind === "fragment" && template.typeName) {
4013
+ symbol.detail = `on ${template.typeName}`;
4014
+ } else if (template.kind !== "fragment") {
4015
+ symbol.detail = template.kind;
4016
+ }
3547
4017
  symbols.push(symbol);
3548
4018
  }
3549
4019
  }
@@ -3551,6 +4021,129 @@ const handleDocumentSymbol = (input) => {
3551
4021
  return symbols;
3552
4022
  };
3553
4023
 
4024
+ //#endregion
4025
+ //#region packages/lsp/src/handlers/field-tree-completion.ts
4026
+ /** Handle a completion request for a callback builder field tree node. */
4027
+ const handleFieldTreeCompletion = (input) => {
4028
+ const { fieldTree, schema, tsSource, offset } = input;
4029
+ const result = findNodeAtOffset(fieldTree, offset);
4030
+ if (!result) return [];
4031
+ if (result.kind === "field") {
4032
+ const { node } = result;
4033
+ const prefix = tsSource.slice(node.fieldNameSpan.start, offset);
4034
+ const parentType = schema.getType(node.parentTypeName);
4035
+ if (!parentType || !(0, graphql.isObjectType)(parentType)) return [];
4036
+ const fields = parentType.getFields();
4037
+ const items = [];
4038
+ for (const [name, field] of Object.entries(fields)) {
4039
+ if (!name.startsWith(prefix)) continue;
4040
+ const namedType = (0, graphql.getNamedType)(field.type);
4041
+ items.push({
4042
+ label: name,
4043
+ kind: CompletionItemKind.Field,
4044
+ detail: namedType?.name
4045
+ });
4046
+ }
4047
+ return items;
4048
+ }
4049
+ if (result.kind === "unionMember") {
4050
+ const { parentNode } = result;
4051
+ const fieldType = parentNode.fieldTypeName ? schema.getType(parentNode.fieldTypeName) : null;
4052
+ if (!fieldType || !(fieldType instanceof graphql.GraphQLUnionType)) return [];
4053
+ const prefix = tsSource.slice(result.branch.typeNameSpan.start, offset);
4054
+ return fieldType.getTypes().filter((member) => member.name.startsWith(prefix)).map((member) => ({
4055
+ label: member.name,
4056
+ kind: CompletionItemKind.Class,
4057
+ detail: `member of ${parentNode.fieldTypeName}`
4058
+ }));
4059
+ }
4060
+ return [];
4061
+ };
4062
+
4063
+ //#endregion
4064
+ //#region packages/lsp/src/handlers/field-tree-definition.ts
4065
+ /** Handle a definition request for a callback builder field tree. */
4066
+ const handleFieldTreeDefinition = async (input) => {
4067
+ const { fieldTree, offset, schemaFiles } = input;
4068
+ const result = findNodeAtOffset(fieldTree, offset);
4069
+ if (!result) return [];
4070
+ const objectTypeInfos = buildObjectTypeInfos(schemaFiles);
4071
+ if (result.kind === "field") {
4072
+ const { node } = result;
4073
+ try {
4074
+ const defResult = await (0, graphql_language_service.getDefinitionQueryResultForField)(node.fieldName, node.parentTypeName, objectTypeInfos);
4075
+ return defResult.definitions.map((def) => ({
4076
+ uri: def.path ?? "",
4077
+ range: {
4078
+ start: {
4079
+ line: def.position.line,
4080
+ character: def.position.character
4081
+ },
4082
+ end: {
4083
+ line: def.range?.end?.line ?? def.position.line,
4084
+ character: def.range?.end?.character ?? def.position.character
4085
+ }
4086
+ }
4087
+ }));
4088
+ } catch {
4089
+ return [];
4090
+ }
4091
+ }
4092
+ if (result.kind === "unionMember") {
4093
+ const typeNameLen = result.branch.typeName.length;
4094
+ const dummyLoc = {
4095
+ start: 0,
4096
+ end: typeNameLen,
4097
+ startToken: null,
4098
+ endToken: null,
4099
+ source: null
4100
+ };
4101
+ const namedTypeNode = {
4102
+ kind: graphql.Kind.NAMED_TYPE,
4103
+ name: {
4104
+ kind: graphql.Kind.NAME,
4105
+ value: result.branch.typeName,
4106
+ loc: dummyLoc
4107
+ },
4108
+ loc: dummyLoc
4109
+ };
4110
+ try {
4111
+ const defResult = await (0, graphql_language_service.getDefinitionQueryResultForNamedType)("", namedTypeNode, objectTypeInfos);
4112
+ return defResult.definitions.map((def) => ({
4113
+ uri: def.path ?? "",
4114
+ range: {
4115
+ start: {
4116
+ line: def.position.line,
4117
+ character: def.position.character
4118
+ },
4119
+ end: {
4120
+ line: def.range?.end?.line ?? def.position.line,
4121
+ character: def.range?.end?.character ?? def.position.character
4122
+ }
4123
+ }
4124
+ }));
4125
+ } catch {
4126
+ return [];
4127
+ }
4128
+ }
4129
+ return [];
4130
+ };
4131
+
4132
+ //#endregion
4133
+ //#region packages/lsp/src/handlers/field-tree-hover.ts
4134
+ /** Handle a hover request for a callback builder field tree node. */
4135
+ const handleFieldTreeHover = (input) => {
4136
+ const { fieldTree, offset } = input;
4137
+ const result = findNodeAtOffset(fieldTree, offset);
4138
+ if (!result || result.kind !== "field") return null;
4139
+ const { node } = result;
4140
+ if (!node.fieldTypeName) return null;
4141
+ return { contents: {
4142
+ kind: "markdown",
4143
+ value: `${node.fieldName}: ${node.fieldTypeName}`
4144
+ } };
4145
+ };
4146
+
3554
4147
  //#endregion
3555
4148
  //#region packages/lsp/src/handlers/formatting.ts
3556
4149
  /**
@@ -3582,7 +4175,7 @@ const handleFormatting = (input) => {
3582
4175
  const handleHover = (input) => {
3583
4176
  const { template, schema, tsSource, tsPosition } = input;
3584
4177
  const reconstructed = reconstructGraphql(template);
3585
- const headerLen = reconstructed.length - template.content.length;
4178
+ const headerLen = computeHeaderLen(template, reconstructed);
3586
4179
  const { preprocessed } = preprocessFragmentArgs(reconstructed);
3587
4180
  const mapper = createPositionMapper({
3588
4181
  tsSource,
@@ -3746,6 +4339,115 @@ const handleRename = (input) => {
3746
4339
  * LSP server: wires all components together via vscode-languageserver.
3747
4340
  * @module
3748
4341
  */
4342
+ /**
4343
+ * Shared 3-phase dispatch: registry/ctx/doc guard → field-tree → template.
4344
+ * Used by completion, hover, and definition handlers.
4345
+ */
4346
+ const resolvePositionContext = (registry, documents, uri, position) => {
4347
+ if (!registry) {
4348
+ return undefined;
4349
+ }
4350
+ const ctx = registry.resolveForUri(uri);
4351
+ if (!ctx) {
4352
+ return undefined;
4353
+ }
4354
+ const doc = documents.get(uri);
4355
+ if (!doc) {
4356
+ return undefined;
4357
+ }
4358
+ const tsSource = doc.getText();
4359
+ const offset = positionToOffset(tsSource, position);
4360
+ const tsPosition = {
4361
+ line: position.line,
4362
+ character: position.character
4363
+ };
4364
+ const untypedTree = ctx.documentManager.findFieldTreeAtOffset(uri, offset);
4365
+ if (untypedTree) {
4366
+ const schemaEntry$1 = ctx.schemaResolver.getSchema(untypedTree.schemaName);
4367
+ if (schemaEntry$1) {
4368
+ const typedTree = resolveFieldTree(untypedTree, schemaEntry$1.schema);
4369
+ if (typedTree) {
4370
+ return {
4371
+ kind: "fieldTree",
4372
+ ctx,
4373
+ tsSource,
4374
+ offset,
4375
+ tsPosition,
4376
+ typedTree,
4377
+ schema: schemaEntry$1.schema,
4378
+ schemaEntry: schemaEntry$1
4379
+ };
4380
+ }
4381
+ }
4382
+ }
4383
+ const template = ctx.documentManager.findTemplateAtOffset(uri, offset);
4384
+ if (!template) {
4385
+ return undefined;
4386
+ }
4387
+ const schemaEntry = ctx.schemaResolver.getSchema(template.schemaName);
4388
+ return {
4389
+ kind: "template",
4390
+ ctx,
4391
+ tsSource,
4392
+ offset,
4393
+ tsPosition,
4394
+ template,
4395
+ schemaEntry
4396
+ };
4397
+ };
4398
+ /** Server capabilities shared between direct and proxy initialization. */
4399
+ const serverCapabilities = {
4400
+ textDocumentSync: vscode_languageserver_node.TextDocumentSyncKind.Full,
4401
+ hoverProvider: true,
4402
+ documentSymbolProvider: true,
4403
+ definitionProvider: true,
4404
+ referencesProvider: true,
4405
+ renameProvider: { prepareProvider: true },
4406
+ documentFormattingProvider: true,
4407
+ completionProvider: { triggerCharacters: [
4408
+ "{",
4409
+ "(",
4410
+ ":",
4411
+ "@",
4412
+ "$",
4413
+ " ",
4414
+ "\n",
4415
+ ".",
4416
+ "\""
4417
+ ] },
4418
+ codeActionProvider: { codeActionKinds: ["refactor.extract"] }
4419
+ };
4420
+ /** Initialize the LSP server from InitializeParams. Used by both direct and proxy modes. */
4421
+ const initializeFromParams = (params, connection) => {
4422
+ const roots = resolveWorkspaceRoots(params);
4423
+ if (roots.length === 0) {
4424
+ connection.window.showErrorMessage("soda-gql LSP: no workspace root provided");
4425
+ return {
4426
+ result: { capabilities: {} },
4427
+ registry: undefined
4428
+ };
4429
+ }
4430
+ const configPaths = discoverConfigs(roots);
4431
+ if (configPaths.length === 0) {
4432
+ connection.window.showErrorMessage("soda-gql LSP: no config file found");
4433
+ return {
4434
+ result: { capabilities: {} },
4435
+ registry: undefined
4436
+ };
4437
+ }
4438
+ const registryResult = createConfigRegistry(configPaths);
4439
+ if (registryResult.isErr()) {
4440
+ connection.window.showErrorMessage(`soda-gql LSP: ${registryResult.error.message}`);
4441
+ return {
4442
+ result: { capabilities: {} },
4443
+ registry: undefined
4444
+ };
4445
+ }
4446
+ return {
4447
+ result: { capabilities: serverCapabilities },
4448
+ registry: registryResult.value
4449
+ };
4450
+ };
3749
4451
  const createLspServer = (options) => {
3750
4452
  const connection = options?.connection ?? (0, vscode_languageserver_node.createConnection)(vscode_languageserver_node.ProposedFeatures.all);
3751
4453
  const documents = new vscode_languageserver_node.TextDocuments(vscode_languageserver_textdocument.TextDocument);
@@ -3771,22 +4473,10 @@ const createLspServer = (options) => {
3771
4473
  });
3772
4474
  return;
3773
4475
  }
3774
- const allDiagnostics = state.templates.flatMap((template) => {
3775
- const entry = ctx.schemaResolver.getSchema(template.schemaName);
3776
- if (!entry) {
3777
- return [];
3778
- }
3779
- const externalFragments = ctx.documentManager.getExternalFragments(uri, template.schemaName).map((f) => f.definition);
3780
- return [...computeTemplateDiagnostics({
3781
- template,
3782
- schema: entry.schema,
3783
- tsSource: state.source,
3784
- externalFragments
3785
- })];
3786
- });
4476
+ const diagnostics = collectRawDiagnostics(state, ctx);
3787
4477
  connection.sendDiagnostics({
3788
4478
  uri,
3789
- diagnostics: allDiagnostics
4479
+ diagnostics: [...diagnostics]
3790
4480
  });
3791
4481
  };
3792
4482
  const publishDiagnosticsForAllOpen = () => {
@@ -3794,46 +4484,20 @@ const createLspServer = (options) => {
3794
4484
  publishDiagnosticsForDocument(doc.uri);
3795
4485
  }
3796
4486
  };
3797
- connection.onInitialize((params) => {
3798
- const roots = resolveWorkspaceRoots(params);
3799
- if (roots.length === 0) {
3800
- connection.window.showErrorMessage("soda-gql LSP: no workspace root provided");
3801
- return { capabilities: {} };
3802
- }
3803
- const configPaths = discoverConfigs(roots);
3804
- if (configPaths.length === 0) {
3805
- connection.window.showErrorMessage("soda-gql LSP: no config file found");
3806
- return { capabilities: {} };
3807
- }
3808
- const registryResult = createConfigRegistry(configPaths);
3809
- if (registryResult.isErr()) {
3810
- connection.window.showErrorMessage(`soda-gql LSP: ${registryResult.error.message}`);
3811
- return { capabilities: {} };
3812
- }
3813
- registry = registryResult.value;
3814
- return { capabilities: {
3815
- textDocumentSync: vscode_languageserver_node.TextDocumentSyncKind.Full,
3816
- hoverProvider: true,
3817
- documentSymbolProvider: true,
3818
- definitionProvider: true,
3819
- referencesProvider: true,
3820
- renameProvider: { prepareProvider: true },
3821
- documentFormattingProvider: true,
3822
- completionProvider: { triggerCharacters: [
3823
- "{",
3824
- "(",
3825
- ":",
3826
- "@",
3827
- "$",
3828
- " ",
3829
- "\n",
3830
- "."
3831
- ] },
3832
- codeActionProvider: { codeActionKinds: ["refactor.extract"] }
3833
- } };
3834
- });
4487
+ let initializeResult;
4488
+ if (options?.initializeParams) {
4489
+ const init = initializeFromParams(options.initializeParams, connection);
4490
+ registry = init.registry;
4491
+ initializeResult = init.result;
4492
+ } else {
4493
+ connection.onInitialize((params) => {
4494
+ const init = initializeFromParams(params, connection);
4495
+ registry = init.registry;
4496
+ return init.result;
4497
+ });
4498
+ }
3835
4499
  connection.onInitialized(() => {
3836
- connection.client.register(vscode_languageserver_node.DidChangeWatchedFilesNotification.type, { watchers: [{ globPattern: "**/*.graphql" }] });
4500
+ connection.client.register(vscode_languageserver_node.DidChangeWatchedFilesNotification.type, { watchers: [{ globPattern: "**/*.graphql" }, { globPattern: "**/soda-gql.config.*" }] });
3837
4501
  });
3838
4502
  documents.onDidChangeContent((change) => {
3839
4503
  if (!registry) {
@@ -3861,95 +4525,97 @@ const createLspServer = (options) => {
3861
4525
  });
3862
4526
  });
3863
4527
  connection.onCompletion((params) => {
3864
- if (!registry) {
3865
- return [];
3866
- }
3867
- const ctx = registry.resolveForUri(params.textDocument.uri);
3868
- if (!ctx) {
3869
- return [];
3870
- }
3871
- const template = ctx.documentManager.findTemplateAtOffset(params.textDocument.uri, positionToOffset(documents.get(params.textDocument.uri)?.getText() ?? "", params.position));
3872
- if (!template) {
4528
+ const resolved = resolvePositionContext(registry, documents, params.textDocument.uri, params.position);
4529
+ if (!resolved) {
3873
4530
  return [];
3874
4531
  }
3875
- const entry = ctx.schemaResolver.getSchema(template.schemaName);
3876
- if (!entry) {
3877
- return [];
4532
+ if (resolved.kind === "fieldTree") {
4533
+ return handleFieldTreeCompletion({
4534
+ fieldTree: resolved.typedTree,
4535
+ schema: resolved.schema,
4536
+ tsSource: resolved.tsSource,
4537
+ tsPosition: resolved.tsPosition,
4538
+ offset: resolved.offset
4539
+ });
3878
4540
  }
3879
- const doc = documents.get(params.textDocument.uri);
3880
- if (!doc) {
4541
+ if (!resolved.schemaEntry) {
3881
4542
  return [];
3882
4543
  }
3883
- const externalFragments = ctx.documentManager.getExternalFragments(params.textDocument.uri, template.schemaName).map((f) => f.definition);
4544
+ const externalFragments = resolved.ctx.documentManager.getExternalFragments(params.textDocument.uri, resolved.template.schemaName).map((f) => f.definition);
3884
4545
  return handleCompletion({
3885
- template,
3886
- schema: entry.schema,
3887
- tsSource: doc.getText(),
3888
- tsPosition: {
3889
- line: params.position.line,
3890
- character: params.position.character
3891
- },
4546
+ template: resolved.template,
4547
+ schema: resolved.schemaEntry.schema,
4548
+ tsSource: resolved.tsSource,
4549
+ tsPosition: resolved.tsPosition,
3892
4550
  externalFragments
3893
4551
  });
3894
4552
  });
3895
4553
  connection.onHover((params) => {
3896
- if (!registry) {
3897
- return null;
3898
- }
3899
- const ctx = registry.resolveForUri(params.textDocument.uri);
3900
- if (!ctx) {
3901
- return null;
3902
- }
3903
- const doc = documents.get(params.textDocument.uri);
3904
- if (!doc) {
4554
+ const resolved = resolvePositionContext(registry, documents, params.textDocument.uri, params.position);
4555
+ if (!resolved) {
3905
4556
  return null;
3906
4557
  }
3907
- const template = ctx.documentManager.findTemplateAtOffset(params.textDocument.uri, positionToOffset(doc.getText(), params.position));
3908
- if (!template) {
3909
- return null;
4558
+ if (resolved.kind === "fieldTree") {
4559
+ return handleFieldTreeHover({
4560
+ fieldTree: resolved.typedTree,
4561
+ offset: resolved.offset
4562
+ });
3910
4563
  }
3911
- const entry = ctx.schemaResolver.getSchema(template.schemaName);
3912
- if (!entry) {
4564
+ if (!resolved.schemaEntry) {
3913
4565
  return null;
3914
4566
  }
3915
4567
  return handleHover({
3916
- template,
3917
- schema: entry.schema,
3918
- tsSource: doc.getText(),
3919
- tsPosition: {
3920
- line: params.position.line,
3921
- character: params.position.character
3922
- }
4568
+ template: resolved.template,
4569
+ schema: resolved.schemaEntry.schema,
4570
+ tsSource: resolved.tsSource,
4571
+ tsPosition: resolved.tsPosition
3923
4572
  });
3924
4573
  });
3925
4574
  connection.onDefinition(async (params) => {
3926
- if (!registry) {
3927
- return [];
3928
- }
3929
- const ctx = registry.resolveForUri(params.textDocument.uri);
3930
- if (!ctx) {
3931
- return [];
3932
- }
3933
- const doc = documents.get(params.textDocument.uri);
3934
- if (!doc) {
4575
+ const resolved = resolvePositionContext(registry, documents, params.textDocument.uri, params.position);
4576
+ if (!resolved) {
4577
+ if (!registry) {
4578
+ return [];
4579
+ }
4580
+ const ctx = registry.resolveForUri(params.textDocument.uri);
4581
+ if (!ctx) {
4582
+ return [];
4583
+ }
4584
+ const doc = documents.get(params.textDocument.uri);
4585
+ if (!doc) {
4586
+ return [];
4587
+ }
4588
+ const offset = positionToOffset(doc.getText(), params.position);
4589
+ const typeNameTemplate = ctx.documentManager.findTemplateByTypeNameOffset(params.textDocument.uri, offset);
4590
+ if (typeNameTemplate?.typeName) {
4591
+ const typeNameEntry = ctx.schemaResolver.getSchema(typeNameTemplate.schemaName);
4592
+ if (typeNameEntry?.files) {
4593
+ return resolveTypeNameToSchemaDefinition(typeNameTemplate.typeName, typeNameEntry.files);
4594
+ }
4595
+ }
3935
4596
  return [];
3936
4597
  }
3937
- const template = ctx.documentManager.findTemplateAtOffset(params.textDocument.uri, positionToOffset(doc.getText(), params.position));
3938
- if (!template) {
3939
- return [];
4598
+ if (resolved.kind === "fieldTree") {
4599
+ if (!resolved.schemaEntry.files) {
4600
+ return [];
4601
+ }
4602
+ return handleFieldTreeDefinition({
4603
+ fieldTree: resolved.typedTree,
4604
+ schema: resolved.schema,
4605
+ tsSource: resolved.tsSource,
4606
+ tsPosition: resolved.tsPosition,
4607
+ offset: resolved.offset,
4608
+ schemaFiles: resolved.schemaEntry.files
4609
+ });
3940
4610
  }
3941
- const externalFragments = ctx.documentManager.getExternalFragments(params.textDocument.uri, template.schemaName);
3942
- const entry = ctx.schemaResolver.getSchema(template.schemaName);
4611
+ const externalFragments = resolved.ctx.documentManager.getExternalFragments(params.textDocument.uri, resolved.template.schemaName);
3943
4612
  return handleDefinition({
3944
- template,
3945
- tsSource: doc.getText(),
3946
- tsPosition: {
3947
- line: params.position.line,
3948
- character: params.position.character
3949
- },
4613
+ template: resolved.template,
4614
+ tsSource: resolved.tsSource,
4615
+ tsPosition: resolved.tsPosition,
3950
4616
  externalFragments,
3951
- schema: entry?.schema,
3952
- schemaFiles: entry?.files
4617
+ schema: resolved.schemaEntry?.schema,
4618
+ schemaFiles: resolved.schemaEntry?.files
3953
4619
  });
3954
4620
  });
3955
4621
  connection.onReferences((params) => {
@@ -4117,11 +4783,18 @@ const createLspServer = (options) => {
4117
4783
  }
4118
4784
  }
4119
4785
  }
4786
+ const configChanged = _params.changes.some((change) => /soda-gql\.config\.[cm]?[jt]s$/.test(change.uri) && (change.type === vscode_languageserver_node.FileChangeType.Changed || change.type === vscode_languageserver_node.FileChangeType.Created));
4787
+ if (configChanged) {
4788
+ connection.window.showInformationMessage("soda-gql: config file changed. Restart the language server to apply.");
4789
+ }
4120
4790
  });
4121
4791
  documents.listen(connection);
4122
- return { start: () => {
4123
- connection.listen();
4124
- } };
4792
+ return {
4793
+ start: () => {
4794
+ connection.listen();
4795
+ },
4796
+ initializeResult
4797
+ };
4125
4798
  };
4126
4799
  /** Check if SWC is unavailable and show a one-time error notification. */
4127
4800
  const checkSwcUnavailable = (swcUnavailable, state, showError) => {
@@ -4178,6 +4851,24 @@ const positionToOffset = (source, position) => {
4178
4851
  };
4179
4852
 
4180
4853
  //#endregion
4854
+ Object.defineProperty(exports, '__toESM', {
4855
+ enumerable: true,
4856
+ get: function () {
4857
+ return __toESM;
4858
+ }
4859
+ });
4860
+ Object.defineProperty(exports, 'collectRawDiagnostics', {
4861
+ enumerable: true,
4862
+ get: function () {
4863
+ return collectRawDiagnostics;
4864
+ }
4865
+ });
4866
+ Object.defineProperty(exports, 'createConfigRegistry', {
4867
+ enumerable: true,
4868
+ get: function () {
4869
+ return createConfigRegistry;
4870
+ }
4871
+ });
4181
4872
  Object.defineProperty(exports, 'createDocumentManager', {
4182
4873
  enumerable: true,
4183
4874
  get: function () {
@@ -4214,4 +4905,4 @@ Object.defineProperty(exports, 'preprocessFragmentArgs', {
4214
4905
  return preprocessFragmentArgs;
4215
4906
  }
4216
4907
  });
4217
- //# sourceMappingURL=server-DsJ1bZ7i.cjs.map
4908
+ //# sourceMappingURL=server-C1MSX490.cjs.map