mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +02:00
feat(server): Add Wireguard code actions: Fix typo
This commit is contained in:
parent
c5fefad56d
commit
e4d7521a4c
@ -5,9 +5,9 @@ import (
|
|||||||
sshconfig "config-lsp/handlers/ssh_config"
|
sshconfig "config-lsp/handlers/ssh_config"
|
||||||
"config-lsp/handlers/ssh_config/diagnostics"
|
"config-lsp/handlers/ssh_config/diagnostics"
|
||||||
"config-lsp/handlers/ssh_config/fields"
|
"config-lsp/handlers/ssh_config/fields"
|
||||||
|
"config-lsp/utils"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hbollon/go-edlib"
|
|
||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,7 +24,8 @@ func getKeywordTypoFixes(
|
|||||||
if typoOption, found := d.Indexes.UnknownOptions[line]; found {
|
if typoOption, found := d.Indexes.UnknownOptions[line]; found {
|
||||||
name := typoOption.Option.Key.Value.Value
|
name := typoOption.Option.Key.Value.Value
|
||||||
|
|
||||||
suggestedOptions := findSimilarOptions(name)
|
opts := utils.KeysOfMap(fields.Options)
|
||||||
|
suggestedOptions := common.FindSimilarItems(fields.CreateNormalizedName(name), opts)
|
||||||
|
|
||||||
actions := make([]protocol.CodeAction, 0, len(suggestedOptions))
|
actions := make([]protocol.CodeAction, 0, len(suggestedOptions))
|
||||||
|
|
||||||
@ -61,43 +62,3 @@ func getKeywordTypoFixes(
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find options that are similar to the given option name.
|
|
||||||
// This is used to find typos & suggest the correct option name.
|
|
||||||
// Once an option is found that has a Damerau-Levenshtein distance of 1, it is immediately returned.
|
|
||||||
// If not, then the next 2 options of similarity 2, or 3 options of similarity 3 are returned.
|
|
||||||
// If no options with similarity <= 3 are found, then an empty slice is returned.
|
|
||||||
func findSimilarOptions(
|
|
||||||
optionName string,
|
|
||||||
) []fields.NormalizedOptionName {
|
|
||||||
normalizedOptionName := string(fields.CreateNormalizedName(optionName))
|
|
||||||
|
|
||||||
optionsPerSimilarity := map[uint8][]fields.NormalizedOptionName{
|
|
||||||
2: make([]fields.NormalizedOptionName, 0, 2),
|
|
||||||
3: make([]fields.NormalizedOptionName, 0, 3),
|
|
||||||
}
|
|
||||||
|
|
||||||
for name := range fields.Options {
|
|
||||||
normalizedName := string(name)
|
|
||||||
similarity := edlib.DamerauLevenshteinDistance(normalizedName, normalizedOptionName)
|
|
||||||
|
|
||||||
switch similarity {
|
|
||||||
case 1:
|
|
||||||
return []fields.NormalizedOptionName{name}
|
|
||||||
case 2:
|
|
||||||
optionsPerSimilarity[2] = append(optionsPerSimilarity[2], name)
|
|
||||||
|
|
||||||
if len(optionsPerSimilarity[2]) >= 2 {
|
|
||||||
return optionsPerSimilarity[2]
|
|
||||||
}
|
|
||||||
case 3:
|
|
||||||
optionsPerSimilarity[3] = append(optionsPerSimilarity[3], name)
|
|
||||||
|
|
||||||
if len(optionsPerSimilarity[3]) >= 3 {
|
|
||||||
return optionsPerSimilarity[3]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(optionsPerSimilarity[2], optionsPerSimilarity[3]...)
|
|
||||||
}
|
|
||||||
|
@ -35,6 +35,12 @@ func Analyze(
|
|||||||
|
|
||||||
d.Indexes = i
|
d.Indexes = i
|
||||||
|
|
||||||
|
analyzeProperties(ctx)
|
||||||
|
|
||||||
|
if len(ctx.diagnostics) > 0 {
|
||||||
|
return ctx.diagnostics
|
||||||
|
}
|
||||||
|
|
||||||
analyzeInterfaceSection(ctx)
|
analyzeInterfaceSection(ctx)
|
||||||
analyzeDNSPropertyContainsFallback(ctx)
|
analyzeDNSPropertyContainsFallback(ctx)
|
||||||
analyzeKeepAlivePropertyIsSet(ctx)
|
analyzeKeepAlivePropertyIsSet(ctx)
|
||||||
|
89
server/handlers/wireguard/analyzer/properties.go
Normal file
89
server/handlers/wireguard/analyzer/properties.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package analyzer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
docvalues "config-lsp/doc-values"
|
||||||
|
"config-lsp/handlers/wireguard/ast"
|
||||||
|
"config-lsp/handlers/wireguard/diagnostics"
|
||||||
|
"config-lsp/handlers/wireguard/fields"
|
||||||
|
"config-lsp/handlers/wireguard/indexes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func analyzeProperties(
|
||||||
|
ctx *analyzerContext,
|
||||||
|
) {
|
||||||
|
for _, section := range ctx.document.Config.Sections {
|
||||||
|
normalizedHeaderName := fields.CreateNormalizedName(section.Header.Name)
|
||||||
|
|
||||||
|
// Whether to check if the property is allowed in the section
|
||||||
|
checkAllowedProperty := true
|
||||||
|
existingProperties := make(map[fields.NormalizedName]*ast.WGProperty)
|
||||||
|
|
||||||
|
it := section.Properties.Iterator()
|
||||||
|
for it.Next() {
|
||||||
|
property := it.Value().(*ast.WGProperty)
|
||||||
|
normalizedPropertyName := fields.CreateNormalizedName(property.Key.Name)
|
||||||
|
|
||||||
|
if property.Key.Name == "" {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: "This property is missing a name",
|
||||||
|
Range: property.Key.ToLSPRange(),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if property.Value == nil || property.Value.Value == "" {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: "This property is missing a value",
|
||||||
|
Range: property.ToLSPRange(),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
|
})
|
||||||
|
checkAllowedProperty = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkAllowedProperty {
|
||||||
|
availableOptions := fields.OptionsHeaderMap[normalizedHeaderName]
|
||||||
|
|
||||||
|
// Duplicate check
|
||||||
|
if existingProperty, found := existingProperties[normalizedPropertyName]; found {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Message: fmt.Sprintf("Property '%s' has already been defined on line %d", property.Key.Name, existingProperty.Start.Line+1),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
|
Range: existingProperty.ToLSPRange(),
|
||||||
|
})
|
||||||
|
// Check if value is valid
|
||||||
|
} else if option, found := availableOptions[normalizedPropertyName]; found {
|
||||||
|
invalidValues := option.DeprecatedCheckIsValid(property.Value.Value)
|
||||||
|
|
||||||
|
for _, invalidValue := range invalidValues {
|
||||||
|
err := docvalues.LSPErrorFromInvalidValue(property.Start.Line, *invalidValue).ShiftCharacter(property.Value.Start.Character)
|
||||||
|
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
|
Range: err.Range.ToLSPRange(),
|
||||||
|
Message: err.Err.Error(),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Unknown property
|
||||||
|
} else {
|
||||||
|
ctx.diagnostics = append(ctx.diagnostics,
|
||||||
|
diagnostics.GenerateUnknownOption(
|
||||||
|
property.ToLSPRange(),
|
||||||
|
property.Key.Name,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.document.Indexes.UnknownProperties[property.Key.Start.Line] = indexes.WGIndexPropertyInfo{
|
||||||
|
Section: section,
|
||||||
|
Property: property,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
existingProperties[normalizedPropertyName] = property
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,6 @@ package analyzer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"config-lsp/common"
|
"config-lsp/common"
|
||||||
docvalues "config-lsp/doc-values"
|
|
||||||
"config-lsp/handlers/wireguard/ast"
|
|
||||||
"config-lsp/handlers/wireguard/fields"
|
"config-lsp/handlers/wireguard/fields"
|
||||||
"config-lsp/utils"
|
"config-lsp/utils"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -14,8 +12,6 @@ import (
|
|||||||
func analyzeStructureIsValid(ctx *analyzerContext) {
|
func analyzeStructureIsValid(ctx *analyzerContext) {
|
||||||
for _, section := range ctx.document.Config.Sections {
|
for _, section := range ctx.document.Config.Sections {
|
||||||
normalizedHeaderName := fields.CreateNormalizedName(section.Header.Name)
|
normalizedHeaderName := fields.CreateNormalizedName(section.Header.Name)
|
||||||
// Whether to check if the property is allowed in the section
|
|
||||||
checkAllowedProperty := true
|
|
||||||
|
|
||||||
if section.Header.Name == "" {
|
if section.Header.Name == "" {
|
||||||
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
||||||
@ -29,8 +25,6 @@ func analyzeStructureIsValid(ctx *analyzerContext) {
|
|||||||
Range: section.Header.ToLSPRange(),
|
Range: section.Header.ToLSPRange(),
|
||||||
Severity: &common.SeverityError,
|
Severity: &common.SeverityError,
|
||||||
})
|
})
|
||||||
// Do not check as the section is unknown
|
|
||||||
checkAllowedProperty = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if section.Properties.Size() == 0 {
|
if section.Properties.Size() == 0 {
|
||||||
@ -42,66 +36,6 @@ func analyzeStructureIsValid(ctx *analyzerContext) {
|
|||||||
protocol.DiagnosticTagUnnecessary,
|
protocol.DiagnosticTagUnnecessary,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
existingProperties := make(map[fields.NormalizedName]*ast.WGProperty)
|
|
||||||
|
|
||||||
it := section.Properties.Iterator()
|
|
||||||
for it.Next() {
|
|
||||||
property := it.Value().(*ast.WGProperty)
|
|
||||||
normalizedPropertyName := fields.CreateNormalizedName(property.Key.Name)
|
|
||||||
|
|
||||||
if property.Key.Name == "" {
|
|
||||||
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
|
||||||
Message: "This property is missing a name",
|
|
||||||
Range: property.Key.ToLSPRange(),
|
|
||||||
Severity: &common.SeverityError,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if property.Value == nil || property.Value.Value == "" {
|
|
||||||
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
|
||||||
Message: "This property is missing a value",
|
|
||||||
Range: property.ToLSPRange(),
|
|
||||||
Severity: &common.SeverityError,
|
|
||||||
})
|
|
||||||
checkAllowedProperty = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if checkAllowedProperty {
|
|
||||||
availableOptions := fields.OptionsHeaderMap[normalizedHeaderName]
|
|
||||||
|
|
||||||
// Duplicate check
|
|
||||||
if existingProperty, found := existingProperties[normalizedPropertyName]; found {
|
|
||||||
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
|
||||||
Message: fmt.Sprintf("Property '%s' has already been defined on line %d", property.Key.Name, existingProperty.Start.Line+1),
|
|
||||||
Severity: &common.SeverityError,
|
|
||||||
Range: existingProperty.ToLSPRange(),
|
|
||||||
})
|
|
||||||
// Check if value is valid
|
|
||||||
} else if option, found := availableOptions[normalizedPropertyName]; found {
|
|
||||||
invalidValues := option.DeprecatedCheckIsValid(property.Value.Value)
|
|
||||||
|
|
||||||
for _, invalidValue := range invalidValues {
|
|
||||||
err := docvalues.LSPErrorFromInvalidValue(property.Start.Line, *invalidValue).ShiftCharacter(property.Value.Start.Character)
|
|
||||||
|
|
||||||
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
|
||||||
Range: err.Range.ToLSPRange(),
|
|
||||||
Message: err.Err.Error(),
|
|
||||||
Severity: &common.SeverityError,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// Unknown property
|
|
||||||
} else {
|
|
||||||
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
|
|
||||||
Message: fmt.Sprintf("Unknown property '%s'", property.Key.Name),
|
|
||||||
Range: property.Key.ToLSPRange(),
|
|
||||||
Severity: &common.SeverityError,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
existingProperties[normalizedPropertyName] = property
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
server/handlers/wireguard/diagnostics/diagnostics.go
Normal file
19
server/handlers/wireguard/diagnostics/diagnostics.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package diagnostics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateUnknownOption(
|
||||||
|
diagnosticRange protocol.Range,
|
||||||
|
propertyName string,
|
||||||
|
) protocol.Diagnostic {
|
||||||
|
return protocol.Diagnostic{
|
||||||
|
Range: diagnosticRange,
|
||||||
|
Message: fmt.Sprintf("Unknown property: %s", propertyName),
|
||||||
|
Severity: &common.SeverityError,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
"config-lsp/handlers/wireguard"
|
||||||
|
"config-lsp/handlers/wireguard/diagnostics"
|
||||||
|
"config-lsp/handlers/wireguard/fields"
|
||||||
|
"config-lsp/utils"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetPropertyKeywordTypoFixes(
|
||||||
|
d *wireguard.WGDocument,
|
||||||
|
params *protocol.CodeActionParams,
|
||||||
|
) []protocol.CodeAction {
|
||||||
|
if common.ServerOptions.NoTypoSuggestions {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
line := params.Range.Start.Line
|
||||||
|
|
||||||
|
if typoInfo, found := d.Indexes.UnknownProperties[line]; found {
|
||||||
|
if options, found := fields.OptionsHeaderMap[fields.CreateNormalizedName(typoInfo.Section.Header.Name)]; found {
|
||||||
|
normalizedPropertyKey := fields.CreateNormalizedName(typoInfo.Property.Key.Name)
|
||||||
|
opts := utils.KeysOfMap(options)
|
||||||
|
|
||||||
|
suggestedProperties := common.FindSimilarItems(normalizedPropertyKey, opts)
|
||||||
|
|
||||||
|
actions := make([]protocol.CodeAction, 0, len(suggestedProperties))
|
||||||
|
|
||||||
|
kind := protocol.CodeActionKindQuickFix
|
||||||
|
for index, normalizedPropertyName := range suggestedProperties {
|
||||||
|
isPreferred := index == 0
|
||||||
|
optionName := fields.AllOptionsFormatted[normalizedPropertyName]
|
||||||
|
|
||||||
|
actions = append(actions, protocol.CodeAction{
|
||||||
|
Title: fmt.Sprintf("Typo Fix: %s", optionName),
|
||||||
|
IsPreferred: &isPreferred,
|
||||||
|
Kind: &kind,
|
||||||
|
Diagnostics: []protocol.Diagnostic{
|
||||||
|
diagnostics.GenerateUnknownOption(
|
||||||
|
typoInfo.Property.ToLSPRange(),
|
||||||
|
typoInfo.Property.Key.Name,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Edit: &protocol.WorkspaceEdit{
|
||||||
|
Changes: map[protocol.DocumentUri][]protocol.TextEdit{
|
||||||
|
params.TextDocument.URI: {
|
||||||
|
{
|
||||||
|
Range: typoInfo.Property.Key.ToLSPRange(),
|
||||||
|
NewText: optionName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -2,7 +2,15 @@ package indexes
|
|||||||
|
|
||||||
import "config-lsp/handlers/wireguard/ast"
|
import "config-lsp/handlers/wireguard/ast"
|
||||||
|
|
||||||
type WGIndexes struct {
|
type WGIndexPropertyInfo struct {
|
||||||
// map of: section name -> WGSection
|
Section *ast.WGSection
|
||||||
SectionsByName map[string][]*ast.WGSection
|
Property *ast.WGProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
type WGIndexes struct {
|
||||||
|
// map of: section name -> *WGSection
|
||||||
|
SectionsByName map[string][]*ast.WGSection
|
||||||
|
|
||||||
|
// map of: line number -> *WGIndexPropertyInfo
|
||||||
|
UnknownProperties map[uint32]WGIndexPropertyInfo
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ func CreateIndexes(config *ast.WGConfig) (*WGIndexes, []common.LSPError) {
|
|||||||
errs := make([]common.LSPError, 0)
|
errs := make([]common.LSPError, 0)
|
||||||
indexes := &WGIndexes{
|
indexes := &WGIndexes{
|
||||||
SectionsByName: make(map[string][]*ast.WGSection),
|
SectionsByName: make(map[string][]*ast.WGSection),
|
||||||
|
UnknownProperties: make(map[uint32]WGIndexPropertyInfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, section := range config.Sections {
|
for _, section := range config.Sections {
|
||||||
|
@ -16,6 +16,7 @@ func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionPa
|
|||||||
actions = append(actions, handlers.GetKeyGenerationCodeActions(d, params)...)
|
actions = append(actions, handlers.GetKeyGenerationCodeActions(d, params)...)
|
||||||
actions = append(actions, handlers.GetKeepaliveCodeActions(d, params)...)
|
actions = append(actions, handlers.GetKeepaliveCodeActions(d, params)...)
|
||||||
actions = append(actions, handlers.GetAddPeerLikeThis(d, params)...)
|
actions = append(actions, handlers.GetAddPeerLikeThis(d, params)...)
|
||||||
|
actions = append(actions, handlers.GetPropertyKeywordTypoFixes(d, params)...)
|
||||||
|
|
||||||
if len(actions) > 0 {
|
if len(actions) > 0 {
|
||||||
return actions, nil
|
return actions, nil
|
||||||
|
Loading…
x
Reference in New Issue
Block a user