mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +02:00
feat: Add diagnostics; Improvements
This commit is contained in:
parent
b9e8fc6e55
commit
0ae6c8d9e2
@ -2,13 +2,15 @@ package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
type Value interface {
|
||||
getTypeDescription() []string
|
||||
GetTypeDescription() []string
|
||||
CheckIsValid(value string) error
|
||||
}
|
||||
|
||||
type EnumValue struct {
|
||||
@ -19,7 +21,7 @@ type EnumValue struct {
|
||||
EnforceValues bool
|
||||
}
|
||||
|
||||
func (v EnumValue) getTypeDescription() []string {
|
||||
func (v EnumValue) GetTypeDescription() []string {
|
||||
lines := make([]string, len(v.Values)+1)
|
||||
lines[0] = "Enum of:"
|
||||
|
||||
@ -29,12 +31,42 @@ func (v EnumValue) getTypeDescription() []string {
|
||||
|
||||
return lines
|
||||
}
|
||||
func (v EnumValue) CheckIsValid(value string) error {
|
||||
if !v.EnforceValues {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, validValue := range v.Values {
|
||||
if validValue == value {
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ValueNotInEnumError{
|
||||
ProvidedValue: value,
|
||||
AvailableValues: v.Values,
|
||||
}
|
||||
}
|
||||
|
||||
type PositiveNumberValue struct{}
|
||||
|
||||
func (v PositiveNumberValue) getTypeDescription() []string {
|
||||
func (v PositiveNumberValue) GetTypeDescription() []string {
|
||||
return []string{"Positive number"}
|
||||
}
|
||||
func (v PositiveNumberValue) CheckIsValid(value string) error {
|
||||
number, err := strconv.Atoi(value)
|
||||
|
||||
if err != nil {
|
||||
return NotANumberError{}
|
||||
}
|
||||
|
||||
if number < 0 {
|
||||
return NumberIsNotPositiveError{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ArrayValue struct {
|
||||
SubValue Value
|
||||
@ -42,25 +74,57 @@ type ArrayValue struct {
|
||||
AllowDuplicates bool
|
||||
}
|
||||
|
||||
func (v ArrayValue) getTypeDescription() []string {
|
||||
func (v ArrayValue) GetTypeDescription() []string {
|
||||
subValue := v.SubValue.(Value)
|
||||
|
||||
return append(
|
||||
[]string{"An Array separated by " + v.Separator + " of:"},
|
||||
subValue.getTypeDescription()...,
|
||||
subValue.GetTypeDescription()...,
|
||||
)
|
||||
}
|
||||
func (v ArrayValue) CheckIsValid(value string) error {
|
||||
values := strings.Split(value, v.Separator)
|
||||
|
||||
for _, subValue := range values {
|
||||
err := v.SubValue.CheckIsValid(subValue)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !v.AllowDuplicates {
|
||||
valuesOccurrences := SliceToMap(values, 0)
|
||||
|
||||
// Only continue if there are actually duplicate values
|
||||
if len(values) != len(valuesOccurrences) {
|
||||
for _, subValue := range values {
|
||||
valuesOccurrences[subValue]++
|
||||
}
|
||||
|
||||
duplicateValues := FilterMapWhere(valuesOccurrences, func(_ string, value int) bool {
|
||||
return value > 1
|
||||
})
|
||||
|
||||
return ArrayContainsDuplicatesError{
|
||||
Duplicates: KeysOfMap(duplicateValues),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type OrValue struct {
|
||||
Values []Value
|
||||
}
|
||||
|
||||
func (v OrValue) getTypeDescription() []string {
|
||||
func (v OrValue) GetTypeDescription() []string {
|
||||
lines := make([]string, 0)
|
||||
|
||||
for _, subValueRaw := range v.Values {
|
||||
subValue := subValueRaw.(Value)
|
||||
subLines := subValue.getTypeDescription()
|
||||
subLines := subValue.GetTypeDescription()
|
||||
|
||||
for index, line := range subLines {
|
||||
if strings.HasPrefix(line, "\t*") {
|
||||
@ -78,21 +142,48 @@ func (v OrValue) getTypeDescription() []string {
|
||||
lines...,
|
||||
)
|
||||
}
|
||||
func (v OrValue) CheckIsValid(value string) error {
|
||||
var firstError error = nil
|
||||
|
||||
for _, subValue := range v.Values {
|
||||
err := subValue.CheckIsValid(value)
|
||||
|
||||
if err == nil {
|
||||
return nil
|
||||
} else if firstError == nil {
|
||||
firstError = err
|
||||
}
|
||||
}
|
||||
|
||||
return firstError
|
||||
}
|
||||
|
||||
type StringValue struct{}
|
||||
|
||||
func (v StringValue) getTypeDescription() []string {
|
||||
func (v StringValue) GetTypeDescription() []string {
|
||||
return []string{"String"}
|
||||
}
|
||||
|
||||
func (v StringValue) CheckIsValid(value string) error {
|
||||
if value == "" {
|
||||
return EmptyStringError{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type CustomValue struct {
|
||||
FetchValue func() Value
|
||||
}
|
||||
|
||||
func (v CustomValue) getTypeDescription() []string {
|
||||
func (v CustomValue) GetTypeDescription() []string {
|
||||
return []string{"Custom"}
|
||||
}
|
||||
|
||||
func (v CustomValue) CheckIsValid(value string) error {
|
||||
return v.FetchValue().CheckIsValid(value)
|
||||
}
|
||||
|
||||
type Prefix struct {
|
||||
Prefix string
|
||||
Meaning string
|
||||
@ -102,8 +193,8 @@ type PrefixWithMeaningValue struct {
|
||||
SubValue Value
|
||||
}
|
||||
|
||||
func (v PrefixWithMeaningValue) getTypeDescription() []string {
|
||||
subDescription := v.SubValue.getTypeDescription()
|
||||
func (v PrefixWithMeaningValue) GetTypeDescription() []string {
|
||||
subDescription := v.SubValue.GetTypeDescription()
|
||||
|
||||
prefixDescription := Map(v.Prefixes, func(prefix Prefix) string {
|
||||
return fmt.Sprintf("_%s_ -> %s", prefix.Prefix, prefix.Meaning)
|
||||
@ -117,6 +208,10 @@ func (v PrefixWithMeaningValue) getTypeDescription() []string {
|
||||
)
|
||||
}
|
||||
|
||||
func (v PrefixWithMeaningValue) CheckIsValid(value string) error {
|
||||
return v.SubValue.CheckIsValid(value)
|
||||
}
|
||||
|
||||
type PathType uint8
|
||||
|
||||
const (
|
||||
@ -129,13 +224,13 @@ type PathValue struct {
|
||||
RequiredType PathType
|
||||
}
|
||||
|
||||
func (v PathValue) getTypeDescription() []string {
|
||||
func (v PathValue) GetTypeDescription() []string {
|
||||
hints := make([]string, 0)
|
||||
|
||||
switch v.RequiredType {
|
||||
case PathTypeExistenceOptional:
|
||||
hints = append(hints, "Optional")
|
||||
break;
|
||||
break
|
||||
case PathTypeFile:
|
||||
hints = append(hints, "File")
|
||||
case PathTypeDirectory:
|
||||
@ -145,13 +240,35 @@ func (v PathValue) getTypeDescription() []string {
|
||||
return []string{strings.Join(hints, ", ")}
|
||||
}
|
||||
|
||||
func (v PathValue) CheckIsValid(value string) error {
|
||||
if !DoesPathExist(value) {
|
||||
return PathDoesNotExistError{}
|
||||
}
|
||||
|
||||
isValid := false
|
||||
|
||||
if (v.RequiredType & PathTypeFile) == PathTypeFile {
|
||||
isValid = isValid && IsPathFile(value)
|
||||
}
|
||||
|
||||
if (v.RequiredType & PathTypeDirectory) == PathTypeDirectory {
|
||||
isValid = isValid && IsPathDirectory(value)
|
||||
}
|
||||
|
||||
if isValid {
|
||||
return nil
|
||||
}
|
||||
|
||||
return PathInvalidError{}
|
||||
}
|
||||
|
||||
type Option struct {
|
||||
Documentation string
|
||||
Value Value
|
||||
}
|
||||
|
||||
func GetDocumentation(o *Option) protocol.MarkupContent {
|
||||
typeDescription := strings.Join(o.Value.getTypeDescription(), "\n")
|
||||
typeDescription := strings.Join(o.Value.GetTypeDescription(), "\n")
|
||||
|
||||
return protocol.MarkupContent{
|
||||
Kind: protocol.MarkupKindPlainText,
|
||||
|
@ -39,10 +39,55 @@ func (e LineNotFoundError) Error() string {
|
||||
}
|
||||
|
||||
type ValueNotInEnumError struct {
|
||||
availableValues []string
|
||||
providedValue string
|
||||
AvailableValues []string
|
||||
ProvidedValue string
|
||||
}
|
||||
|
||||
type NotANumberError struct{}
|
||||
|
||||
func (e NotANumberError) Error() string {
|
||||
return "This is not a number"
|
||||
}
|
||||
|
||||
type NumberIsNotPositiveError struct{}
|
||||
|
||||
func (e NumberIsNotPositiveError) Error() string {
|
||||
return "This number is not positive"
|
||||
}
|
||||
|
||||
type EmptyStringError struct{}
|
||||
|
||||
func (e EmptyStringError) Error() string {
|
||||
return "This setting may not be empty"
|
||||
}
|
||||
|
||||
type ArrayContainsDuplicatesError struct {
|
||||
Duplicates []string
|
||||
}
|
||||
|
||||
func (e ArrayContainsDuplicatesError) Error() string {
|
||||
return fmt.Sprintf("Array contains the following duplicate values: %s", strings.Join(e.Duplicates, ","))
|
||||
}
|
||||
|
||||
type PathDoesNotExistError struct{}
|
||||
|
||||
func (e PathDoesNotExistError) Error() string {
|
||||
return "This path does not exist"
|
||||
}
|
||||
|
||||
type PathInvalidError struct{}
|
||||
|
||||
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.Sprint("'%s' is not valid. Select one from: %s", e.providedValue, strings.Join(e.availableValues, ","))
|
||||
return fmt.Sprintf("'%s' is not valid. Select one from: %s", e.ProvidedValue, strings.Join(e.AvailableValues, ","))
|
||||
}
|
||||
|
@ -28,3 +28,75 @@ func Map[T any, O any](values []T, f func(T) O) []O {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func SliceToSet[T comparable](values []T) map[T]struct{} {
|
||||
set := make(map[T]struct{})
|
||||
|
||||
for _, value := range values {
|
||||
set[value] = struct{}{}
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func SliceToMap[T comparable, O any](values []T, defaultValue O) map[T]O {
|
||||
set := make(map[T]O)
|
||||
|
||||
for _, value := range values {
|
||||
set[value] = defaultValue
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func FilterWhere[T any](values []T, f func(T) bool) []T {
|
||||
result := make([]T, 0)
|
||||
|
||||
for _, value := range values {
|
||||
if f(value) {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func FilterMapWhere[T comparable, O any](values map[T]O, f func(T, O) bool) map[T]O {
|
||||
result := make(map[T]O)
|
||||
|
||||
for key, value := range values {
|
||||
if f(key, value) {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func KeysOfMap[T comparable, O any](values map[T]O) []T {
|
||||
keys := make([]T, 0)
|
||||
|
||||
for key := range values {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
func DoesPathExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func IsPathDirectory(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
|
||||
return err == nil && info.IsDir()
|
||||
}
|
||||
|
||||
func IsPathFile(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
|
||||
return err == nil && !info.IsDir()
|
||||
}
|
||||
|
29
handlers/openssh/analyzer.go
Normal file
29
handlers/openssh/analyzer.go
Normal file
@ -0,0 +1,29 @@
|
||||
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 {}
|
@ -54,3 +54,31 @@ func SendDiagnosticsFromParserErrors(context *glsp.Context, uri protocol.Documen
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -39,4 +39,3 @@ func QueryOpenSSHOptions(
|
||||
|
||||
return availableQueries, nil
|
||||
}
|
||||
|
||||
|
@ -320,7 +320,6 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may
|
||||
options, _ = QueryOpenSSHOptions("HostbasedAcceptedKeyTypes")
|
||||
}
|
||||
|
||||
|
||||
return PrefixPlusMinusCaret(options)
|
||||
},
|
||||
},
|
||||
|
@ -18,5 +18,8 @@ func TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeText
|
||||
ClearDiagnostics(context, params.TextDocument.URI)
|
||||
}
|
||||
|
||||
analyzeErrors := AnalyzeValue()
|
||||
SendDiagnosticsFromAnalyzerErrors(context, params.TextDocument.URI, analyzeErrors)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user