Skip to content

Commit

Permalink
Merge pull request #193 from Matts966/master
Browse files Browse the repository at this point in the history
Support workspace level linters
  • Loading branch information
mattn authored Dec 20, 2021
2 parents e7272ce + fc10d4a commit d4dc649
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 27 deletions.
73 changes: 55 additions & 18 deletions langserver/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -240,24 +247,29 @@ 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 {
if diagURI == "file:" {
diagURI = uri
}
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,
})
}

h.conn.Notify(
context.Background(),
"textDocument/publishDiagnostics",
&PublishDiagnosticsParams{
URI: uri,
Diagnostics: diagnostics,
Version: h.files[uri].Version,
})
}()
}
}
Expand Down Expand Up @@ -322,7 +334,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)
Expand Down Expand Up @@ -361,17 +373,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)
Expand Down Expand Up @@ -484,7 +505,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))},
Expand All @@ -497,7 +534,7 @@ func (h *langHandler) lint(ctx context.Context, uri DocumentURI) ([]Diagnostic,
}
}

return diagnostics, nil
return uriToDiagnostics, nil
}

func itoaPtrIfNotZero(n int) *string {
Expand Down
97 changes: 88 additions & 9 deletions langserver/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"): {},
},
}

Expand All @@ -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"): {},
},
}

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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])
}
}

0 comments on commit d4dc649

Please sign in to comment.