mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +02:00
feat: Improve diagnostics; Make more modular everything; improvements
This commit is contained in:
parent
0ae6c8d9e2
commit
b897906b51
26
common/analyzer.go
Normal file
26
common/analyzer.go
Normal file
@ -0,0 +1,26 @@
|
||||
package common
|
||||
|
||||
func AnalyzeValues(
|
||||
parser SimpleConfigParser,
|
||||
availableOptions map[string]Option,
|
||||
) []ValueError {
|
||||
errors := make([]ValueError, 0)
|
||||
|
||||
for optionName, line := range parser.Lines {
|
||||
documentationOption := availableOptions[optionName]
|
||||
|
||||
err := documentationOption.Value.CheckIsValid(line.Value)
|
||||
|
||||
if err != nil {
|
||||
errors = append(errors, ValueError{
|
||||
Line: line.Position.Line,
|
||||
Option: optionName,
|
||||
Value: line.Value,
|
||||
DocError: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
27
common/diagnostics.go
Normal file
27
common/diagnostics.go
Normal file
@ -0,0 +1,27 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func ClearDiagnostics(context *glsp.Context, uri protocol.DocumentUri) {
|
||||
context.Notify(
|
||||
"textDocument/publishDiagnostics",
|
||||
protocol.PublishDiagnosticsParams{
|
||||
URI: uri,
|
||||
Diagnostics: []protocol.Diagnostic{},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func SendDiagnostics(context *glsp.Context, uri protocol.DocumentUri, diagnostics []protocol.Diagnostic) {
|
||||
context.Notify(
|
||||
"textDocument/publishDiagnostics",
|
||||
protocol.PublishDiagnosticsParams{
|
||||
URI: uri,
|
||||
Diagnostics: diagnostics,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
103
common/errors.go
103
common/errors.go
@ -3,33 +3,90 @@ package common
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
type ParserError interface{}
|
||||
type DiagnosticableError interface {
|
||||
GetDiagnostic(uri protocol.DocumentUri) protocol.Diagnostic
|
||||
}
|
||||
|
||||
type OptionError struct {
|
||||
Line uint32
|
||||
ProvidedOption string
|
||||
DocError error
|
||||
}
|
||||
func (e OptionError) GetPublishDiagnosticsParams() protocol.Diagnostic {
|
||||
severity := protocol.DiagnosticSeverityError
|
||||
|
||||
return protocol.Diagnostic{
|
||||
Message: e.DocError.Error(),
|
||||
Range: protocol.Range{
|
||||
Start: protocol.Position{
|
||||
Line: e.Line,
|
||||
Character: 0,
|
||||
},
|
||||
End: protocol.Position{
|
||||
Line: e.Line,
|
||||
Character: uint32(utf8.RuneCountInString(e.ProvidedOption)),
|
||||
},
|
||||
},
|
||||
Severity: &severity,
|
||||
}
|
||||
}
|
||||
func (e OptionError) Error() string {
|
||||
return "Option error"
|
||||
}
|
||||
|
||||
type ValueError struct {
|
||||
Line uint32
|
||||
Option string
|
||||
Value string
|
||||
|
||||
DocError error
|
||||
}
|
||||
func (e ValueError) GetPublishDiagnosticsParams() protocol.Diagnostic {
|
||||
severity := protocol.DiagnosticSeverityError
|
||||
start := uint32(utf8.RuneCountInString(e.Option) + utf8.RuneCountInString(" "))
|
||||
|
||||
return protocol.Diagnostic{
|
||||
Message: e.DocError.Error(),
|
||||
Range: protocol.Range{
|
||||
Start: protocol.Position{
|
||||
Line: e.Line,
|
||||
Character: start,
|
||||
},
|
||||
End: protocol.Position{
|
||||
Line: e.Line,
|
||||
Character: start + uint32(utf8.RuneCountInString(e.Value)),
|
||||
},
|
||||
},
|
||||
Severity: &severity,
|
||||
}
|
||||
}
|
||||
func (e ValueError) Error() string {
|
||||
return "Value error"
|
||||
}
|
||||
|
||||
type OptionAlreadyExistsError struct {
|
||||
Option string
|
||||
FoundOnLine uint32
|
||||
AlreadyLine uint32
|
||||
}
|
||||
|
||||
func (e OptionAlreadyExistsError) Error() string {
|
||||
return fmt.Sprintf("Option %s already exists", e.Option)
|
||||
return fmt.Sprintf("This option is already defined on line %d", e.AlreadyLine)
|
||||
}
|
||||
|
||||
type OptionUnknownError struct {
|
||||
Option string
|
||||
}
|
||||
type OptionUnknownError struct {}
|
||||
|
||||
func (e OptionUnknownError) Error() string {
|
||||
return fmt.Sprintf("Option '%s' does not exist", e.Option)
|
||||
return "This option does not exist"
|
||||
}
|
||||
|
||||
type MalformedLineError struct {
|
||||
Line string
|
||||
}
|
||||
type MalformedLineError struct {}
|
||||
|
||||
func (e MalformedLineError) Error() string {
|
||||
return fmt.Sprintf("Malformed line: %s", e.Line)
|
||||
return "Malformed line"
|
||||
}
|
||||
|
||||
type LineNotFoundError struct{}
|
||||
@ -38,21 +95,26 @@ func (e LineNotFoundError) Error() string {
|
||||
return "Line not found"
|
||||
}
|
||||
|
||||
// Value errors
|
||||
type ValueNotInEnumError struct {
|
||||
AvailableValues []string
|
||||
ProvidedValue string
|
||||
}
|
||||
|
||||
func (e ValueNotInEnumError) Error() string {
|
||||
return fmt.Sprintf("This value is not valid. Select one from: %s", strings.Join(e.AvailableValues, ","))
|
||||
}
|
||||
|
||||
type NotANumberError struct{}
|
||||
|
||||
func (e NotANumberError) Error() string {
|
||||
return "This is not a number"
|
||||
return "This must be number"
|
||||
}
|
||||
|
||||
type NumberIsNotPositiveError struct{}
|
||||
|
||||
func (e NumberIsNotPositiveError) Error() string {
|
||||
return "This number is not positive"
|
||||
return "This number must be positive for this setting"
|
||||
}
|
||||
|
||||
type EmptyStringError struct{}
|
||||
@ -64,9 +126,8 @@ func (e EmptyStringError) Error() string {
|
||||
type ArrayContainsDuplicatesError struct {
|
||||
Duplicates []string
|
||||
}
|
||||
|
||||
func (e ArrayContainsDuplicatesError) Error() string {
|
||||
return fmt.Sprintf("Array contains the following duplicate values: %s", strings.Join(e.Duplicates, ","))
|
||||
return fmt.Sprintf("Remove the following duplicate values: %s", strings.Join(e.Duplicates, ","))
|
||||
}
|
||||
|
||||
type PathDoesNotExistError struct{}
|
||||
@ -81,13 +142,3 @@ func (e PathInvalidError) Error() string {
|
||||
return "This path is invalid"
|
||||
}
|
||||
|
||||
type ValueError struct {
|
||||
Line int
|
||||
Start int
|
||||
End int
|
||||
Error error
|
||||
}
|
||||
|
||||
func (e ValueNotInEnumError) Error() string {
|
||||
return fmt.Sprintf("'%s' is not valid. Select one from: %s", e.ProvidedValue, strings.Join(e.AvailableValues, ","))
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type SimpleConfigPosition struct {
|
||||
Line int
|
||||
Line uint32
|
||||
}
|
||||
|
||||
type SimpleConfigLine struct {
|
||||
@ -33,21 +33,17 @@ type SimpleConfigParser struct {
|
||||
Options SimpleConfigOptions
|
||||
}
|
||||
|
||||
func (p *SimpleConfigParser) AddLine(line string, lineNumber int) error {
|
||||
func (p *SimpleConfigParser) AddLine(line string, lineNumber uint32) (string, error) {
|
||||
parts := strings.SplitN(line, p.Options.Separator, 2)
|
||||
|
||||
if len(parts) == 0 {
|
||||
return MalformedLineError{
|
||||
Line: line,
|
||||
}
|
||||
return "", MalformedLineError{}
|
||||
}
|
||||
|
||||
option := parts[0]
|
||||
|
||||
if _, exists := (*p.Options.AvailableOptions)[option]; !exists {
|
||||
return OptionUnknownError{
|
||||
Option: option,
|
||||
}
|
||||
return option, OptionUnknownError{}
|
||||
}
|
||||
|
||||
value := ""
|
||||
@ -57,9 +53,8 @@ func (p *SimpleConfigParser) AddLine(line string, lineNumber int) error {
|
||||
}
|
||||
|
||||
if _, exists := p.Lines[option]; exists {
|
||||
return OptionAlreadyExistsError{
|
||||
Option: option,
|
||||
FoundOnLine: uint32(lineNumber),
|
||||
return option, OptionAlreadyExistsError{
|
||||
AlreadyLine: p.Lines[option].Position.Line,
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +65,7 @@ func (p *SimpleConfigParser) AddLine(line string, lineNumber int) error {
|
||||
},
|
||||
}
|
||||
|
||||
return nil
|
||||
return option, nil
|
||||
|
||||
}
|
||||
|
||||
@ -91,7 +86,7 @@ func (p *SimpleConfigParser) UpsertOption(option string, value string) {
|
||||
if _, exists := p.Lines[option]; exists {
|
||||
p.ReplaceOption(option, value)
|
||||
} else {
|
||||
p.AddLine(option+p.Options.Separator+value, len(p.Lines))
|
||||
p.AddLine(option+p.Options.Separator+value, uint32(len(p.Lines)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,24 +101,26 @@ func (p *SimpleConfigParser) GetOption(option string) (SimpleConfigLine, error)
|
||||
Line: 0,
|
||||
},
|
||||
},
|
||||
OptionUnknownError{
|
||||
Option: option,
|
||||
}
|
||||
OptionUnknownError{}
|
||||
}
|
||||
|
||||
func (p *SimpleConfigParser) ParseFromFile(content string) []ParserError {
|
||||
func (p *SimpleConfigParser) ParseFromFile(content string) []OptionError {
|
||||
lines := strings.Split(content, "\n")
|
||||
errors := make([]ParserError, 0)
|
||||
errors := make([]OptionError, 0)
|
||||
|
||||
for index, line := range lines {
|
||||
if p.Options.IgnorePattern.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
err := p.AddLine(line, index)
|
||||
option, err := p.AddLine(line, uint32(index))
|
||||
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
errors = append(errors, OptionError{
|
||||
Line: uint32(index),
|
||||
ProvidedOption: option,
|
||||
DocError: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,7 +132,7 @@ func (p *SimpleConfigParser) Clear() {
|
||||
}
|
||||
|
||||
// TODO: Use better approach: Store an extra array of lines in order; with references to the SimpleConfigLine
|
||||
func (p *SimpleConfigParser) FindByLineNumber(lineNumber int) (string, SimpleConfigLine, error) {
|
||||
func (p *SimpleConfigParser) FindByLineNumber(lineNumber uint32) (string, SimpleConfigLine, error) {
|
||||
for option, line := range p.Lines {
|
||||
if line.Position.Line == lineNumber {
|
||||
return option, line, nil
|
||||
|
@ -1,29 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import "config-lsp/common"
|
||||
|
||||
// TODO: Cache options in a map like: EnumValues -> []Option
|
||||
// for faster lookup
|
||||
|
||||
func AnalyzeValue() []common.ValueError {
|
||||
errors := make([]common.ValueError, 0)
|
||||
|
||||
for optionName, line := range Parser.Lines {
|
||||
documentationOption := Options[optionName]
|
||||
|
||||
err := documentationOption.Value.CheckIsValid(line.Value)
|
||||
|
||||
if err != nil {
|
||||
errors = append(errors, common.ValueError{
|
||||
Line: line.Position.Line,
|
||||
Start: len(optionName) + len(" "),
|
||||
End: len(optionName) + len(" ") + len(line.Value),
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
// func AnalyzeSSHConfigIssues() []common.ParserError {}
|
@ -1,84 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"fmt"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func ClearDiagnostics(context *glsp.Context, uri protocol.DocumentUri) {
|
||||
context.Notify(
|
||||
"textDocument/publishDiagnostics",
|
||||
protocol.PublishDiagnosticsParams{
|
||||
URI: uri,
|
||||
Diagnostics: []protocol.Diagnostic{},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func SendDiagnosticsFromParserErrors(context *glsp.Context, uri protocol.DocumentUri, parserErrors []common.ParserError) {
|
||||
diagnosticErrors := make([]protocol.Diagnostic, 0)
|
||||
|
||||
for _, parserError := range parserErrors {
|
||||
switch parserError.(type) {
|
||||
case common.OptionAlreadyExistsError:
|
||||
{
|
||||
err := parserError.(common.OptionAlreadyExistsError)
|
||||
existingOption, _ := Parser.GetOption(err.Option)
|
||||
|
||||
diagnosticErrors = append(diagnosticErrors, protocol.Diagnostic{
|
||||
Range: protocol.Range{
|
||||
Start: protocol.Position{
|
||||
Line: err.FoundOnLine,
|
||||
Character: 0,
|
||||
},
|
||||
End: protocol.Position{
|
||||
Line: err.FoundOnLine,
|
||||
Character: uint32(utf8.RuneCountInString(err.Option)),
|
||||
},
|
||||
},
|
||||
Message: fmt.Sprintf("Option '%s' has already been declared on line %v", err.Option, existingOption.Position.Line+1),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.Notify(
|
||||
"textDocument/publishDiagnostics",
|
||||
protocol.PublishDiagnosticsParams{
|
||||
URI: uri,
|
||||
Diagnostics: diagnosticErrors,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func SendDiagnosticsFromAnalyzerErrors(context *glsp.Context, uri protocol.DocumentUri, errors []common.ValueError) {
|
||||
diagnosticErrors := make([]protocol.Diagnostic, 0)
|
||||
|
||||
for _, err := range errors {
|
||||
diagnosticErrors = append(diagnosticErrors, protocol.Diagnostic{
|
||||
Range: protocol.Range{
|
||||
Start: protocol.Position{
|
||||
Line: uint32(err.Line),
|
||||
Character: uint32(err.Start),
|
||||
},
|
||||
End: protocol.Position{
|
||||
Line: uint32(err.Line),
|
||||
Character: uint32(err.End),
|
||||
},
|
||||
},
|
||||
Message: err.Error.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
context.Notify(
|
||||
"textDocument/publishDiagnostics",
|
||||
protocol.PublishDiagnosticsParams{
|
||||
URI: uri,
|
||||
Diagnostics: diagnosticErrors,
|
||||
},
|
||||
)
|
||||
}
|
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (interface{}, error) {
|
||||
option, line, err := Parser.FindByLineNumber(int(params.Position.Line))
|
||||
option, line, err := Parser.FindByLineNumber(uint32(params.Position.Line))
|
||||
|
||||
if err == nil {
|
||||
if line.IsCursorAtRootOption(int(params.Position.Character)) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
@ -10,16 +12,33 @@ func TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeText
|
||||
content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text
|
||||
|
||||
Parser.Clear()
|
||||
errors := Parser.ParseFromFile(content)
|
||||
diagnostics := make([]protocol.Diagnostic, 0)
|
||||
|
||||
if len(errors) > 0 {
|
||||
SendDiagnosticsFromParserErrors(context, params.TextDocument.URI, errors)
|
||||
diagnostics = append(
|
||||
diagnostics,
|
||||
common.Map(
|
||||
Parser.ParseFromFile(content),
|
||||
func (err common.OptionError) protocol.Diagnostic {
|
||||
return err.GetPublishDiagnosticsParams()
|
||||
},
|
||||
)...,
|
||||
)
|
||||
|
||||
diagnostics = append(
|
||||
diagnostics,
|
||||
common.Map(
|
||||
common.AnalyzeValues(Parser, Options),
|
||||
func (err common.ValueError) protocol.Diagnostic {
|
||||
return err.GetPublishDiagnosticsParams()
|
||||
},
|
||||
)...,
|
||||
)
|
||||
|
||||
if len(diagnostics) > 0 {
|
||||
common.SendDiagnostics(context, params.TextDocument.URI, diagnostics)
|
||||
} else {
|
||||
ClearDiagnostics(context, params.TextDocument.URI)
|
||||
common.ClearDiagnostics(context, params.TextDocument.URI)
|
||||
}
|
||||
|
||||
analyzeErrors := AnalyzeValue()
|
||||
SendDiagnosticsFromAnalyzerErrors(context, params.TextDocument.URI, analyzeErrors)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"os"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
@ -14,10 +15,30 @@ func TextDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocu
|
||||
return err
|
||||
}
|
||||
|
||||
errors := Parser.ParseFromFile(string(readBytes))
|
||||
diagnostics := make([]protocol.Diagnostic, 0)
|
||||
|
||||
if len(errors) > 0 {
|
||||
SendDiagnosticsFromParserErrors(context, params.TextDocument.URI, errors)
|
||||
diagnostics = append(
|
||||
diagnostics,
|
||||
common.Map(
|
||||
Parser.ParseFromFile(string(readBytes)),
|
||||
func (err common.OptionError) protocol.Diagnostic {
|
||||
return err.GetPublishDiagnosticsParams()
|
||||
},
|
||||
)...,
|
||||
)
|
||||
|
||||
diagnostics = append(
|
||||
diagnostics,
|
||||
common.Map(
|
||||
common.AnalyzeValues(Parser, Options),
|
||||
func (err common.ValueError) protocol.Diagnostic {
|
||||
return err.GetPublishDiagnosticsParams()
|
||||
},
|
||||
)...,
|
||||
)
|
||||
|
||||
if len(diagnostics) > 0 {
|
||||
common.SendDiagnostics(context, params.TextDocument.URI, diagnostics)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
Loading…
x
Reference in New Issue
Block a user