@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.
- package/README.md +46 -71
- package/dist/actions/get-completion-info.actions.js +4 -6
- package/dist/actions/get-completion-info.actions.js.map +1 -1
- package/dist/actions/get-semantic-diagnostics.actions.d.ts +13 -0
- package/dist/actions/get-semantic-diagnostics.actions.js +467 -48
- package/dist/actions/get-semantic-diagnostics.actions.js.map +1 -1
- package/dist/actions/go-to-dependency-definition.actions.d.ts +18 -0
- package/dist/actions/go-to-dependency-definition.actions.js +73 -0
- package/dist/actions/go-to-dependency-definition.actions.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +37 -4
- package/dist/index.js.map +1 -1
- package/dist/language-service/ns-language-service.d.ts +6 -5
- package/dist/language-service/ns-language-service.js +8 -2
- package/dist/language-service/ns-language-service.js.map +1 -1
- package/dist/language-service/parse-config-service.js +1 -0
- package/dist/language-service/parse-config-service.js.map +1 -1
- package/dist/language-service/types.d.ts +0 -1
- package/dist/logger.js +1 -0
- package/dist/logger.js.map +1 -1
- package/package.json +19 -11
- package/dist/helpers/check-types.helper.d.ts +0 -3
- package/dist/helpers/check-types.helper.js +0 -30
- package/dist/helpers/check-types.helper.js.map +0 -1
- package/dist/helpers/compare-string-types.helper.d.ts +0 -4
- package/dist/helpers/compare-string-types.helper.js +0 -16
- package/dist/helpers/compare-string-types.helper.js.map +0 -1
- package/dist/helpers/find-node-at-position.helper.d.ts +0 -2
- package/dist/helpers/find-node-at-position.helper.js +0 -16
- package/dist/helpers/find-node-at-position.helper.js.map +0 -1
- package/dist/helpers/find-type-references.helper.d.ts +0 -8
- package/dist/helpers/find-type-references.helper.js +0 -51
- package/dist/helpers/find-type-references.helper.js.map +0 -1
- package/dist/helpers/get-type-of-node.helper.d.ts +0 -2
- package/dist/helpers/get-type-of-node.helper.js +0 -14
- package/dist/helpers/get-type-of-node.helper.js.map +0 -1
- package/dist/parsers/inject.parser.d.ts +0 -16
- package/dist/parsers/inject.parser.js +0 -44
- package/dist/parsers/inject.parser.js.map +0 -1
- package/dist/parsers/injectable.parser.d.ts +0 -5
- package/dist/parsers/injectable.parser.js +0 -13
- package/dist/parsers/injectable.parser.js.map +0 -1
- package/dist/parsers/ns-module.parser.d.ts +0 -45
- package/dist/parsers/ns-module.parser.js +0 -237
- package/dist/parsers/ns-module.parser.js.map +0 -1
- package/dist/parsers/ns-modules.parser.d.ts +0 -7
- package/dist/parsers/ns-modules.parser.js +0 -35
- 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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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 =
|
|
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 =
|
|
27
|
-
|
|
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,
|
|
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 =
|
|
41
|
-
const nsModule =
|
|
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,
|
|
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.
|
|
224
|
+
referenceModule.imports.forEach((module) => {
|
|
61
225
|
if (ts.isIdentifier(module.declaration)) {
|
|
62
|
-
const references = (0,
|
|
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 =
|
|
71
|
-
const nsModules =
|
|
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,
|
|
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:
|
|
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:
|
|
289
|
+
messageText: `Module: ${referenceModule.moduleName}`,
|
|
103
290
|
},
|
|
104
291
|
],
|
|
105
292
|
});
|
|
106
293
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|