feat: Improve diagnostics; Make more modular everything; improvements

This commit is contained in:
Myzel394 2024-07-28 22:45:49 +02:00
parent 0ae6c8d9e2
commit b897906b51
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
9 changed files with 199 additions and 171 deletions

26
common/analyzer.go Normal file
View 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
View 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,
},
)
}

View File

@ -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, ","))
}

View File

@ -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

View File

@ -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 {}

View File

@ -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,
},
)
}

View File

@ -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)) {

View File

@ -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
}

View File

@ -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