From 58eca462508d90483c233aee3ef72008c472ba05 Mon Sep 17 00:00:00 2001 From: Matts966 Date: Sun, 19 Dec 2021 21:38:34 +0900 Subject: [PATCH 1/2] Support workspace level linters --- langserver/handler.go | 77 +++++++++++++++++++++++------- langserver/handler_test.go | 97 ++++++++++++++++++++++++++++++++++---- 2 files changed, 147 insertions(+), 27 deletions(-) diff --git a/langserver/handler.go b/langserver/handler.go index fcd34a4..9a38235 100644 --- a/langserver/handler.go +++ b/langserver/handler.go @@ -60,6 +60,7 @@ type Language struct { LintCategoryMap map[string]string `yaml:"lint-category-map" json:"lintCategoryMap"` LintSource string `yaml:"lint-source" json:"lintSource"` LintSeverity int `yaml:"lint-severity" json:"lintSeverity"` + LintWorkspace bool `yaml:"lint-workspace" json:"lintWorkspace"` FormatCommand string `yaml:"format-command" json:"formatCommand"` FormatStdin bool `yaml:"format-stdin" json:"formatStdin"` SymbolCommand string `yaml:"symbol-command" json:"symbolCommand"` @@ -98,6 +99,8 @@ func NewHandler(config *Config) jsonrpc2.Handler { conn: nil, filename: config.Filename, rootMarkers: *config.RootMarkers, + + lastPublishedURIs: make(map[string]map[DocumentURI]struct{}), } go handler.linter() return jsonrpc2.HandlerWithError(handler.handle) @@ -120,6 +123,10 @@ type langHandler struct { filename string folders []string rootMarkers []string + + // lastPublishedURIs is mapping from LanguageID string to mapping of + // whether diagnostics are published in a DocumentURI or not. + lastPublishedURIs map[string]map[DocumentURI]struct{} } // File is @@ -240,24 +247,33 @@ func (h *langHandler) linter() { running[uri] = cancel go func() { - diagnostics, err := h.lint(ctx, uri) + uriToDiagnostics, err := h.lint(ctx, uri) if err != nil { h.logger.Println(err) return } - if diagnostics == nil { - return + for diagURI, diagnostics := range uriToDiagnostics { + copiedDiagnostics := diagnostics + copiedDiagURI := diagURI + if diagURI == "file:" { + copiedDiagURI = uri + } + go func() { + version := 0 + if _, ok := h.files[uri]; ok { + version = h.files[uri].Version + } + h.conn.Notify( + ctx, + "textDocument/publishDiagnostics", + &PublishDiagnosticsParams{ + URI: copiedDiagURI, + Diagnostics: copiedDiagnostics, + Version: version, + }) + }() } - - h.conn.Notify( - context.Background(), - "textDocument/publishDiagnostics", - &PublishDiagnosticsParams{ - URI: uri, - Diagnostics: diagnostics, - Version: h.files[uri].Version, - }) }() } } @@ -322,7 +338,7 @@ func isFilename(s string) bool { } } -func (h *langHandler) lint(ctx context.Context, uri DocumentURI) ([]Diagnostic, error) { +func (h *langHandler) lint(ctx context.Context, uri DocumentURI) (map[DocumentURI][]Diagnostic, error) { f, ok := h.files[uri] if !ok { return nil, fmt.Errorf("document not found: %v", uri) @@ -361,17 +377,26 @@ func (h *langHandler) lint(ctx context.Context, uri DocumentURI) ([]Diagnostic, if h.loglevel >= 1 { h.logger.Printf("lint for LanguageID not supported: %v", f.LanguageID) } - return []Diagnostic{}, nil + return map[DocumentURI][]Diagnostic{}, nil } - diagnostics := []Diagnostic{} + uriToDiagnostics := map[DocumentURI][]Diagnostic{} for i, config := range configs { + if config.LintWorkspace { + for lastPublishedURI := range h.lastPublishedURIs[f.LanguageID] { + if _, ok := uriToDiagnostics[lastPublishedURI]; !ok { + uriToDiagnostics[lastPublishedURI] = []Diagnostic{} + } + } + h.lastPublishedURIs[f.LanguageID] = make(map[DocumentURI]struct{}) + } + if config.LintCommand == "" { continue } command := config.LintCommand - if !config.LintStdin && !strings.Contains(command, "${INPUT}") { + if !config.LintStdin && !config.LintWorkspace && !strings.Contains(command, "${INPUT}") { command = command + " ${INPUT}" } rootPath := h.findRootPath(fname, config) @@ -484,7 +509,23 @@ func (h *langHandler) lint(ctx context.Context, uri DocumentURI) ([]Diagnostic, severity = 4 } - diagnostics = append(diagnostics, Diagnostic{ + diagURI := uri + h.logger.Println(entry.Filename) + if entry.Filename != "" { + if filepath.IsAbs(entry.Filename) { + diagURI = toURI(entry.Filename) + } else { + diagURI = toURI(filepath.Join(rootPath, entry.Filename)) + } + } + if diagURI != uri && !config.LintWorkspace { + continue + } + + if config.LintWorkspace { + h.lastPublishedURIs[f.LanguageID][diagURI] = struct{}{} + } + uriToDiagnostics[diagURI] = append(uriToDiagnostics[diagURI], Diagnostic{ Range: Range{ Start: Position{Line: entry.Lnum - 1 - config.LintOffset, Character: entry.Col - 1}, End: Position{Line: entry.Lnum - 1 - config.LintOffset, Character: entry.Col - 1 + len([]rune(word))}, @@ -497,7 +538,7 @@ func (h *langHandler) lint(ctx context.Context, uri DocumentURI) ([]Diagnostic, } } - return diagnostics, nil + return uriToDiagnostics, nil } func itoaPtrIfNotZero(n int) *string { diff --git a/langserver/handler_test.go b/langserver/handler_test.go index 0a8ecf2..e7f81f2 100644 --- a/langserver/handler_test.go +++ b/langserver/handler_test.go @@ -14,7 +14,7 @@ func TestLintNoLinter(t *testing.T) { logger: log.New(log.Writer(), "", log.LstdFlags), configs: map[string][]Language{}, files: map[DocumentURI]*File{ - DocumentURI("file:///foo"): &File{}, + DocumentURI("file:///foo"): {}, }, } @@ -29,7 +29,7 @@ func TestLintNoFileMatched(t *testing.T) { logger: log.New(log.Writer(), "", log.LstdFlags), configs: map[string][]Language{}, files: map[DocumentURI]*File{ - DocumentURI("file:///foo"): &File{}, + DocumentURI("file:///foo"): {}, }, } @@ -57,14 +57,15 @@ func TestLintFileMatched(t *testing.T) { }, }, files: map[DocumentURI]*File{ - uri: &File{ + uri: { LanguageID: "vim", Text: "scriptencoding utf-8\nabnormal!\n", }, }, } - d, err := h.lint(context.Background(), uri) + uriToDiag, err := h.lint(context.Background(), uri) + d := uriToDiag[uri] if err != nil { t.Fatal(err) } @@ -110,7 +111,8 @@ func TestLintFileMatchedForce(t *testing.T) { }, } - d, err := h.lint(context.Background(), uri) + uriToDiag, err := h.lint(context.Background(), uri) + d := uriToDiag[uri] if err != nil { t.Fatal(err) } @@ -160,7 +162,8 @@ func TestLintOffsetColumnsZero(t *testing.T) { }, } - d, err := h.lint(context.Background(), uri) + uriToDiag, err := h.lint(context.Background(), uri) + d := uriToDiag[uri] if err != nil { t.Fatal(err) } @@ -200,7 +203,8 @@ func TestLintOffsetColumnsNoOffset(t *testing.T) { }, } - d, err := h.lint(context.Background(), uri) + uriToDiag, err := h.lint(context.Background(), uri) + d := uriToDiag[uri] if err != nil { t.Fatal(err) } @@ -241,7 +245,8 @@ func TestLintOffsetColumnsNonZero(t *testing.T) { }, } - d, err := h.lint(context.Background(), uri) + uriToDiag, err := h.lint(context.Background(), uri) + d := uriToDiag[uri] if err != nil { t.Fatal(err) } @@ -285,7 +290,8 @@ func TestLintCategoryMap(t *testing.T) { }, } - d, err := h.lint(context.Background(), uri) + uriToDiag, err := h.lint(context.Background(), uri) + d := uriToDiag[uri] if err != nil { t.Fatal(err) } @@ -333,3 +339,76 @@ func TestLintRequireRootMarker(t *testing.T) { t.Fatal("diagnostics should be zero as we have no root marker for the language but require one", d) } } + +// Test if lint can return diagnostics for multiple files +func TestLintMultipleFiles(t *testing.T) { + base, _ := os.Getwd() + file := filepath.Join(base, "foo") + file2 := filepath.Join(base, "bar") + uri := toURI(file) + uri2 := toURI(file2) + + h := &langHandler{ + logger: log.New(log.Writer(), "", log.LstdFlags), + rootPath: base, + configs: map[string][]Language{ + "vim": { + { + LintCommand: `echo ` + file + `:2:1:First file! && echo ` + file2 + `:1:2:Second file!`, + LintFormats: []string{"%f:%l:%c:%m"}, + LintIgnoreExitCode: true, + LintWorkspace: true, + }, + }, + }, + files: map[DocumentURI]*File{ + uri: { + LanguageID: "vim", + Text: "scriptencoding utf-8\nabnormal!\n", + }, + uri2: { + LanguageID: "vim", + Text: "scriptencoding utf-8\nabnormal!\n", + }, + }, + lastPublishedURIs: make(map[string]map[DocumentURI]struct{}), + } + + d, err := h.lint(context.Background(), uri) + if err != nil { + t.Fatal(err) + } + if len(d) != 2 { + t.Fatalf("diagnostics should be two, but got %#v", d) + } + if d[uri][0].Range.Start.Character != 0 { + t.Fatalf("first range.start.character should be %v but got: %v", 0, d[uri][0].Range.Start.Character) + } + if d[uri][0].Range.Start.Line != 1 { + t.Fatalf("first range.start.line should be %v but got: %v", 1, d[uri][0].Range.Start.Line) + } + if d[uri2][0].Range.Start.Character != 1 { + t.Fatalf("second range.start.character should be %v but got: %v", 1, d[uri2][0].Range.Start.Character) + } + if d[uri2][0].Range.Start.Line != 0 { + t.Fatalf("second range.start.line should be %v but got: %v", 0, d[uri2][0].Range.Start.Line) + } + + h.configs["vim"][0].LintCommand = `echo ` + file + `:2:1:First file only!` + d, err = h.lint(context.Background(), uri) + if err != nil { + t.Fatal(err) + } + if len(d) != 2 { + t.Fatalf("diagnostics should be two, but got %#v", d) + } + if d[uri][0].Range.Start.Character != 0 { + t.Fatalf("first range.start.character should be %v but got: %v", 0, d[uri][0].Range.Start.Character) + } + if d[uri][0].Range.Start.Line != 1 { + t.Fatalf("first range.start.line should be %v but got: %v", 1, d[uri][0].Range.Start.Line) + } + if len(d[uri2]) != 0 { + t.Fatalf("second diagnostics should be empty but got: %v", d[uri2]) + } +} From fc10d4a354d9e209a8e55f6f619eb43aeb1ffcd7 Mon Sep 17 00:00:00 2001 From: Matts966 Date: Mon, 20 Dec 2021 01:19:35 +0900 Subject: [PATCH 2/2] Remove redundant goroutine --- langserver/handler.go | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/langserver/handler.go b/langserver/handler.go index 9a38235..a0dfc72 100644 --- a/langserver/handler.go +++ b/langserver/handler.go @@ -254,25 +254,21 @@ func (h *langHandler) linter() { } for diagURI, diagnostics := range uriToDiagnostics { - copiedDiagnostics := diagnostics - copiedDiagURI := diagURI if diagURI == "file:" { - copiedDiagURI = uri + diagURI = uri } - go func() { - version := 0 - if _, ok := h.files[uri]; ok { - version = h.files[uri].Version - } - h.conn.Notify( - ctx, - "textDocument/publishDiagnostics", - &PublishDiagnosticsParams{ - URI: copiedDiagURI, - Diagnostics: copiedDiagnostics, - Version: version, - }) - }() + version := 0 + if _, ok := h.files[uri]; ok { + version = h.files[uri].Version + } + h.conn.Notify( + ctx, + "textDocument/publishDiagnostics", + &PublishDiagnosticsParams{ + URI: diagURI, + Diagnostics: diagnostics, + Version: version, + }) } }() }