@nexus-ioc/language-service 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +46 -71
  2. package/dist/actions/get-completion-info.actions.js +4 -6
  3. package/dist/actions/get-completion-info.actions.js.map +1 -1
  4. package/dist/actions/get-semantic-diagnostics.actions.d.ts +13 -0
  5. package/dist/actions/get-semantic-diagnostics.actions.js +467 -48
  6. package/dist/actions/get-semantic-diagnostics.actions.js.map +1 -1
  7. package/dist/actions/go-to-dependency-definition.actions.d.ts +18 -0
  8. package/dist/actions/go-to-dependency-definition.actions.js +73 -0
  9. package/dist/actions/go-to-dependency-definition.actions.js.map +1 -0
  10. package/dist/index.d.ts +10 -0
  11. package/dist/index.js +37 -4
  12. package/dist/index.js.map +1 -1
  13. package/dist/language-service/ns-language-service.d.ts +6 -5
  14. package/dist/language-service/ns-language-service.js +8 -2
  15. package/dist/language-service/ns-language-service.js.map +1 -1
  16. package/dist/language-service/parse-config-service.js +1 -0
  17. package/dist/language-service/parse-config-service.js.map +1 -1
  18. package/dist/language-service/types.d.ts +0 -1
  19. package/dist/logger.js +1 -0
  20. package/dist/logger.js.map +1 -1
  21. package/package.json +19 -11
  22. package/dist/helpers/check-types.helper.d.ts +0 -3
  23. package/dist/helpers/check-types.helper.js +0 -30
  24. package/dist/helpers/check-types.helper.js.map +0 -1
  25. package/dist/helpers/compare-string-types.helper.d.ts +0 -4
  26. package/dist/helpers/compare-string-types.helper.js +0 -16
  27. package/dist/helpers/compare-string-types.helper.js.map +0 -1
  28. package/dist/helpers/find-node-at-position.helper.d.ts +0 -2
  29. package/dist/helpers/find-node-at-position.helper.js +0 -16
  30. package/dist/helpers/find-node-at-position.helper.js.map +0 -1
  31. package/dist/helpers/find-type-references.helper.d.ts +0 -8
  32. package/dist/helpers/find-type-references.helper.js +0 -51
  33. package/dist/helpers/find-type-references.helper.js.map +0 -1
  34. package/dist/helpers/get-type-of-node.helper.d.ts +0 -2
  35. package/dist/helpers/get-type-of-node.helper.js +0 -14
  36. package/dist/helpers/get-type-of-node.helper.js.map +0 -1
  37. package/dist/parsers/inject.parser.d.ts +0 -16
  38. package/dist/parsers/inject.parser.js +0 -44
  39. package/dist/parsers/inject.parser.js.map +0 -1
  40. package/dist/parsers/injectable.parser.d.ts +0 -5
  41. package/dist/parsers/injectable.parser.js +0 -13
  42. package/dist/parsers/injectable.parser.js.map +0 -1
  43. package/dist/parsers/ns-module.parser.d.ts +0 -45
  44. package/dist/parsers/ns-module.parser.js +0 -237
  45. package/dist/parsers/ns-module.parser.js.map +0 -1
  46. package/dist/parsers/ns-modules.parser.d.ts +0 -7
  47. package/dist/parsers/ns-modules.parser.js +0 -35
  48. package/dist/parsers/ns-modules.parser.js.map +0 -1
@@ -2,14 +2,21 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getSemanticDiagnosticsActions = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const type_checker_1 = require("@nexus-ioc/type-checker");
5
6
  const ts = tslib_1.__importStar(require("typescript/lib/tsserverlibrary"));
