diff --git a/doc-values/base-value.go b/doc-values/base-value.go index 402c3de..1935d62 100644 --- a/doc-values/base-value.go +++ b/doc-values/base-value.go @@ -1,11 +1,68 @@ package docvalues import ( + "config-lsp/utils" + protocol "github.com/tliron/glsp/protocol_3_16" ) type Value interface { GetTypeDescription() []string - CheckIsValid(value string) error + CheckIsValid(value string) []*InvalidValue FetchCompletions(line string, cursor uint32) []protocol.CompletionItem } + +type InvalidValue struct { + Err error + Start uint32 + End uint32 +} + +func (v *InvalidValue) Shift(offset uint32) { + v.Start += offset + v.End += offset +} + +func (v *InvalidValue) GetRange(line uint32, characterStart uint32) protocol.Range { + return protocol.Range{ + Start: protocol.Position{ + Line: line, + Character: characterStart + v.Start, + }, + End: protocol.Position{ + Line: line, + Character: characterStart + v.End, + }, + } +} + +func (v *InvalidValue) GetMessage() string { + return v.Err.Error() +} + +func ShiftInvalidValues(offset uint32, invalidValues []*InvalidValue) { + if len(invalidValues) > 0 { + for _, invalidValue := range invalidValues { + invalidValue.Shift(offset) + } + } +} + +func InvalidValuesToErrorDiagnostics( + line uint32, + offset uint32, + values []*InvalidValue, +) []protocol.Diagnostic { + severity := protocol.DiagnosticSeverityError + + return utils.Map( + values, + func(value *InvalidValue) protocol.Diagnostic { + return protocol.Diagnostic{ + Range: value.GetRange(line, offset), + Severity: &severity, + Message: value.GetMessage(), + } + }, + ) +} diff --git a/doc-values/utils.go b/doc-values/utils.go index a016cda..772e215 100644 --- a/doc-values/utils.go +++ b/doc-values/utils.go @@ -2,7 +2,9 @@ package docvalues import ( "config-lsp/utils" + protocol "github.com/tliron/glsp/protocol_3_16" + "golang.org/x/exp/slices" ) func GenerateBase10Completions(prefix string) []protocol.CompletionItem { @@ -18,3 +20,24 @@ func GenerateBase10Completions(prefix string) []protocol.CompletionItem { }, ) } + +func MergeKeyEnumAssignmentMaps(maps ...map[EnumString]Value) map[EnumString]Value { + existingEnums := make(map[string]interface{}) + result := make(map[EnumString]Value) + + slices.Reverse(maps) + + for _, m := range maps { + for key, value := range m { + if _, ok := existingEnums[key.InsertText]; !ok { + continue + } + + existingEnums[key.InsertText] = nil + + result[key] = value + } + } + + return result +} diff --git a/doc-values/value-array.go b/doc-values/value-array.go index 1223694..23fc486 100644 --- a/doc-values/value-array.go +++ b/doc-values/value-array.go @@ -9,11 +9,11 @@ import ( ) type ArrayContainsDuplicatesError struct { - Duplicates []string + Value string } func (e ArrayContainsDuplicatesError) Error() string { - return fmt.Sprintf("The following values are duplicated: %s", strings.Join(e.Duplicates, ",")) + return fmt.Sprintf("'%s' is a duplicate value (and duplicates are not allowed)", e.Value) } var SimpleDuplicatesExtractor = func(value string) string { @@ -52,7 +52,9 @@ func (v ArrayValue) GetTypeDescription() []string { subValue.GetTypeDescription()..., ) } -func (v ArrayValue) CheckIsValid(value string) error { + +func (v ArrayValue) CheckIsValid(value string) []*InvalidValue { + errors := []*InvalidValue{} values := strings.Split(value, v.Separator) if *v.DuplicatesExtractor != nil { @@ -68,25 +70,48 @@ func (v ArrayValue) CheckIsValid(value string) error { valuesOccurrences[duplicateValue]++ } - duplicateValues := utils.FilterMapWhere(valuesOccurrences, func(_ string, value int) bool { + duplicateValuesAsList := utils.FilterMapWhere(valuesOccurrences, func(_ string, value int) bool { return value > 1 }) + duplicateValues := utils.KeysAsSet(duplicateValuesAsList) - return ArrayContainsDuplicatesError{ - Duplicates: utils.KeysOfMap(duplicateValues), + duplicateIndexStart := uint32(0) + duplicateIndexEnd := uint32(0) + + currentIndex := uint32(0) + for _, rawValue := range values { + if _, found := duplicateValues[rawValue]; found { + duplicateIndexStart = currentIndex + duplicateIndexEnd = currentIndex + uint32(len(rawValue)) + + errors = append(errors, &InvalidValue{ + Err: ArrayContainsDuplicatesError{ + Value: rawValue, + }, + Start: duplicateIndexStart, + End: duplicateIndexEnd, + }) + } } + + return errors } } + currentIndex := uint32(0) for _, subValue := range values { - err := v.SubValue.CheckIsValid(subValue) + newErrors := v.SubValue.CheckIsValid(subValue) - if err != nil { - return err + if len(newErrors) > 0 { + ShiftInvalidValues(currentIndex, newErrors) } + + errors = append(errors, newErrors...) + + currentIndex += uint32(len(subValue) + len(v.Separator)) } - return nil + return errors } func (v ArrayValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem { diff --git a/doc-values/value-custom.go b/doc-values/value-custom.go index 9fdc464..3f255db 100644 --- a/doc-values/value-custom.go +++ b/doc-values/value-custom.go @@ -24,7 +24,7 @@ func (v CustomValue) GetTypeDescription() []string { return []string{"Custom"} } -func (v CustomValue) CheckIsValid(value string) error { +func (v CustomValue) CheckIsValid(value string) []*InvalidValue { return v.FetchValue(EmptyValueContextInstance).CheckIsValid(value) } diff --git a/doc-values/value-enum.go b/doc-values/value-enum.go index 6a21c4e..b180bf9 100644 --- a/doc-values/value-enum.go +++ b/doc-values/value-enum.go @@ -14,7 +14,11 @@ type ValueNotInEnumError struct { } func (e ValueNotInEnumError) Error() string { - return fmt.Sprintf("This value is not valid. Select one from: %s", strings.Join(e.AvailableValues, ",")) + if len(e.AvailableValues) <= 6 { + return fmt.Sprintf("This value is not valid. Select one from: %s", strings.Join(e.AvailableValues, ",")) + } else { + return fmt.Sprintf("This value is not valid") + } } type EnumString struct { @@ -63,7 +67,7 @@ func (v EnumValue) GetTypeDescription() []string { return lines } -func (v EnumValue) CheckIsValid(value string) error { +func (v EnumValue) CheckIsValid(value string) []*InvalidValue { if !v.EnforceValues { return nil } @@ -75,9 +79,15 @@ func (v EnumValue) CheckIsValid(value string) error { } - return ValueNotInEnumError{ - ProvidedValue: value, - AvailableValues: utils.Map(v.Values, func(value EnumString) string { return value.InsertText }), + return []*InvalidValue{ + { + Err: ValueNotInEnumError{ + ProvidedValue: value, + AvailableValues: utils.Map(v.Values, func(value EnumString) string { return value.InsertText }), + }, + Start: 0, + End: uint32(len(value)), + }, } } func (v EnumValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem { diff --git a/doc-values/value-ip-address.go b/doc-values/value-ip-address.go index aa9b44c..939c3be 100644 --- a/doc-values/value-ip-address.go +++ b/doc-values/value-ip-address.go @@ -63,14 +63,20 @@ func (v IPAddressValue) GetTypeDescription() []string { return []string{"An IP Address"} } -func (v IPAddressValue) CheckIsValid(value string) error { +func (v IPAddressValue) CheckIsValid(value string) []*InvalidValue { var ip net.Prefix if v.AllowRange { rawIP, err := net.ParsePrefix(value) if err != nil { - return InvalidIPAddress{} + return []*InvalidValue{ + { + Err: InvalidIPAddress{}, + Start: 0, + End: uint32(len(value)), + }, + } } ip = rawIP @@ -78,14 +84,24 @@ func (v IPAddressValue) CheckIsValid(value string) error { rawIP, err := net.ParseAddr(value) if err != nil { - return InvalidIPAddress{} + return []*InvalidValue{{ + Err: InvalidIPAddress{}, + Start: 0, + End: uint32(len(value)), + }, + } } ip = net.PrefixFrom(rawIP, 32) } if !ip.IsValid() { - return InvalidIPAddress{} + return []*InvalidValue{{ + Err: InvalidIPAddress{}, + Start: 0, + End: uint32(len(value)), + }, + } } if v.AllowedIPs != nil { @@ -95,13 +111,23 @@ func (v IPAddressValue) CheckIsValid(value string) error { } } - return IpRangeNotAllowedError{} + return []*InvalidValue{{ + Err: IPAddressNotAllowedError{}, + Start: 0, + End: uint32(len(value)), + }, + } } if v.DisallowedIPs != nil { for _, disallowedIP := range *v.DisallowedIPs { if disallowedIP.Contains(ip.Addr()) { - return IPAddressNotAllowedError{} + return []*InvalidValue{{ + Err: IPAddressNotAllowedError{}, + Start: 0, + End: uint32(len(value)), + }, + } } } } @@ -114,7 +140,12 @@ func (v IPAddressValue) CheckIsValid(value string) error { return nil } - return InvalidIPAddress{} + return []*InvalidValue{{ + Err: InvalidIPAddress{}, + Start: 0, + End: uint32(len(value)), + }, + } } func (v IPAddressValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem { diff --git a/doc-values/value-key-enum-assignment.go b/doc-values/value-key-enum-assignment.go index 9e53e80..226583d 100644 --- a/doc-values/value-key-enum-assignment.go +++ b/doc-values/value-key-enum-assignment.go @@ -9,8 +9,8 @@ import ( ) type KeyEnumAssignmentValue struct { - Values map[EnumString]Value - Separator string + Values map[EnumString]Value + Separator string ValueIsOptional bool } @@ -19,13 +19,12 @@ func (v KeyEnumAssignmentValue) GetTypeDescription() []string { firstKey := utils.KeysOfMap(v.Values)[0] valueDescription := v.Values[firstKey].GetTypeDescription() - if (len(valueDescription) == 1) { + if len(valueDescription) == 1 { return []string{ fmt.Sprintf("Key-Value pair in form of '<%s>%s<%s>'", firstKey.DescriptionText, v.Separator, valueDescription[0]), } } - } - + } var result []string for key, value := range v.Values { @@ -42,17 +41,17 @@ func (v KeyEnumAssignmentValue) getValue(findKey string) (*Value, bool) { for key, value := range v.Values { if key.InsertText == findKey { switch value.(type) { - case CustomValue: - customValue := value.(CustomValue) - context := KeyValueAssignmentContext{ - SelectedKey: findKey, - } + case CustomValue: + customValue := value.(CustomValue) + context := KeyValueAssignmentContext{ + SelectedKey: findKey, + } - fetchedValue := customValue.FetchValue(context) + fetchedValue := customValue.FetchValue(context) - return &fetchedValue, true - default: - return &value, true + return &fetchedValue, true + default: + return &value, true } } } @@ -60,7 +59,7 @@ func (v KeyEnumAssignmentValue) getValue(findKey string) (*Value, bool) { return nil, false } -func (v KeyEnumAssignmentValue) CheckIsValid(value string) error { +func (v KeyEnumAssignmentValue) CheckIsValid(value string) []*InvalidValue { parts := strings.Split(value, v.Separator) if len(parts) == 0 || parts[0] == "" { @@ -73,22 +72,35 @@ func (v KeyEnumAssignmentValue) CheckIsValid(value string) error { return nil } - return KeyValueAssignmentError{} + return []*InvalidValue{ + { + Err: KeyValueAssignmentError{}, + Start: 0, + End: uint32(len(parts[0]) + len(v.Separator)), + }, + } } checkValue, found := v.getValue(parts[0]) if !found { - return ValueNotInEnumError{ - AvailableValues: utils.Map(utils.KeysOfMap(v.Values), func(key EnumString) string { return key.InsertText }), - ProvidedValue: parts[0], + return []*InvalidValue{ + { + Err: ValueNotInEnumError{ + AvailableValues: utils.Map(utils.KeysOfMap(v.Values), func(key EnumString) string { return key.InsertText }), + ProvidedValue: parts[0], + }, + Start: 0, + End: uint32(len(parts[0])), + }, } } - err := (*checkValue).CheckIsValid(parts[1]) + errors := (*checkValue).CheckIsValid(parts[1]) - if err != nil { - return err + if len(errors) > 0 { + ShiftInvalidValues(uint32(len(parts[0])+len(v.Separator)), errors) + return errors } return nil diff --git a/doc-values/value-key-value-assignment.go b/doc-values/value-key-value-assignment.go index e098929..deb31e2 100644 --- a/doc-values/value-key-value-assignment.go +++ b/doc-values/value-key-value-assignment.go @@ -65,7 +65,7 @@ func (v KeyValueAssignmentValue) getValue(selectedKey string) Value { } } -func (v KeyValueAssignmentValue) CheckIsValid(value string) error { +func (v KeyValueAssignmentValue) CheckIsValid(value string) []*InvalidValue { parts := strings.Split(value, v.Separator) if len(parts) == 0 || parts[0] == "" { @@ -84,13 +84,20 @@ func (v KeyValueAssignmentValue) CheckIsValid(value string) error { return nil } - return KeyValueAssignmentError{} + return []*InvalidValue{ + { + Err: KeyValueAssignmentError{}, + Start: 0, + End: uint32(len(parts[0]) + len(v.Separator)), + }, + } } - err = v.getValue(parts[0]).CheckIsValid(parts[1]) + errors := v.getValue(parts[0]).CheckIsValid(parts[1]) - if err != nil { - return err + if len(errors) > 0 { + ShiftInvalidValues(uint32(len(parts[0])+len(v.Separator)), errors) + return errors } return nil diff --git a/doc-values/value-number.go b/doc-values/value-number.go index 6cbc23e..b2f05b4 100644 --- a/doc-values/value-number.go +++ b/doc-values/value-number.go @@ -48,15 +48,27 @@ func (v NumberValue) GetTypeDescription() []string { return []string{"A number"} } -func (v NumberValue) CheckIsValid(value string) error { +func (v NumberValue) CheckIsValid(value string) []*InvalidValue { number, err := strconv.Atoi(value) if err != nil { - return NotANumberError{} + return []*InvalidValue{ + { + Err: NotANumberError{}, + Start: 0, + End: uint32(len(value)), + }, + } } if (v.Min != nil && number < *v.Min) || (v.Max != nil && number > *v.Max) { - return NumberNotInRangeError{v.Min, v.Max} + return []*InvalidValue{ + { + Err: NumberNotInRangeError{v.Min, v.Max}, + Start: 0, + End: uint32(len(value)), + }, + } } return nil diff --git a/doc-values/value-or.go b/doc-values/value-or.go index 711f20d..296d7eb 100644 --- a/doc-values/value-or.go +++ b/doc-values/value-or.go @@ -33,20 +33,20 @@ func (v OrValue) GetTypeDescription() []string { lines..., ) } -func (v OrValue) CheckIsValid(value string) error { - var lastError error = nil +func (v OrValue) CheckIsValid(value string) []*InvalidValue { + errors := make([]*InvalidValue, 0) for _, subValue := range v.Values { - err := subValue.CheckIsValid(value) + valueErrors := subValue.CheckIsValid(value) - if err == nil { + if len(valueErrors) == 0 { return nil - } else { - lastError = err } + + errors = append(errors, valueErrors...) } - return lastError + return errors } func (v OrValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem { completions := make([]protocol.CompletionItem, 0) diff --git a/doc-values/value-path.go b/doc-values/value-path.go index b07072e..ef22d26 100644 --- a/doc-values/value-path.go +++ b/doc-values/value-path.go @@ -46,9 +46,14 @@ func (v PathValue) GetTypeDescription() []string { return []string{strings.Join(hints, ", ")} } -func (v PathValue) CheckIsValid(value string) error { +func (v PathValue) CheckIsValid(value string) []*InvalidValue { if !utils.DoesPathExist(value) { - return PathDoesNotExistError{} + return []*InvalidValue{{ + Err: PathDoesNotExistError{}, + Start: 0, + End: uint32(len(value)), + }, + } } isValid := false @@ -65,7 +70,12 @@ func (v PathValue) CheckIsValid(value string) error { return nil } - return PathInvalidError{} + return []*InvalidValue{{ + Err: PathInvalidError{}, + Start: 0, + End: uint32(len(value)), + }, + } } func (v PathValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem { diff --git a/doc-values/value-power-of-two.go b/doc-values/value-power-of-two.go new file mode 100644 index 0000000..8cf1d12 --- /dev/null +++ b/doc-values/value-power-of-two.go @@ -0,0 +1,77 @@ +package docvalues + +import ( + "config-lsp/utils" + "strconv" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +type NotAPowerOfTwoError struct{} + +func (e NotAPowerOfTwoError) Error() string { + return "This must be a power of 2 (e.g. 32, 64, 128, 265, 512, 1024,, 1024, ...)" +} + +type PowerOfTwoValue struct{} + +func (v PowerOfTwoValue) GetTypeDescription() []string { + return []string{"A power of 2"} +} + +func isPowerOfTwo(number int) bool { + count := 0 + + for number > 0 { + count += number & 1 + number >>= 1 + + if count > 1 { + return false + } + } + + return true +} + +func (v PowerOfTwoValue) CheckIsValid(value string) []*InvalidValue { + number, err := strconv.Atoi(value) + + if err != nil { + return []*InvalidValue{{ + Err: NotANumberError{}, + Start: 0, + End: uint32(len(value)), + }, + } + } + + if number <= 0 || !isPowerOfTwo(number) { + return []*InvalidValue{{ + Err: NotAPowerOfTwoError{}, + Start: 0, + End: uint32(len(value)), + }, + } + } + + return nil +} + +var powers = []int{1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536} + +func (v PowerOfTwoValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem { + textFormat := protocol.InsertTextFormatPlainText + kind := protocol.CompletionItemKindEnum + + return utils.Map( + powers, + func(power int) protocol.CompletionItem { + return protocol.CompletionItem{ + Label: strconv.Itoa(power), + InsertTextFormat: &textFormat, + Kind: &kind, + } + }, + ) +} diff --git a/doc-values/value-prefix.go b/doc-values/value-prefix.go index b9abb2b..7854f7e 100644 --- a/doc-values/value-prefix.go +++ b/doc-values/value-prefix.go @@ -30,7 +30,7 @@ func (v PrefixWithMeaningValue) GetTypeDescription() []string { ) } -func (v PrefixWithMeaningValue) CheckIsValid(value string) error { +func (v PrefixWithMeaningValue) CheckIsValid(value string) []*InvalidValue { return v.SubValue.CheckIsValid(value) } diff --git a/doc-values/value-regex.go b/doc-values/value-regex.go index b48765d..2d5fbfc 100644 --- a/doc-values/value-regex.go +++ b/doc-values/value-regex.go @@ -25,9 +25,14 @@ func (v RegexValue) GetTypeDescription() []string { } } -func (v RegexValue) CheckIsValid(value string) error { +func (v RegexValue) CheckIsValid(value string) []*InvalidValue { if value == "" { - return EmptyStringError{} + return []*InvalidValue{{ + Err: EmptyStringError{}, + Start: 0, + End: uint32(len(value)), + }, + } } return nil diff --git a/doc-values/value-string.go b/doc-values/value-string.go index e0b97ca..f5f8b7c 100644 --- a/doc-values/value-string.go +++ b/doc-values/value-string.go @@ -16,9 +16,14 @@ func (v StringValue) GetTypeDescription() []string { return []string{"String"} } -func (v StringValue) CheckIsValid(value string) error { +func (v StringValue) CheckIsValid(value string) []*InvalidValue { if value == "" { - return EmptyStringError{} + return []*InvalidValue{{ + Err: EmptyStringError{}, + Start: 0, + End: uint32(len(value)), + }, + } } return nil