@@ -13,19 +13,21 @@ import (
13
13
14
14
"github.com/microsoft/typescript-go/shim/ast"
15
15
"github.com/microsoft/typescript-go/shim/bundled"
16
- "github.com/microsoft/typescript-go/shim/compiler "
16
+ "github.com/microsoft/typescript-go/shim/core "
17
17
"github.com/microsoft/typescript-go/shim/ls"
18
18
"github.com/microsoft/typescript-go/shim/lsp/lsproto"
19
19
"github.com/microsoft/typescript-go/shim/project"
20
20
"github.com/microsoft/typescript-go/shim/scanner"
21
+
22
+ // "github.com/microsoft/typescript-go/shim/tspath"
21
23
"github.com/microsoft/typescript-go/shim/vfs"
22
- "github.com/microsoft/typescript-go/shim/vfs/cachedvfs"
23
24
"github.com/microsoft/typescript-go/shim/vfs/osvfs"
25
+
24
26
"github.com/sourcegraph/jsonrpc2"
25
27
"github.com/web-infra-dev/rslint/internal/config"
26
28
"github.com/web-infra-dev/rslint/internal/linter"
27
29
"github.com/web-infra-dev/rslint/internal/rule"
28
- "github.com/web-infra-dev/rslint/internal/utils"
30
+ util "github.com/web-infra-dev/rslint/internal/utils"
29
31
)
30
32
31
33
func ptrTo [T any ](v T ) * T {
@@ -41,16 +43,50 @@ type LSPServer struct {
41
43
// align with https://github.com/microsoft/typescript-go/blob/5cdf239b02006783231dd4da8ca125cef398cd27/internal/lsp/server.go#L147
42
44
//nolint
43
45
projectService * project.Service
46
+ //nolint
47
+ logger * project.Logger
48
+ fs vfs.FS
49
+ defaultLibraryPath string
50
+ typingsLocation string
51
+ cwd string
52
+ rslintConfig config.RslintConfig
53
+ }
54
+
55
+ func (s * LSPServer ) FS () vfs.FS {
56
+ return s .fs
57
+ }
58
+ func (s * LSPServer ) DefaultLibraryPath () string {
59
+ return s .defaultLibraryPath
60
+ }
61
+ func (s * LSPServer ) TypingsLocation () string {
62
+ return s .typingsLocation
63
+ }
64
+ func (s * LSPServer ) GetCurrentDirectory () string {
65
+ return s .cwd
66
+ }
67
+ func (s * LSPServer ) Client () project.Client {
68
+ return nil
69
+ }
70
+
71
+ // FIXME: support watcher in the future
72
+ func (s * LSPServer ) WatchFiles (ctx context.Context , watchers []* lsproto.FileSystemWatcher ) (project.WatcherHandle , error ) {
73
+ return "" , nil
44
74
}
45
75
46
76
func NewLSPServer () * LSPServer {
77
+ log .Printf ("cwd: %v" , util .Must (os .Getwd ()))
47
78
return & LSPServer {
48
79
documents : make (map [lsproto.DocumentUri ]string ),
49
80
diagnostics : make (map [lsproto.DocumentUri ][]rule.RuleDiagnostic ),
81
+ fs : bundled .WrapFS (osvfs .FS ()),
82
+ cwd : util .Must (os .Getwd ()),
50
83
}
51
84
}
52
85
53
- func (s * LSPServer ) Handle (ctx context.Context , conn * jsonrpc2.Conn , req * jsonrpc2.Request ) (interface {}, error ) {
86
+ func (s * LSPServer ) Handle (requestCtx context.Context , conn * jsonrpc2.Conn , req * jsonrpc2.Request ) (interface {}, error ) {
87
+ // FIXME: implement cancel logic
88
+ ctx := core .WithRequestID (requestCtx , req .ID .String ())
89
+
54
90
s .conn = conn
55
91
switch req .Method {
56
92
case "initialize" :
@@ -108,6 +144,39 @@ func (s *LSPServer) handleInitialize(ctx context.Context, req *jsonrpc2.Request)
108
144
}
109
145
}
110
146
147
+ s .projectService = project .NewService (s , project.ServiceOptions {
148
+ Logger : project .NewLogger ([]io.Writer {os .Stderr }, "tsgo.log" , project .LogLevelVerbose ),
149
+ PositionEncoding : lsproto .PositionEncodingKindUTF8 ,
150
+ })
151
+ // Try to find rslint configuration files with multiple strategies
152
+ var rslintConfigPath string
153
+ var configFound bool
154
+
155
+ // Use helper function to find config
156
+ rslintConfigPath , configFound = findRslintConfig (s .fs , s .cwd )
157
+
158
+ if ! configFound {
159
+ return nil , errors .New ("config file not found" )
160
+ }
161
+
162
+ // Load rslint configuration and extract tsconfig paths
163
+ loader := config .NewConfigLoader (s .fs , s .cwd )
164
+ rslintConfig , configDirectory , err := loader .LoadRslintConfig (rslintConfigPath )
165
+ if err != nil {
166
+ return nil , fmt .Errorf ("could not load rslint config: %w" , err )
167
+ }
168
+ s .rslintConfig = rslintConfig
169
+ tsConfigs , err := loader .LoadTsConfigsFromRslintConfig (rslintConfig , configDirectory )
170
+ if err != nil {
171
+ return nil , fmt .Errorf ("could not load TypeScript configs from rslint config: %w" , err )
172
+ }
173
+
174
+ if len (tsConfigs ) == 0 {
175
+ return nil , errors .New ("no TypeScript configurations found in rslint config" )
176
+ }
177
+
178
+ // Do not pre-create configured projects here. The service will create
179
+ // configured or inferred projects on demand when files are opened.
111
180
result := & lsproto.InitializeResult {
112
181
Capabilities : & lsproto.ServerCapabilities {
113
182
TextDocumentSync : & lsproto.TextDocumentSyncOptionsOrKind {
@@ -123,7 +192,7 @@ func (s *LSPServer) handleInitialize(ctx context.Context, req *jsonrpc2.Request)
123
192
}
124
193
125
194
func (s * LSPServer ) handleDidOpen (ctx context.Context , req * jsonrpc2.Request ) {
126
- log .Printf ("Handling didOpen: %+v" , req )
195
+ log .Printf ("Handling didOpen: %+v,%+v " , req , ctx )
127
196
var params lsproto.DidOpenTextDocumentParams
128
197
if err := json .Unmarshal (* req .Params , & params ); err != nil {
129
198
return
@@ -173,7 +242,7 @@ func (s *LSPServer) handleShutdown(ctx context.Context, req *jsonrpc2.Request) (
173
242
}
174
243
175
244
func (s * LSPServer ) handleCodeAction (ctx context.Context , req * jsonrpc2.Request ) (interface {}, error ) {
176
- log .Printf ("Handling codeAction: %+v" , req )
245
+ log .Printf ("Handling codeAction: %+v,%+v " , req , ctx )
177
246
var params lsproto.CodeActionParams
178
247
if err := json .Unmarshal (* req .Params , & params ); err != nil {
179
248
return nil , & jsonrpc2.Error {
@@ -254,100 +323,10 @@ func (s *LSPServer) runDiagnostics(ctx context.Context, uri lsproto.DocumentUri,
254
323
// Initialize rule registry with all available rules (ensure it's done once)
255
324
config .RegisterAllRules ()
256
325
257
- // Convert URI to file path
258
- filePath := uriToPath (uri )
259
-
260
- // Create a temporary file system with the content
261
- vfs := bundled .WrapFS (cachedvfs .From (osvfs .FS ()))
262
-
263
- // Determine working directory with improved fallback strategy
264
- workingDir := s .rootURI
265
- if workingDir == "" || workingDir == "." {
266
- // If rootURI is not set properly, try to infer from the file path
267
- if filePath != "" {
268
- // Use the directory of the current file as a starting point
269
- if idx := strings .LastIndex (filePath , "/" ); idx != - 1 {
270
- workingDir = filePath [:idx ]
271
- } else {
272
- workingDir = "."
273
- }
274
- } else {
275
- workingDir = "."
276
- }
277
- }
278
-
279
- host := utils .CreateCompilerHost (workingDir , vfs )
280
-
281
- // Try to find rslint configuration files with multiple strategies
282
- var rslintConfigPath string
283
- var configFound bool
284
-
285
- // Use helper function to find config
286
- rslintConfigPath , workingDir , configFound = findRslintConfig (vfs , workingDir , filePath )
287
-
288
- if ! configFound {
289
- return
290
- }
291
-
292
- // Load rslint configuration and extract tsconfig paths
293
- loader := config .NewConfigLoader (vfs , workingDir )
294
- rslintConfig , configDirectory , err := loader .LoadRslintConfig (rslintConfigPath )
295
- if err != nil {
296
- log .Printf ("Could not load rslint config: %v" , err )
297
- return
298
- }
299
-
300
- tsConfigs , err := loader .LoadTsConfigsFromRslintConfig (rslintConfig , configDirectory )
301
- if err != nil {
302
- log .Printf ("Could not load TypeScript configs from rslint config: %v" , err )
303
- return
304
- }
305
-
306
- if len (tsConfigs ) == 0 {
307
- log .Printf ("No TypeScript configurations found in rslint config" )
308
- return
309
- }
310
-
311
- // Create multiple programs for all tsconfig files
312
- var programs []* compiler.Program
313
- var targetFile * ast.SourceFile
314
-
315
- for _ , tsConfigPath := range tsConfigs {
316
- program , err := utils .CreateProgram (true , vfs , workingDir , tsConfigPath , host )
317
- if err != nil {
318
- log .Printf ("Could not create program for %s: %v" , tsConfigPath , err )
319
- continue
320
- }
321
- programs = append (programs , program )
322
-
323
- // Check if the current file is in this program
324
- sourceFiles := program .GetSourceFiles ()
325
-
326
- if targetFile == nil {
327
- for _ , sf := range sourceFiles {
328
- if strings .HasSuffix (sf .FileName (), filePath ) || sf .FileName () == filePath {
329
- targetFile = sf
330
- break
331
- }
332
- }
333
- }
334
- }
335
-
336
- if len (programs ) == 0 {
337
- log .Printf ("Could not create any programs" )
338
- return
339
- }
340
-
341
- if targetFile == nil {
342
- // If we can't find the file in any program, skip diagnostics
343
- log .Printf ("Could not find file %s in any program" , filePath )
344
- return
345
- }
346
-
347
326
// Collect diagnostics
348
327
var lsp_diagnostics []* lsproto.Diagnostic
349
328
350
- rule_diags , err := runLintWithPrograms (uri , programs , rslintConfig )
329
+ rule_diags , err := runLintWithProjectService (uri , s . projectService , ctx , s . rslintConfig )
351
330
352
331
if err != nil {
353
332
log .Printf ("Error running lint: %v" , err )
@@ -410,41 +389,17 @@ func uriToPath(uri lsproto.DocumentUri) string {
410
389
}
411
390
412
391
// findRslintConfig searches for rslint configuration files using multiple strategies
413
- func findRslintConfig (fs vfs.FS , workingDir , filePath string ) (string , string , bool ) {
392
+ func findRslintConfig (fs vfs.FS , workingDir string ) (string , bool ) {
414
393
defaultConfigs := []string {"rslint.json" , "rslint.jsonc" }
415
394
416
395
// Strategy 1: Try in the working directory
417
396
for _ , configName := range defaultConfigs {
418
397
configPath := workingDir + "/" + configName
419
398
if fs .FileExists (configPath ) {
420
- return configPath , workingDir , true
421
- }
422
- }
423
-
424
- // Strategy 2: If not found, walk up the directory tree
425
- if filePath != "" {
426
- dir := filePath
427
- if idx := strings .LastIndex (dir , "/" ); idx != - 1 {
428
- dir = dir [:idx ]
429
- }
430
-
431
- for i := 0 ; i < 5 && dir != "/" && dir != "" ; i ++ { // Limit search depth
432
- for _ , configName := range defaultConfigs {
433
- testPath := dir + "/" + configName
434
- if fs .FileExists (testPath ) {
435
- return testPath , dir , true
436
- }
437
- }
438
- // Move up one directory
439
- if idx := strings .LastIndex (dir , "/" ); idx != - 1 {
440
- dir = dir [:idx ]
441
- } else {
442
- break
443
- }
399
+ return configPath , true
444
400
}
445
401
}
446
-
447
- return "" , workingDir , false
402
+ return "" , false
448
403
}
449
404
450
405
func runLSP () int {
@@ -484,14 +439,20 @@ type LintResponse struct {
484
439
RuleCount int `json:"ruleCount"`
485
440
}
486
441
487
- func runLintWithPrograms (uri lsproto.DocumentUri , programs []* compiler.Program , rslintConfig config.RslintConfig ) ([]rule.RuleDiagnostic , error ) {
488
- if len (programs ) == 0 {
489
- return nil , errors .New ("no programs provided" )
490
- }
491
-
442
+ func runLintWithProjectService (uri lsproto.DocumentUri , service * project.Service , ctx context.Context , rslintConfig config.RslintConfig ) ([]rule.RuleDiagnostic , error ) {
443
+ log .Printf ("context: %v" , ctx )
492
444
// Initialize rule registry with all available rules
493
445
config .RegisterAllRules ()
494
-
446
+ filename := uriToPath (uri )
447
+ content , ok := service .FS ().ReadFile (filename )
448
+ if ! ok {
449
+ return nil , fmt .Errorf ("failed to read file %s" , filename )
450
+ }
451
+ service .OpenFile (filename , content , core .GetScriptKindFromFileName (filename ), service .GetCurrentDirectory ())
452
+ project := service .EnsureDefaultProjectForURI (uri )
453
+ languageService , done := project .GetLanguageServiceForRequest (ctx )
454
+ program := languageService .GetProgram ()
455
+ defer done ()
495
456
// Collect diagnostics
496
457
var diagnostics []rule.RuleDiagnostic
497
458
var diagnosticsLock sync.Mutex
@@ -502,23 +463,12 @@ func runLintWithPrograms(uri lsproto.DocumentUri, programs []*compiler.Program,
502
463
defer diagnosticsLock .Unlock ()
503
464
diagnostics = append (diagnostics , d )
504
465
}
505
- filename := uriToPath (uri )
506
466
507
- // Run linter with all programs using rule registry
508
- _ , err := linter .RunLinter (
509
- programs ,
510
- false , // Don't use single-threaded mode for LSP
511
- []string {filename },
512
- utils .ExcludePaths ,
467
+ linter .RunLinterInProgram (program , []string {filename }, util .ExcludePaths ,
513
468
func (sourceFile * ast.SourceFile ) []linter.ConfiguredRule {
514
469
activeRules := config .GlobalRuleRegistry .GetEnabledRules (rslintConfig , sourceFile .FileName ())
515
470
return activeRules
516
- },
517
- diagnosticCollector ,
518
- )
519
- if err != nil {
520
- return nil , fmt .Errorf ("error running linter: %w" , err )
521
- }
471
+ }, diagnosticCollector )
522
472
523
473
if diagnostics == nil {
524
474
diagnostics = []rule.RuleDiagnostic {}
0 commit comments