6
- const check_types_helper_1 = require("../helpers/check-types.helper");
7
- const compare_string_types_helper_1 = require("../helpers/compare-string-types.helper");
8
- const find_type_references_helper_1 = require("../helpers/find-type-references.helper");
9
- const inject_parser_1 = require("../parsers/inject.parser");
10
- const injectable_parser_1 = require("../parsers/injectable.parser");
11
- const ns_module_parser_1 = require("../parsers/ns-module.parser");
12
- const ns_modules_parser_1 = require("../parsers/ns-modules.parser");
7
+ /**
8
+ * Generates semantic diagnostics for Nexus IoC dependency injection
9
+ *
10
+ * Analyzes @Injectable classes and their @Inject decorators to detect:
11
+ * - Missing dependencies (not provided in any module)
12
+ * - Type mismatches between injected and provided types
13
+ * - Services not connected to any module
14
+ * - Circular dependencies
15
+ *
16
+ * @param fileName - The source file to analyze
17
+ * @param tsNsLs - The Nexus IoC Language Service instance
18
+ * @returns Array of TypeScript diagnostics including original diagnostics and DI-specific errors
19
+ */
13
20
  const getSemanticDiagnosticsActions = (fileName, tsNsLs) => {
14
21
  const sourceFile = tsNsLs.tsLS.getProgram()?.getSourceFile(fileName);
15
22
  const originalDiagnostic = tsNsLs.tsLS.getSemanticDiagnostics(fileName);
@@ -18,17 +25,174 @@ const getSemanticDiagnosticsActions = (fileName, tsNsLs) => {
18
25
  return originalDiagnostic;
19
26
  }
20
27
  const diagnostic = [];
21
- const injectableClasses = injectable_parser_1.InjectableParser.execute(sourceFile);
28
+ const injectableClasses = type_checker_1.InjectableParser.execute(sourceFile);
29
+ const childScopes = createChildScopeLookup(tsNsLs, typeChecker);
22
30
  if (!injectableClasses.length) {
23
31
  return originalDiagnostic;
24
32
  }
33
+ // Build a map of class names to their inject parameters for circular dependency detection
34
+ const paramsMap = new Map();
25
35
  for (const injectableClass of injectableClasses) {
26
- const params = inject_parser_1.InjectParser.execute(injectableClass, tsNsLs.logger);
27
- if (!params.length) {
36
+ const params = type_checker_1.InjectParser.execute(injectableClass, tsNsLs.logger);
37
+ const className = injectableClass.name?.text;
38
+ if (className && params.length > 0) {
39
+ paramsMap.set(className, params);
40
+ }
41
+ }
42
+ // Check for circular dependencies
43
+ const circularDetector = new type_checker_1.CircularDependencyDetectorHelper();
44
+ const circularAnalysis = circularDetector.detectCircularDependencies(injectableClasses, paramsMap);
45
+ // Add circular dependency diagnostics
46
+ for (const circular of circularAnalysis.circularDependencies) {
47
+ diagnostic.push({
48
+ file: sourceFile,
49
+ start: circular.circularParam.start,
50
+ length: circular.circularParam.length,
51
+ messageText: `${circular.message}. Consider using forwardRef() to resolve this circular dependency.`,
52
+ category: ts.DiagnosticCategory.Error,
53
+ code: 9998,
54
+ relatedInformation: [
55
+ {
56
+ category: ts.DiagnosticCategory.Suggestion,
57
+ code: 9998,
58
+ file: sourceFile,
59
+ start: circular.classDeclaration.getStart(),
60
+ length: circular.classDeclaration.getEnd() -
61
+ circular.classDeclaration.getStart(),
62
+ messageText: `Circular dependency path: ${circular.cycle.join(" -> ")}`,
63
+ },
64
+ ],
65
+ });
66
+ }
67
+ // Validate factory provider dependencies
68
+ const allModules = type_checker_1.ModulesParser.execute(sourceFile);
69
+ const parsedModules = type_checker_1.ModuleParser.execute(allModules, typeChecker, tsNsLs);
70
+ for (const module of parsedModules) {
71
+ for (const provider of module.providers) {
72
+ if (provider.provideType === "useFactory" && provider.inject) {
73
+ // Validate each dependency in the inject array
74
+ for (const injectToken of provider.inject) {
75
+ const tokenText = injectToken.getText();
76
+ let found = false;
77
+ // Check if the dependency exists in the same module
78
+ for (const moduleProvider of module.providers) {
79
+ if (ts.isStringLiteral(injectToken)) {
80
+ if (moduleProvider.provide
81
+ .getText()
82
+ .replaceAll('"', "")
83
+ .replaceAll("'", "") ===
84
+ tokenText.replaceAll('"', "").replaceAll("'", "")) {
85
+ found = true;
86
+ break;
87
+ }
88
+ }
89
+ else if (ts.isIdentifier(injectToken)) {
90
+ if ((0, type_checker_1.checkTypesHelper)(moduleProvider.declaration, injectToken, typeChecker, tsNsLs)) {
91
+ found = true;
92
+ break;
93
+ }
94
+ }
95
+ }
96
+ // Check in imported modules
97
+ if (!found) {
98
+ for (const importedModule of module.imports) {
99
+ if (ts.isIdentifier(importedModule.declaration)) {
100
+ const references = (0, type_checker_1.findTypeReferences)(importedModule.declaration, tsNsLs) || [];
101
+ for (const reference of references) {
102
+ const importedSourceFile = tsNsLs.tsLS
103
+ .getProgram()
104
+ ?.getSourceFile(reference.fileName);
105
+ if (!importedSourceFile)
106
+ continue;
107
+ const importedModules = type_checker_1.ModulesParser.executeByModuleName(importedSourceFile, importedModule.declaration, typeChecker, tsNsLs);
108
+ const importedNsModules = type_checker_1.ModuleParser.execute(importedModules, typeChecker, tsNsLs);
109
+ for (const importedNsModule of importedNsModules) {
110
+ for (const exportedProvider of importedNsModule.exports) {
111
+ if (ts.isStringLiteral(injectToken)) {
112
+ if (exportedProvider.name === tokenText) {
113
+ found = true;
114
+ break;
115
+ }
116
+ }
117
+ else if (ts.isIdentifier(injectToken)) {
118
+ if ((0, type_checker_1.checkTypesHelper)(exportedProvider.declaration, injectToken, typeChecker, tsNsLs)) {
119
+ found = true;
120
+ break;
121
+ }
122
+ }
123
+ }
124
+ if (found)
125
+ break;
126
+ }
127
+ if (found)
128
+ break;
129
+ }
130
+ if (found)
131
+ break;
132
+ }
133
+ }
134
+ }
135
+ // Check in global modules
136
+ if (!found) {
137
+ const globalModules = findGlobalModules(tsNsLs, typeChecker);
138
+ for (const globalModule of globalModules) {
139
+ for (const exportedProvider of globalModule.exports) {
140
+ if (ts.isStringLiteral(injectToken)) {
141
+ if (exportedProvider.name === tokenText) {
142
+ found = true;
143
+ break;
144
+ }
145
+ }
146
+ else if (ts.isIdentifier(injectToken)) {
147
+ if ((0, type_checker_1.checkTypesHelper)(exportedProvider.declaration, injectToken, typeChecker, tsNsLs)) {
148
+ found = true;
149
+ break;
150
+ }
151
+ }
152
+ }
153
+ if (found)
154
+ break;
155
+ }
156
+ }
157
+ // A module in a child scope falls back to the declaring module's visibility
158
+ if (!found) {
159
+ found =
160
+ findInChildScopeParents(module, injectToken, childScopes) !==
161
+ undefined;
162
+ }
163
+ if (!found) {
164
+ diagnostic.push({
165
+ file: sourceFile,
166
+ start: injectToken.getStart(),
167
+ length: injectToken.getEnd() - injectToken.getStart(),
168
+ messageText: `Factory provider dependency '${tokenText}' is not provided in module '${module.moduleName}'`,
169
+ category: ts.DiagnosticCategory.Error,
170
+ code: 9999,
171
+ relatedInformation: [
172
+ {
173
+ category: ts.DiagnosticCategory.Suggestion,
174
+ code: 9999,
175
+ file: sourceFile,
176
+ start: provider.start,
177
+ length: provider.length,
178
+ messageText: `Factory provider: ${provider.provide.getText()}`,
179
+ },
180
+ ],
181
+ });
182
+ }
183
+ }
184
+ }
185
+ }
186
+ }
187
+ // Process each injectable class for missing dependencies and type mismatches
188
+ for (const injectableClass of injectableClasses) {
189
+ const className = injectableClass.name?.text;
190
+ const params = className ? paramsMap.get(className) : undefined;
191
+ if (!params || !params.length) {
28
192
  continue;
29
193
  }
30
194
  tsNsLs.logger.log(`[NsLanguageServer][getSemanticDiagnostics][params] ${params.length}`);
31
- const references = (0, find_type_references_helper_1.findTypeReferences)(injectableClass, tsNsLs.tsLS, tsNsLs.logger);
195
+ const references = (0, type_checker_1.findTypeReferences)(injectableClass, tsNsLs);
32
196
  const referenceModules = [];
33
197
  for (const reference of references) {
34
198
  const sourceFileReference = tsNsLs.tsLS
@@ -37,8 +201,8 @@ const getSemanticDiagnosticsActions = (fileName, tsNsLs) => {
37
201
  if (!sourceFileReference) {
38
202
  continue;
39
203
  }
40
- const modules = ns_modules_parser_1.NsModulesParser.executeByClassDependency(sourceFileReference, injectableClass, tsNsLs.logger);
41
- const nsModule = ns_module_parser_1.NsModuleParser.execute(modules, typeChecker, tsNsLs.logger);
204
+ const modules = type_checker_1.ModulesParser.executeByClassDependency(sourceFileReference, injectableClass, tsNsLs);
205
+ const nsModule = type_checker_1.ModuleParser.execute(modules, typeChecker, tsNsLs);
42
206
  referenceModules.push(...nsModule);
43
207
  }
44
208
  for (const param of params) {
@@ -52,14 +216,14 @@ const getSemanticDiagnosticsActions = (fileName, tsNsLs) => {
52
216
  return false;
53
217
  }
54
218
  if (ts.isIdentifier(param.name)) {
55
- return (0, check_types_helper_1.checkTypesHelper)(provider.declaration, param.name, typeChecker, tsNsLs.logger);
219
+ return (0, type_checker_1.checkTypesHelper)(provider.declaration, param.name, typeChecker, tsNsLs);
56
220
  }
57
221
  return false;
58
222
  });
59
223
  if (!dependencyDeclare) {
60
- referenceModule.imports.map((module) => {
224
+ referenceModule.imports.forEach((module) => {
61
225
  if (ts.isIdentifier(module.declaration)) {
62
- const references = (0, find_type_references_helper_1.findTypeReferences)(module.declaration, tsNsLs.tsLS, tsNsLs.logger) || [];
226
+ const references = (0, type_checker_1.findTypeReferences)(module.declaration, tsNsLs) || [];
63
227
  if (!references.length) {
64
228
  return;
65
229
  }
@@ -67,15 +231,15 @@ const getSemanticDiagnosticsActions = (fileName, tsNsLs) => {
67
231
  const sourceFile = tsNsLs.tsLS
68
232
  .getProgram()
69
233
  ?.getSourceFile(reference.fileName);
70
- const modules = ns_modules_parser_1.NsModulesParser.executeByModuleName(sourceFile, module.declaration, typeChecker, tsNsLs.logger);
71
- const nsModules = ns_module_parser_1.NsModuleParser.execute(modules, typeChecker, tsNsLs.logger);
234
+ const modules = type_checker_1.ModulesParser.executeByModuleName(sourceFile, module.declaration, typeChecker, tsNsLs);
235
+ const nsModules = type_checker_1.ModuleParser.execute(modules, typeChecker, tsNsLs);
72
236
  for (const nsModule of nsModules) {
73
237
  dependencyDeclare = nsModule.exports.find((provider) => {
74
238
  if (ts.isStringLiteral(param.name)) {
75
239
  return provider.name === param.name.getText();
76
240
  }
77
241
  if (ts.isIdentifier(param.name)) {
78
- return (0, check_types_helper_1.checkTypesHelper)(provider.declaration, param.name, typeChecker, tsNsLs.logger);
242
+ return (0, type_checker_1.checkTypesHelper)(provider.declaration, param.name, typeChecker, tsNsLs);
79
243
  }
80
244
  return false;
81
245
  });
@@ -84,12 +248,35 @@ const getSemanticDiagnosticsActions = (fileName, tsNsLs) => {
84
248
  }
85
249
  });
86
250
  }
251
+ // Search global modules if dependency not found
87
252
  if (!dependencyDeclare) {
253
+ const globalModules = findGlobalModules(tsNsLs, typeChecker);
254
+ for (const globalModule of globalModules) {
255
+ dependencyDeclare = globalModule.exports.find((provider) => {
256
+ if (ts.isStringLiteral(param.name)) {
257
+ return provider.name === param.name.getText();
258
+ }
259
+ if (ts.isIdentifier(param.name)) {
260
+ return (0, type_checker_1.checkTypesHelper)(provider.declaration, param.name, typeChecker, tsNsLs);
261
+ }
262
+ return false;
263
+ });
264
+ if (dependencyDeclare) {
265
+ break;
266
+ }
267
+ }
268
+ }
269
+ // A module in a child scope falls back to the declaring module's visibility
270
+ if (!dependencyDeclare) {
271
+ dependencyDeclare = findInChildScopeParents(referenceModule, param.name, childScopes);
272
+ }
273
+ // Skip error reporting for optional dependencies
274
+ if (!dependencyDeclare && !param.isOptional) {
88
275
  diagnostic.push({
89
276
  file: sourceFile,
90
277
  start: param.start,
91
278
  length: param.length,
92
- messageText: `У класса ${injectableClass.name?.text} отсутствуют зависимости: ${param.name.getText()}`,
279
+ messageText: `Class '${injectableClass.name?.text}' is missing dependency: ${param.name.getText()}`,
93
280
  category: ts.DiagnosticCategory.Error,
94
281
  code: 9999,
95
282
  relatedInformation: [
@@ -99,55 +286,287 @@ const getSemanticDiagnosticsActions = (fileName, tsNsLs) => {
99
286
  file: referenceModule.sourceFile,
100
287
  start: referenceModule.start,
101
288
  length: referenceModule.length,
102
- messageText: `Модуль: ${referenceModule.moduleName}`,
289
+ messageText: `Module: ${referenceModule.moduleName}`,
103
290
  },
104
291
  ],
105
292
  });
106
293
  }
107
- if (dependencyDeclare?.provide && param.parameterType) {
108
- let isEqual;
109
- if (param.parameterType.kind === ts.SyntaxKind.StringKeyword) {
110
- isEqual = (0, compare_string_types_helper_1.compareStringTypes)(param.parameterType, dependencyDeclare.declaration, typeChecker);
294
+ // Perform type checking for found dependencies
295
+ // Skip type checking for optional dependencies - they can accept the provider type or undefined
296
+ if (dependencyDeclare && param.parameterType && !param.isOptional) {
297
+ let declarationNode;
298
+ // For ProviderType (has 'provide' property), use the declaration
299
+ if ("provide" in dependencyDeclare) {
300
+ declarationNode = dependencyDeclare.declaration;
111
301
  }
302
+ // For ExportType (no 'provide' property), use the declaration directly
112
303
  else {
113
- isEqual = (0, check_types_helper_1.checkTypesHelper)(param.parameterType, dependencyDeclare.declaration, typeChecker, tsNsLs.logger);
304
+ declarationNode = dependencyDeclare.declaration;
305
+ }
306
+ if (declarationNode) {
307
+ const isEqual = (0, type_checker_1.compareTypes)(param.parameterType, declarationNode, typeChecker);
308
+ if (!isEqual) {
309
+ diagnostic.push({
310
+ file: sourceFile,
311
+ start: param.start,
312
+ length: param.length,
313
+ messageText: `Type mismatch for dependency '${param.name.getText()}'`,
314
+ category: ts.DiagnosticCategory.Error,
315
+ code: 9999,
316
+ relatedInformation: [
317
+ {
318
+ category: ts.DiagnosticCategory.Suggestion,
319
+ code: 9999,
320
+ file: referenceModule.sourceFile,
321
+ start: referenceModule.start,
322
+ length: referenceModule.length,
323
+ messageText: `Module: ${referenceModule.moduleName}`,
324
+ },
325
+ ],
326
+ });
327
+ }
328
+ }
329
+ }
330
+ }
331
+ // Skip error reporting for optional dependencies in orphan services
332
+ if (!referenceModules.length && !param.isOptional) {
333
+ // Check global modules before reporting error for orphan services
334
+ let foundInGlobalModule = false;
335
+ let globalDependencyDeclare;
336
+ const globalModules = findGlobalModules(tsNsLs, typeChecker);
337
+ for (const globalModule of globalModules) {
338
+ const dependencyDeclare = globalModule.exports.find((provider) => {
339
+ if (ts.isStringLiteral(param.name)) {
340
+ return provider.name === param.name.getText();
341
+ }
342
+ if (ts.isIdentifier(param.name)) {
343
+ return (0, type_checker_1.checkTypesHelper)(provider.declaration, param.name, typeChecker, tsNsLs);
344
+ }
345
+ return false;
346
+ });
347
+ if (dependencyDeclare) {
348
+ foundInGlobalModule = true;
349
+ globalDependencyDeclare = dependencyDeclare;
350
+ break;
114
351
  }
352
+ }
353
+ // Perform type checking for dependencies found in global modules
354
+ // Skip type checking for optional dependencies - they can accept the provider type or undefined
355
+ if (globalDependencyDeclare &&
356
+ param.parameterType &&
357
+ !param.isOptional) {
358
+ const isEqual = (0, type_checker_1.compareTypes)(param.parameterType, globalDependencyDeclare.declaration, typeChecker);
115
359
  if (!isEqual) {
116
360
  diagnostic.push({
117
361
  file: sourceFile,
118
362
  start: param.start,
119
363
  length: param.length,
120
- messageText: `У зависимости ${param.name.getText()} не совпадают типы`,
364
+ messageText: `Type mismatch for dependency '${param.name.getText()}' (found in global module)`,
121
365
  category: ts.DiagnosticCategory.Error,
122
366
  code: 9999,
123
- relatedInformation: [
124
- {
125
- category: ts.DiagnosticCategory.Suggestion,
126
- code: 9999,
127
- file: referenceModule.sourceFile,
128
- start: referenceModule.start,
129
- length: referenceModule.length,
130
- messageText: `Модуль: ${referenceModule.moduleName}`,
131
- },
132
- ],
133
367
  });
134
368
  }
135
369
  }
136
- }
137
- if (!referenceModules.length) {
138
- // кейс когда мы указали зависимости в классе, но он не связан ни с одним модулем
139
- diagnostic.push({
140
- file: sourceFile,
141
- start: param.start,
142
- length: param.length,
143
- messageText: `У класса ${injectableClass.name?.text} отсутствуют зависимости: ${param.name.getText()}`,
144
- category: ts.DiagnosticCategory.Error,
145
- code: 9999,
146
- });
370
+ // Only report error if not found in global modules
371
+ if (!foundInGlobalModule) {
372
+ // Case when we specified dependencies in the class, but it is not connected to any module
373
+ diagnostic.push({
374
+ file: sourceFile,
375
+ start: param.start,
376
+ length: param.length,
377
+ messageText: `Class '${injectableClass.name?.text}' is missing dependency: ${param.name.getText()}`,
378
+ category: ts.DiagnosticCategory.Error,
379
+ code: 9999,
380
+ });
381
+ }
147
382
  }
148
383
  }
149
384
  }
150
385
  return [...originalDiagnostic, ...diagnostic];
151
386
  };
152
387
  exports.getSemanticDiagnosticsActions = getSemanticDiagnosticsActions;
388
+ /** Every `@Module` declared in the program's own (non-declaration,
389
+ * non-`node_modules`) source files. */
390
+ function findProjectModules(tsNsLs, typeChecker) {
391
+ const program = tsNsLs.tsLS.getProgram();
392
+ if (!program) {
393
+ return [];
394
+ }
395
+ const result = [];
396
+ for (const sourceFile of program.getSourceFiles()) {
397
+ if (sourceFile.isDeclarationFile ||
398
+ sourceFile.fileName.includes("node_modules")) {
399
+ continue;
400
+ }
401
+ const modules = type_checker_1.ModulesParser.execute(sourceFile);
402
+ result.push(...type_checker_1.ModuleParser.execute(modules, typeChecker, tsNsLs));
403
+ }
404
+ return result;
405
+ }
406
+ /** Whether `identifier` names the class `module` was parsed from. */
407
+ function refersToModule(identifier, module, typeChecker) {
408
+ let symbol = typeChecker.getSymbolAtLocation(identifier);
409
+ if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
410
+ symbol = typeChecker.getAliasedSymbol(symbol);
411
+ }
412
+ return (symbol?.declarations?.some((declaration) => declaration.getSourceFile().fileName === module.sourceFile.fileName &&
413
+ declaration.getStart() === module.start) ?? false);
414
+ }
415
+ function matchesToken(token, candidate, typeChecker, tsNsLs) {
416
+ if (ts.isStringLiteral(token)) {
417
+ const text = token.text;
418
+ if ("provide" in candidate) {
419
+ return (candidate.provideType !== "class" &&
420
+ candidate.provide.getText().replaceAll('"', "").replaceAll("'", "") ===
421
+ text);
422
+ }
423
+ return candidate.name?.replaceAll('"', "").replaceAll("'", "") === text;
424
+ }
425
+ return (ts.isIdentifier(token) &&
426
+ (0, type_checker_1.checkTypesHelper)(candidate.declaration, token, typeChecker, tsNsLs));
427
+ }
428
+ /** `token` among `module`'s own providers or its imports' exports. */
429
+ function findInModuleVisibility(module, token, projectModules, tsNsLs, typeChecker) {
430
+ const own = module.providers.find((provider) => matchesToken(token, provider, typeChecker, tsNsLs));
431
+ if (own)
432
+ return own;
433
+ for (const importEntry of module.imports) {
434
+ if (!ts.isIdentifier(importEntry.declaration))
435
+ continue;
436
+ const declaration = importEntry.declaration;
437
+ for (const importedModule of projectModules) {
438
+ if (!refersToModule(declaration, importedModule, typeChecker))
439
+ continue;
440
+ const exported = importedModule.exports.find((entry) => matchesToken(token, entry, typeChecker, tsNsLs));
441
+ if (exported)
442
+ return exported;
443
+ }
444
+ }
445
+ return undefined;
446
+ }
447
+ function createChildScopeLookup(tsNsLs, typeChecker) {
448
+ let modules;
449
+ return {
450
+ tsNsLs,
451
+ typeChecker,
452
+ modules: () => {
453
+ modules ??= findProjectModules(tsNsLs, typeChecker);
454
+ return modules;
455
+ },
456
+ };
457
+ }
458
+ function moduleKey(module) {
459
+ return `${module.sourceFile.fileName}:${module.start}`;
460
+ }
461
+ /** Modules whose `@Module({ children })` lists `module`. */
462
+ function findDeclaringModules(module, lookup) {
463
+ return lookup
464
+ .modules()
465
+ .filter((candidate) => candidate.children.some((child) => refersToModule(child.declaration, module, lookup.typeChecker)));
466
+ }
467
+ /**
468
+ * The declared child roots whose module tree `module` belongs to: `module`
469
+ * itself if some module lists it in `children`, plus the child roots of
470
+ * every module that imports it. `undefined` when `module` is also reachable
471
+ * outside any child scope: it has no importer and is not a declared child
472
+ * (a top-level module such as the app root), or one of its importers is in
473
+ * that situation.
474
+ *
475
+ * Known limitation: the legacy language service has no application entry
476
+ * point, so "reachable outside a child scope" means "imported (directly or
477
+ * transitively) by some module that is not itself inside a child scope",
478
+ * even if that importer is never reached from the real root. In that case
479
+ * the fallback is not applied and the diagnostic is kept, never hidden.
480
+ */
481
+ function findChildScopeRoots(module, lookup, visiting = new Set()) {
482
+ const key = moduleKey(module);
483
+ if (visiting.has(key))
484
+ return [];
485
+ visiting.add(key);
486
+ const isDeclaredChild = findDeclaringModules(module, lookup).length > 0;
487
+ const importers = lookup
488
+ .modules()
489
+ .filter((candidate) => candidate.imports.some((entry) => ts.isIdentifier(entry.declaration) &&
490
+ refersToModule(entry.declaration, module, lookup.typeChecker)));
491
+ if (!isDeclaredChild && importers.length === 0)
492
+ return undefined;
493
+ const roots = isDeclaredChild ? [module] : [];
494
+ for (const importer of importers) {
495
+ const importerRoots = findChildScopeRoots(importer, lookup, visiting);
496
+ if (importerRoots === undefined)
497
+ return undefined;
498
+ roots.push(...importerRoots);
499
+ }
500
+ return roots.length > 0 ? roots : undefined;
501
+ }
502
+ /**
503
+ * A module inside a child scope (a module listed in some `@Module({ children
504
+ * })`, or any module reachable from one through imports) falls back to
505
+ * what the declaring module sees in the parent: its own providers and its
506
+ * imports' exports (global modules are already searched by the caller),
507
+ * recursing up the chain of declaring modules. The token must be found for
508
+ * every child scope the module belongs to. Returns `undefined` for a module
509
+ * that is not (only) inside child scopes, so diagnostics for ordinary
510
+ * modules are unchanged.
511
+ */
512
+ function findInChildScopeParents(module, token, lookup, visitedRoots = new Set()) {
513
+ const roots = findChildScopeRoots(module, lookup);
514
+ if (!roots)
515
+ return undefined;
516
+ let found;
517
+ const seen = new Set();
518
+ for (const root of roots) {
519
+ const key = moduleKey(root);
520
+ if (seen.has(key))
521
+ continue;
522
+ seen.add(key);
523
+ if (visitedRoots.has(key))
524
+ return undefined;
525
+ const path = new Set(visitedRoots).add(key);
526
+ let rootFound;
527
+ for (const declaringModule of findDeclaringModules(root, lookup)) {
528
+ rootFound =
529
+ findInModuleVisibility(declaringModule, token, lookup.modules(), lookup.tsNsLs, lookup.typeChecker) ?? findInChildScopeParents(declaringModule, token, lookup, path);
530
+ if (rootFound)
531
+ break;
532
+ }
533
+ if (!rootFound)
534
+ return undefined;
535
+ found ??= rootFound;
536
+ }
537
+ return found;
538
+ }
539
+ /**
540
+ * Finds all global modules in the project
541
+ *
542
+ * Searches through all source files in the program to find modules
543
+ * decorated with @Global() that export providers.
544
+ *
545
+ * @param tsNsLs - The Nexus IoC Language Service instance
546
+ * @param typeChecker - TypeScript type checker for type analysis
547
+ * @returns Array of global module declarations
548
+ */
549
+ function findGlobalModules(tsNsLs, typeChecker) {
550
+ const program = tsNsLs.tsLS.getProgram();
551
+ if (!program) {
552
+ return [];
553
+ }
554
+ const globalModules = [];
555
+ const sourceFiles = program.getSourceFiles();
556
+ for (const sourceFile of sourceFiles) {
557
+ // Skip declaration files and node_modules
558
+ if (sourceFile.isDeclarationFile ||
559
+ sourceFile.fileName.includes("node_modules")) {
560
+ continue;
561
+ }
562
+ const modules = type_checker_1.ModulesParser.execute(sourceFile);
563
+ const nsModules = type_checker_1.ModuleParser.execute(modules, typeChecker, tsNsLs);
564
+ for (const nsModule of nsModules) {
565
+ if (nsModule.isGlobal && nsModule.exports.length > 0) {
566
+ globalModules.push(nsModule);
567
+ }
568
+ }
569
+ }
570
+ return globalModules;
571
+ }
153
572
  //# sourceMappingURL=get-semantic-diagnostics.actions.js.map