1
1
package haxeLanguageServer .features .haxe ;
2
2
3
3
import haxe .Json ;
4
+ import haxe .display .Diagnostic ;
5
+ import haxe .display .Display .DiagnosticsParams ;
6
+ import haxe .display .Display .DisplayMethods ;
4
7
import haxe .display .JsonModuleTypes ;
5
8
import haxe .ds .BalancedTree ;
6
9
import haxe .io .Path ;
@@ -13,10 +16,10 @@ import js.Node.setImmediate;
13
16
import js .node .ChildProcess ;
14
17
import jsonrpc .CancellationToken ;
15
18
import languageServerProtocol .Types .Diagnostic ;
16
- import languageServerProtocol .Types .DiagnosticSeverity ;
17
19
import languageServerProtocol .Types .Location ;
18
20
19
21
using Lambda ;
22
+ using haxeLanguageServer .features .haxe .DiagnosticsFeature ;
20
23
21
24
class DiagnosticsFeature {
22
25
public static inline final SortImportsUsingsTitle = " Sort imports/usings" ;
@@ -30,6 +33,9 @@ class DiagnosticsFeature {
30
33
final pendingRequests : Map <DocumentUri , CancellationTokenSource >;
31
34
final errorUri : DocumentUri ;
32
35
36
+ final useJsonRpc : Bool ;
37
+ final timerName : String ;
38
+
33
39
var haxelibPath : Null <FsPath >;
34
40
35
41
public function new (context : Context ) {
@@ -38,23 +44,56 @@ class DiagnosticsFeature {
38
44
pendingRequests = new Map ();
39
45
errorUri = new FsPath (Path .join ([context .workspacePath .toString (), " Error" ])).toUri ();
40
46
47
+ useJsonRpc = context .haxeServer .supports (DisplayMethods . Diagnostics );
48
+ timerName = useJsonRpc ? DisplayMethods . Diagnostics : " @diagnostics" ;
49
+
41
50
ChildProcess .exec (context .config .haxelib .executable + " config" , (error , stdout , stderr ) -> haxelibPath = new FsPath (stdout .trim ()));
42
51
43
52
context .languageServerProtocol .onNotification (LanguageServerMethods . RunGlobalDiagnostics , onRunGlobalDiagnostics );
44
53
}
45
54
46
55
function onRunGlobalDiagnostics (_ ) {
47
56
final stopProgress = context .startProgress (" Collecting Diagnostics" );
48
- final onResolve = context .startTimer (" @diagnostics" );
49
-
50
- context .callDisplay (" global diagnostics" , [" diagnostics" ], null , null , function (result ) {
51
- processDiagnosticsReply (null , onResolve , result );
52
- context .languageServerProtocol .sendNotification (LanguageServerMethods . DidRunRunGlobalDiagnostics );
53
- stopProgress ();
54
- }, function (error ) {
55
- processErrorReply (null , error );
56
- stopProgress ();
57
- });
57
+ final onResolve = context .startTimer (timerName );
58
+
59
+ if (useJsonRpc ) {
60
+ context .callHaxeMethod (DisplayMethods . Diagnostics , {}, null , result -> {
61
+ processDiagnosticsReply (null , onResolve , result );
62
+ context .languageServerProtocol .sendNotification (LanguageServerMethods . DidRunRunGlobalDiagnostics );
63
+ stopProgress ();
64
+ return null ;
65
+ }, function (error ) {
66
+ processErrorReply (null , error );
67
+ stopProgress ();
68
+ });
69
+ } else {
70
+ context .callDisplay (" global diagnostics" , [" diagnostics" ], null , null , function (result ) {
71
+ final data = parseLegacyDiagnostics (result );
72
+ if (data == null ) {
73
+ clearDiagnosticsOnClient (errorUri );
74
+ } else {
75
+ processDiagnosticsReply (null , onResolve , data );
76
+ }
77
+ context .languageServerProtocol .sendNotification (LanguageServerMethods . DidRunRunGlobalDiagnostics );
78
+ stopProgress ();
79
+ }, function (error ) {
80
+ processErrorReply (null , error );
81
+ stopProgress ();
82
+ });
83
+ }
84
+ }
85
+
86
+ function parseLegacyDiagnostics (result : DisplayResult ): Null <ReadOnlyArray <{file : haxe.display. FsPath , diagnostics : ReadOnlyArray <haxe.display. Diagnostic <Any >>}>> {
87
+ return switch result {
88
+ case DResult (s ):
89
+ try {
90
+ Json .parse (s );
91
+ } catch (e ) {
92
+ trace (" Error parsing diagnostics response: " + e );
93
+ null ;
94
+ }
95
+ case DCancelled : null ;
96
+ };
58
97
}
59
98
60
99
function processErrorReply (uri : Null <DocumentUri >, error : String ) {
@@ -108,7 +147,7 @@ class DiagnosticsFeature {
108
147
109
148
final diag = {
110
149
range : {start : position , end : endPosition },
111
- severity : DiagnosticSeverity . Error ,
150
+ severity : languageServerProtocol. Types . DiagnosticSeverity. Error ,
112
151
message : problemMatcher .matched (7 )
113
152
};
114
153
publishDiagnostic (targetUri , diag , error );
@@ -122,7 +161,7 @@ class DiagnosticsFeature {
122
161
}
123
162
final diag = {
124
163
range : {start : {line : 0 , character : 0 }, end : {line : 0 , character : 0 }},
125
- severity : DiagnosticSeverity . Error ,
164
+ severity : languageServerProtocol. Types . DiagnosticSeverity. Error ,
126
165
message : problemMatcher .matched (2 )
127
166
};
128
167
publishDiagnostic (errorUri , diag , error );
@@ -132,22 +171,12 @@ class DiagnosticsFeature {
132
171
function publishDiagnostic (uri : DocumentUri , diag : Diagnostic , error : String ) {
133
172
context .languageServerProtocol .sendNotification (PublishDiagnosticsNotification .type , {uri : uri , diagnostics : [diag ]});
134
173
final argumentsMap = diagnosticsArguments [uri ] = new DiagnosticsMap ();
135
- argumentsMap .set ({code : CompilerError , range : diag .range }, error );
174
+ argumentsMap .set ({code : DKCompilerError , range : diag .range }, error );
136
175
}
137
176
138
- function processDiagnosticsReply (uri : Null <DocumentUri >, onResolve : (result : Dynamic , ? debugInfo : String ) -> Void , result : DisplayResult ) {
177
+ function processDiagnosticsReply (uri : Null <DocumentUri >, onResolve : (result : Dynamic , ? debugInfo : String ) -> Void ,
178
+ data : ReadOnlyArray <{file : haxe.display. FsPath , diagnostics : ReadOnlyArray <haxe.display. Diagnostic <Any >>}>) {
139
179
clearDiagnosticsOnClient (errorUri );
140
- final data : Array <HaxeDiagnosticResponse <Any >> = switch result {
141
- case DResult (s ):
142
- try {
143
- Json .parse (s );
144
- } catch (e ) {
145
- trace (" Error parsing diagnostics response: " + e );
146
- return ;
147
- }
148
- case DCancelled :
149
- return ;
150
- }
151
180
var count = 0 ;
152
181
final sent = new Map <DocumentUri , Bool >();
153
182
for (data in data ) {
@@ -181,7 +210,7 @@ class DiagnosticsFeature {
181
210
final diag : Diagnostic = {
182
211
range : range ,
183
212
code : hxDiag .code ,
184
- severity : hxDiag .severity ,
213
+ severity : cast hxDiag .severity ,
185
214
message : hxDiag .kind .getMessage (doc , hxDiag .args , range ),
186
215
data : {kind : hxDiag .kind },
187
216
relatedInformation : hxDiag .relatedInformation ?. map (rel -> {
@@ -192,7 +221,7 @@ class DiagnosticsFeature {
192
221
message : convertIndentation (rel .message , rel .depth )
193
222
})
194
223
}
195
- if (kind == ReplaceableCode || kind == UnusedImport || diag .message .contains (" has no effect" ) || kind == InactiveBlock ) {
224
+ if (kind == ReplaceableCode || kind == DKUnusedImport || diag .message .contains (" has no effect" ) || kind == InactiveBlock ) {
196
225
diag .severity = Hint ;
197
226
diag .tags = [Unnecessary ];
198
227
}
@@ -229,23 +258,23 @@ class DiagnosticsFeature {
229
258
return ! PathHelper .matches (path , pathFilter );
230
259
}
231
260
232
- function filterRelevantDiagnostics (diagnostics : Array <HaxeDiagnostic <Any >>): Array <HaxeDiagnostic <Any >> {
261
+ function filterRelevantDiagnostics (diagnostics : ReadOnlyArray <HaxeDiagnostic <Any >>): ReadOnlyArray <HaxeDiagnostic <Any >> {
233
262
// hide regular compiler errors while there's parser errors, they can be misleading
234
263
final hasProblematicParserErrors = diagnostics .find (d -> switch (d .kind : Int ) {
235
- case ParserError : d .args != " Missing ;" ; // don't be too strict
264
+ case DKParserError : d .args != " Missing ;" ; // don't be too strict
236
265
case _ : false ;
237
266
}) != null ;
238
267
if (hasProblematicParserErrors ) {
239
268
diagnostics = diagnostics .filter (d -> switch (d .kind : Int ) {
240
- case CompilerError , UnresolvedIdentifier : false ;
269
+ case DKCompilerError , DKUnresolvedIdentifier : false ;
241
270
case _ : true ;
242
271
});
243
272
}
244
273
245
274
// hide unused import warnings while there's compiler errors (to avoid false positives)
246
- final hasCompilerErrors = diagnostics .find (d -> d .kind == cast CompilerError ) != null ;
275
+ final hasCompilerErrors = diagnostics .find (d -> d .kind == cast DKCompilerError ) != null ;
247
276
if (hasCompilerErrors ) {
248
- diagnostics = diagnostics .filter (d -> d .kind != cast UnusedImport );
277
+ diagnostics = diagnostics .filter (d -> d .kind != cast DKUnusedImport );
249
278
}
250
279
251
280
// hide inactive blocks that are contained within other inactive blocks
@@ -300,25 +329,62 @@ class DiagnosticsFeature {
300
329
301
330
function invokePendingRequest (uri : DocumentUri , token : CancellationToken ) {
302
331
final doc : Null <HaxeDocument > = context .documents .getHaxe (uri );
332
+
303
333
if (doc != null ) {
304
- final onResolve = context .startTimer (" @diagnostics" );
305
- context .callDisplay (" @diagnostics" , [doc .uri .toFsPath () + " @0@diagnostics" ], null , token , result -> {
306
- pendingRequests .remove (uri );
307
- processDiagnosticsReply (uri , onResolve , result );
308
- }, error -> {
309
- pendingRequests .remove (uri );
310
- processErrorReply (uri , error );
311
- });
334
+ final onResolve = context .startTimer (timerName );
335
+ if (useJsonRpc ) {
336
+ var params : DiagnosticsParams = {fileContents : []};
337
+
338
+ if (context .config .user .diagnosticsForAllOpenFiles ) {
339
+ context .documents .iter (function (doc ) {
340
+ final path = doc .uri .toFsPath ();
341
+ if (doc .languageId == " haxe" && ! isPathFiltered (path )) {
342
+ params .fileContents .sure ().push ({file : path , contents : null });
343
+ }
344
+ });
345
+ } else {
346
+ params .file = doc .uri .toFsPath ();
347
+ }
348
+
349
+ context .callHaxeMethod (DisplayMethods . Diagnostics , params , token , result -> {
350
+ pendingRequests .remove (uri );
351
+ processDiagnosticsReply (uri , onResolve , result );
352
+ return null ;
353
+ }, error -> {
354
+ pendingRequests .remove (uri );
355
+ processErrorReply (uri , error );
356
+ });
357
+ } else {
358
+ context .callDisplay (" @diagnostics" , [doc .uri .toFsPath () + " @0@diagnostics" ], null , token , result -> {
359
+ pendingRequests .remove (uri );
360
+ final data = parseLegacyDiagnostics (result );
361
+ if (data == null ) {
362
+ clearDiagnosticsOnClient (errorUri );
363
+ } else {
364
+ processDiagnosticsReply (null , onResolve , data );
365
+ }
366
+ }, error -> {
367
+ pendingRequests .remove (uri );
368
+ processErrorReply (uri , error );
369
+ });
370
+ }
312
371
} else {
313
372
pendingRequests .remove (uri );
314
373
}
315
374
}
316
375
317
376
function cancelPendingRequest (uri : DocumentUri ) {
318
- var tokenSource = pendingRequests [uri ];
319
- if (tokenSource != null ) {
320
- pendingRequests .remove (uri );
321
- tokenSource .cancel ();
377
+ if (useJsonRpc && context .config .user .diagnosticsForAllOpenFiles ) {
378
+ for (tokenSource in pendingRequests ) {
379
+ tokenSource .cancel ();
380
+ }
381
+ pendingRequests .clear ();
382
+ } else {
383
+ var tokenSource = pendingRequests [uri ];
384
+ if (tokenSource != null ) {
385
+ pendingRequests .remove (uri );
386
+ tokenSource .cancel ();
387
+ }
322
388
}
323
389
}
324
390
@@ -333,79 +399,22 @@ class DiagnosticsFeature {
333
399
}
334
400
}
335
401
336
- enum abstract UnresolvedIdentifierSuggestion (Int ) {
337
- final Import ;
338
- final Typo ;
339
- }
340
-
341
- enum abstract MissingFieldCauseKind <T >(String ) {
342
- final AbstractParent : MissingFieldCauseKind <{parent : JsonTypePathWithParams }>;
343
- final ImplementedInterface : MissingFieldCauseKind <{parent : JsonTypePathWithParams }>;
344
- final PropertyAccessor : MissingFieldCauseKind <{property : JsonClassField , isGetter : Bool }>;
345
- final FieldAccess : MissingFieldCauseKind <{}>;
346
- final FinalFields : MissingFieldCauseKind <{fields : Array <JsonClassField >}>;
347
- }
348
-
349
- typedef MissingFieldCause <T > = {
350
- var kind : MissingFieldCauseKind <T >;
351
- var args : T ;
352
- }
353
-
354
- typedef MissingField = {
355
- var field : JsonClassField ;
356
- var type : JsonType <Dynamic >;
357
-
358
- /**
359
- When implementing multiple interfaces, there can be field duplicates among them. This flag is only
360
- true for the first such occurrence of a field, so that the "Implement all" code action doesn't end
361
- up implementing the same field multiple times.
362
- **/
363
- var unique : Bool ;
364
- }
365
-
366
- typedef MissingFieldDiagnostic = {
367
- var fields : Array <MissingField >;
368
- var cause : MissingFieldCause <Dynamic >;
369
- }
370
-
371
- typedef MissingFieldDiagnostics = {
372
- var moduleType : JsonModuleType <Dynamic >;
373
- var moduleFile : String ;
374
- var entries : Array <MissingFieldDiagnostic >;
375
- }
376
-
377
- typedef ReplaceableCode = {
378
- var description : String ;
379
- var range : Range ;
380
- var ? newCode : String ;
381
- }
382
-
383
- enum abstract DiagnosticKind <T >(Int ) from Int to Int {
384
- final UnusedImport : DiagnosticKind <Void >;
385
- final UnresolvedIdentifier : DiagnosticKind <Array <{kind : UnresolvedIdentifierSuggestion , name : String }>>;
386
- final CompilerError : DiagnosticKind <String >;
387
- final ReplaceableCode : DiagnosticKind <ReplaceableCode >;
388
- final ParserError : DiagnosticKind <String >;
389
- final DeprecationWarning : DiagnosticKind <String >;
390
- final InactiveBlock : DiagnosticKind <Void >;
391
- final MissingFields : DiagnosticKind <MissingFieldDiagnostics >;
392
-
393
- public inline function new (i : Int ) {
394
- this = i ;
395
- }
402
+ class DiagnosticKindHelper {
403
+ public static function make <T >(code : Int )
404
+ return (code : DiagnosticKind <T >);
396
405
397
- public function getMessage ( doc : Null <HaxeDocument >, args : T , range : Range ) {
398
- return switch ( this : DiagnosticKind < T >) {
399
- case UnusedImport : " Unused import/using" ;
400
- case UnresolvedIdentifier :
406
+ public static function getMessage < T >( dk : DiagnosticKind < T >, doc : Null <HaxeDocument >, args : T , range : Range ) {
407
+ return switch dk {
408
+ case DKUnusedImport : " Unused import/using" ;
409
+ case DKUnresolvedIdentifier :
401
410
var message = ' Unknown identifier' ;
402
411
if (doc != null ) {
403
412
message + = ' : ${doc .getText (range )}' ;
404
413
}
405
414
message ;
406
- case CompilerError : args .trim ();
415
+ case DKCompilerError : args .trim ();
407
416
case ReplaceableCode : args .description ;
408
- case ParserError : args ;
417
+ case DKParserError : args ;
409
418
case DeprecationWarning : args ;
410
419
case InactiveBlock : " Inactive conditional compilation block" ;
411
420
case MissingFields :
0 commit comments