From b897906b510bf7613d0b7e53bf01b90f8be02e74 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 28 Jul 2024 22:45:49 +0200 Subject: [PATCH] feat: Improve diagnostics; Make more modular everything; improvements --- common/analyzer.go | 26 +++++ common/diagnostics.go | 27 +++++ common/errors.go | 103 ++++++++++++++----- common/parser.go | 39 ++++--- handlers/openssh/analyzer.go | 29 ------ handlers/openssh/diagnostics.go | 84 --------------- handlers/openssh/text-document-completion.go | 2 +- handlers/openssh/text-document-did-change.go | 33 ++++-- handlers/openssh/text-document-did-open.go | 27 ++++- 9 files changed, 199 insertions(+), 171 deletions(-) create mode 100644 common/analyzer.go create mode 100644 common/diagnostics.go delete mode 100644 handlers/openssh/analyzer.go delete mode 100644 handlers/openssh/diagnostics.go diff --git a/common/analyzer.go b/common/analyzer.go new file mode 100644 index 0000000..d1853de --- /dev/null +++ b/common/analyzer.go @@ -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 +} + diff --git a/common/diagnostics.go b/common/diagnostics.go new file mode 100644 index 0000000..4193c19 --- /dev/null +++ b/common/diagnostics.go @@ -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, + }, + ) +} + diff --git a/common/errors.go b/common/errors.go index cbc9b42..1ec1650 100644 --- a/common/errors.go +++ b/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, ",")) -} diff --git a/common/parser.go b/common/parser.go index 58c2edc..8bf2883 100644 --- a/common/parser.go +++ b/common/parser.go @@ -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 diff --git a/handlers/openssh/analyzer.go b/handlers/openssh/analyzer.go deleted file mode 100644 index 797a501..0000000 --- a/handlers/openssh/analyzer.go +++ /dev/null @@ -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 {} diff --git a/handlers/openssh/diagnostics.go b/handlers/openssh/diagnostics.go deleted file mode 100644 index afadadd..0000000 --- a/handlers/openssh/diagnostics.go +++ /dev/null @@ -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, - }, - ) -} diff --git a/handlers/openssh/text-document-completion.go b/handlers/openssh/text-document-completion.go index ce779ee..7e04aae 100644 --- a/handlers/openssh/text-document-completion.go +++ b/handlers/openssh/text-document-completion.go @@ -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)) { diff --git a/handlers/openssh/text-document-did-change.go b/handlers/openssh/text-document-did-change.go index 64eacac..4168db3 100644 --- a/handlers/openssh/text-document-did-change.go +++ b/handlers/openssh/text-document-did-change.go @@ -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 } diff --git a/handlers/openssh/text-document-did-open.go b/handlers/openssh/text-document-did-open.go index f7b7131..2f89156 100644 --- a/handlers/openssh/text-document-did-open.go +++ b/handlers/openssh/text-document-did-open.go @@ -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