mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +02:00
refactor(server): Refactor Wireguard config; Improve completions + bunch of other stuff
Signed-off-by: Myzel394 <github.7a2op@simplelogin.co>
This commit is contained in:
parent
020cc8ad67
commit
a0dca94b9d
@ -1,7 +1,8 @@
|
|||||||
package analyzer
|
package analyzer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"config-lsp/handlers/wireguard/parser"
|
"config-lsp/handlers/wireguard"
|
||||||
|
"config-lsp/handlers/wireguard/ast"
|
||||||
"config-lsp/utils"
|
"config-lsp/utils"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@ -14,12 +15,14 @@ PrivateKey = abc
|
|||||||
[Interface]
|
[Interface]
|
||||||
PrivateKey = def
|
PrivateKey = def
|
||||||
`)
|
`)
|
||||||
p := parser.CreateWireguardParser()
|
d := &wireguard.WGDocument{
|
||||||
p.ParseFromString(content)
|
Config: ast.NewWGConfig(),
|
||||||
|
}
|
||||||
|
d.Config.Parse(content)
|
||||||
|
|
||||||
diagnostics := Analyze(p)
|
diagnostics := Analyze(d)
|
||||||
|
|
||||||
if len(diagnostics) == 0 {
|
if !(len(diagnostics) > 0) {
|
||||||
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
|
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -29,12 +32,14 @@ func TestInvalidValue(t *testing.T) {
|
|||||||
[Interface]
|
[Interface]
|
||||||
DNS = nope
|
DNS = nope
|
||||||
`)
|
`)
|
||||||
p := parser.CreateWireguardParser()
|
d := &wireguard.WGDocument{
|
||||||
p.ParseFromString(content)
|
Config: ast.NewWGConfig(),
|
||||||
|
}
|
||||||
|
d.Config.Parse(content)
|
||||||
|
|
||||||
diagnostics := Analyze(p)
|
diagnostics := Analyze(d)
|
||||||
|
|
||||||
if len(diagnostics) == 0 {
|
if !(len(diagnostics) > 0) {
|
||||||
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
|
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,12 +51,15 @@ PrivateKey = abc
|
|||||||
DNS = 1.1.1.1
|
DNS = 1.1.1.1
|
||||||
PrivateKey = def
|
PrivateKey = def
|
||||||
`)
|
`)
|
||||||
p := parser.CreateWireguardParser()
|
|
||||||
p.ParseFromString(content)
|
|
||||||
|
|
||||||
diagnostics := Analyze(p)
|
d := &wireguard.WGDocument{
|
||||||
|
Config: ast.NewWGConfig(),
|
||||||
|
}
|
||||||
|
d.Config.Parse(content)
|
||||||
|
|
||||||
if len(diagnostics) == 0 {
|
diagnostics := Analyze(d)
|
||||||
|
|
||||||
|
if !(len(diagnostics) > 0) {
|
||||||
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
|
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,12 +35,21 @@ func (c *WGConfig) Parse(input string) []common.LSPError {
|
|||||||
lineNumber := uint32(rawLineNumber)
|
lineNumber := uint32(rawLineNumber)
|
||||||
|
|
||||||
if emptyPattern.MatchString(line) {
|
if emptyPattern.MatchString(line) {
|
||||||
|
// Set end of last section
|
||||||
|
if currentSection != nil {
|
||||||
|
currentSection.End.Line = lineNumber
|
||||||
|
currentSection.End.Character = 0
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if commentPattern.MatchString(line) {
|
if commentPattern.MatchString(line) {
|
||||||
c.CommentLines[lineNumber] = struct{}{}
|
c.CommentLines[lineNumber] = struct{}{}
|
||||||
|
// Set end of last section
|
||||||
|
if currentSection != nil {
|
||||||
|
currentSection.End.Line = lineNumber
|
||||||
|
currentSection.End.Character = uint32(len(line))
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +89,13 @@ func (c *WGConfig) Parse(input string) []common.LSPError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Else property
|
// Else property
|
||||||
|
|
||||||
|
// Set end of last section
|
||||||
|
if currentSection != nil {
|
||||||
|
currentSection.End.Line = lineNumber
|
||||||
|
currentSection.End.Character = uint32(len(line))
|
||||||
|
}
|
||||||
|
|
||||||
if currentSection == nil {
|
if currentSection == nil {
|
||||||
// Root properties are not allowed
|
// Root properties are not allowed
|
||||||
errors = append(errors, common.LSPError{
|
errors = append(errors, common.LSPError{
|
||||||
@ -212,12 +228,12 @@ func (c *WGConfig) Parse(input string) []common.LSPError {
|
|||||||
Character: propertyEnd,
|
Character: propertyEnd,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
RawValue: line,
|
||||||
Key: key,
|
Key: key,
|
||||||
Separator: &separator,
|
Separator: &separator,
|
||||||
Value: value,
|
Value: value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
@ -36,6 +36,14 @@ PublicKey = 1234567890
|
|||||||
t.Errorf("Parse: Expected comments to be present on lines 0 and 12")
|
t.Errorf("Parse: Expected comments to be present on lines 0 and 12")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !(config.Sections[0].Start.Line == 3 && config.Sections[0].End.Line == 8) {
|
||||||
|
t.Errorf("Parse: Expected section 0 to be present on lines 3 and 6, but it is: %v", config.Sections[0].End)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(config.Sections[0].Start.Character == 0 && config.Sections[0].End.Character == 0) {
|
||||||
|
t.Errorf("Parse: Expected section 0 to be present on characters 0 and 0, but it is: %v", config.Sections[0].End)
|
||||||
|
}
|
||||||
|
|
||||||
if !(config.Sections[0].Header.Name == "Interface" && config.Sections[1].Header.Name == "Peer") {
|
if !(config.Sections[0].Header.Name == "Interface" && config.Sections[1].Header.Name == "Peer") {
|
||||||
t.Errorf("Parse: Expected sections to be present on lines 0, 1, and 2")
|
t.Errorf("Parse: Expected sections to be present on lines 0, 1, and 2")
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ type WGPropertySeparator struct {
|
|||||||
|
|
||||||
type WGProperty struct {
|
type WGProperty struct {
|
||||||
common.LocationRange
|
common.LocationRange
|
||||||
|
RawValue string
|
||||||
Key WGPropertyKey
|
Key WGPropertyKey
|
||||||
Separator *WGPropertySeparator
|
Separator *WGPropertySeparator
|
||||||
Value *WGPropertyValue
|
Value *WGPropertyValue
|
||||||
|
@ -10,11 +10,11 @@ func (c *WGConfig) FindSectionByLine(line uint32) *WGSection {
|
|||||||
line,
|
line,
|
||||||
func(current *WGSection, target uint32) int {
|
func(current *WGSection, target uint32) int {
|
||||||
if target < current.Start.Line {
|
if target < current.Start.Line {
|
||||||
return 1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
if target > current.Start.Line {
|
if target > current.End.Line {
|
||||||
return -1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
@ -1,452 +0,0 @@
|
|||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
docvalues "config-lsp/doc-values"
|
|
||||||
"config-lsp/handlers/wireguard/fields"
|
|
||||||
"config-lsp/handlers/wireguard/parser"
|
|
||||||
"config-lsp/utils"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Analyze(
|
|
||||||
p parser.WireguardParser,
|
|
||||||
) []protocol.Diagnostic {
|
|
||||||
sectionsErrors := analyzeSections(p.Sections)
|
|
||||||
sectionsErrors = append(sectionsErrors, analyzeOnlyOneInterfaceSectionSpecified(p)...)
|
|
||||||
|
|
||||||
if len(sectionsErrors) > 0 {
|
|
||||||
return sectionsErrors
|
|
||||||
}
|
|
||||||
|
|
||||||
validCheckErrors := checkIfValuesAreValid(p.Sections)
|
|
||||||
|
|
||||||
if len(validCheckErrors) > 0 {
|
|
||||||
return validCheckErrors
|
|
||||||
}
|
|
||||||
|
|
||||||
diagnostics := make([]protocol.Diagnostic, 0)
|
|
||||||
diagnostics = append(diagnostics, analyzeParserForDuplicateProperties(p)...)
|
|
||||||
diagnostics = append(diagnostics, analyzeDNSContainsFallback(p)...)
|
|
||||||
diagnostics = append(diagnostics, analyzeKeepAliveIsSet(p)...)
|
|
||||||
diagnostics = append(diagnostics, analyzeSymmetricPropertiesExist(p)...)
|
|
||||||
diagnostics = append(diagnostics, analyzeDuplicateAllowedIPs(p)...)
|
|
||||||
|
|
||||||
return diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
func analyzeSections(
|
|
||||||
sections []*parser.WireguardSection,
|
|
||||||
) []protocol.Diagnostic {
|
|
||||||
var diagnostics []protocol.Diagnostic
|
|
||||||
|
|
||||||
for _, section := range sections {
|
|
||||||
sectionDiagnostics := analyzeSection(*section)
|
|
||||||
|
|
||||||
if len(sectionDiagnostics) > 0 {
|
|
||||||
diagnostics = append(diagnostics, sectionDiagnostics...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(diagnostics) > 0 {
|
|
||||||
return diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
return diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
func analyzeOnlyOneInterfaceSectionSpecified(
|
|
||||||
p parser.WireguardParser,
|
|
||||||
) []protocol.Diagnostic {
|
|
||||||
var diagnostics []protocol.Diagnostic
|
|
||||||
alreadyFound := false
|
|
||||||
|
|
||||||
for _, section := range p.GetSectionsByName("Interface") {
|
|
||||||
if alreadyFound {
|
|
||||||
severity := protocol.DiagnosticSeverityError
|
|
||||||
diagnostics = append(diagnostics, protocol.Diagnostic{
|
|
||||||
Message: "Only one [Interface] section is allowed",
|
|
||||||
Severity: &severity,
|
|
||||||
Range: section.GetHeaderLineRange(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
alreadyFound = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
func analyzeDNSContainsFallback(
|
|
||||||
p parser.WireguardParser,
|
|
||||||
) []protocol.Diagnostic {
|
|
||||||
lineNumber, property := p.FindFirstPropertyByName("DNS")
|
|
||||||
|
|
||||||
if property == nil {
|
|
||||||
return []protocol.Diagnostic{}
|
|
||||||
}
|
|
||||||
|
|
||||||
dnsAmount := len(strings.Split(property.Value.Value, ","))
|
|
||||||
|
|
||||||
if dnsAmount == 1 {
|
|
||||||
severity := protocol.DiagnosticSeverityHint
|
|
||||||
|
|
||||||
return []protocol.Diagnostic{
|
|
||||||
{
|
|
||||||
Message: "There is only one DNS server specified. It is recommended to set up fallback DNS servers",
|
|
||||||
Severity: &severity,
|
|
||||||
Range: protocol.Range{
|
|
||||||
Start: protocol.Position{
|
|
||||||
Line: *lineNumber,
|
|
||||||
Character: property.Value.Location.Start,
|
|
||||||
},
|
|
||||||
End: protocol.Position{
|
|
||||||
Line: *lineNumber,
|
|
||||||
Character: property.Value.Location.End,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return []protocol.Diagnostic{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func analyzeKeepAliveIsSet(
|
|
||||||
p parser.WireguardParser,
|
|
||||||
) []protocol.Diagnostic {
|
|
||||||
var diagnostics []protocol.Diagnostic
|
|
||||||
|
|
||||||
for _, section := range p.GetSectionsByName("Peer") {
|
|
||||||
// If an endpoint is set, then we should only check for the keepalive property
|
|
||||||
if section.ExistsProperty("Endpoint") && !section.ExistsProperty("PersistentKeepalive") {
|
|
||||||
severity := protocol.DiagnosticSeverityHint
|
|
||||||
diagnostics = append(diagnostics, protocol.Diagnostic{
|
|
||||||
Message: "PersistentKeepalive is not set. It is recommended to set this property, as it helps to maintain the connection when users are behind NAT",
|
|
||||||
Severity: &severity,
|
|
||||||
Range: section.GetRange(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the values are valid.
|
|
||||||
// Assumes that sections have been analyzed already.
|
|
||||||
func checkIfValuesAreValid(
|
|
||||||
sections []*parser.WireguardSection,
|
|
||||||
) []protocol.Diagnostic {
|
|
||||||
var diagnostics []protocol.Diagnostic
|
|
||||||
|
|
||||||
for _, section := range sections {
|
|
||||||
for lineNumber, property := range section.Properties {
|
|
||||||
diagnostics = append(
|
|
||||||
diagnostics,
|
|
||||||
analyzeProperty(property, section, lineNumber)...,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
func analyzeSection(
|
|
||||||
s parser.WireguardSection,
|
|
||||||
) []protocol.Diagnostic {
|
|
||||||
var diagnostics []protocol.Diagnostic
|
|
||||||
|
|
||||||
if s.Name == nil {
|
|
||||||
// No section name
|
|
||||||
severity := protocol.DiagnosticSeverityError
|
|
||||||
diagnostics = append(diagnostics, protocol.Diagnostic{
|
|
||||||
Message: "This section is missing a name",
|
|
||||||
Severity: &severity,
|
|
||||||
Range: s.GetRange(),
|
|
||||||
})
|
|
||||||
return diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, found := fields.OptionsHeaderMap[*s.Name]; !found {
|
|
||||||
// Unknown section
|
|
||||||
severity := protocol.DiagnosticSeverityError
|
|
||||||
diagnostics = append(diagnostics, protocol.Diagnostic{
|
|
||||||
Message: fmt.Sprintf("Unknown section '%s'. It must be one of: [Interface], [Peer]", *s.Name),
|
|
||||||
Severity: &severity,
|
|
||||||
Range: s.GetHeaderLineRange(),
|
|
||||||
})
|
|
||||||
|
|
||||||
return diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
return diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the property is valid.
|
|
||||||
// Returns a list of diagnostics.
|
|
||||||
// `belongingSection` is the section to which the property belongs. This value is
|
|
||||||
// expected to be non-nil and expected to be a valid Wireguard section.
|
|
||||||
func analyzeProperty(
|
|
||||||
p parser.WireguardProperty,
|
|
||||||
belongingSection *parser.WireguardSection,
|
|
||||||
propertyLine uint32,
|
|
||||||
) []protocol.Diagnostic {
|
|
||||||
sectionOptions := fields.OptionsHeaderMap[*belongingSection.Name]
|
|
||||||
option, found := sectionOptions[p.Key.Name]
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
// Unknown property
|
|
||||||
severity := protocol.DiagnosticSeverityError
|
|
||||||
return []protocol.Diagnostic{
|
|
||||||
{
|
|
||||||
Message: fmt.Sprintf("Unknown property '%s'", p.Key.Name),
|
|
||||||
Severity: &severity,
|
|
||||||
Range: protocol.Range{
|
|
||||||
Start: protocol.Position{
|
|
||||||
Line: propertyLine,
|
|
||||||
Character: p.Key.Location.Start,
|
|
||||||
},
|
|
||||||
End: protocol.Position{
|
|
||||||
Line: propertyLine,
|
|
||||||
Character: p.Key.Location.End,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Value == nil {
|
|
||||||
// No value to check
|
|
||||||
severity := protocol.DiagnosticSeverityWarning
|
|
||||||
return []protocol.Diagnostic{
|
|
||||||
{
|
|
||||||
Message: "Property is missing a value",
|
|
||||||
Severity: &severity,
|
|
||||||
Range: p.GetLineRange(propertyLine),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errors := option.DeprecatedCheckIsValid(p.Value.Value)
|
|
||||||
|
|
||||||
return utils.Map(errors, func(err *docvalues.InvalidValue) protocol.Diagnostic {
|
|
||||||
severity := protocol.DiagnosticSeverityError
|
|
||||||
return protocol.Diagnostic{
|
|
||||||
Message: err.GetMessage(),
|
|
||||||
Severity: &severity,
|
|
||||||
Range: protocol.Range{
|
|
||||||
Start: protocol.Position{
|
|
||||||
Line: propertyLine,
|
|
||||||
Character: p.Value.Location.Start + err.Start,
|
|
||||||
},
|
|
||||||
End: protocol.Position{
|
|
||||||
Line: propertyLine,
|
|
||||||
Character: p.Value.Location.Start + err.End,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func analyzeParserForDuplicateProperties(
|
|
||||||
p parser.WireguardParser,
|
|
||||||
) []protocol.Diagnostic {
|
|
||||||
diagnostics := make([]protocol.Diagnostic, 0)
|
|
||||||
|
|
||||||
for _, section := range p.Sections {
|
|
||||||
diagnostics = append(diagnostics, analyzeDuplicateProperties(*section)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
func analyzeDuplicateProperties(
|
|
||||||
s parser.WireguardSection,
|
|
||||||
) []protocol.Diagnostic {
|
|
||||||
var diagnostics []protocol.Diagnostic
|
|
||||||
|
|
||||||
existingProperties := make(map[string]uint32)
|
|
||||||
|
|
||||||
lines := utils.KeysOfMap(s.Properties)
|
|
||||||
slices.Sort(lines)
|
|
||||||
|
|
||||||
for _, currentLineNumber := range lines {
|
|
||||||
property := s.Properties[currentLineNumber]
|
|
||||||
var skipCheck = false
|
|
||||||
|
|
||||||
if s.Name != nil {
|
|
||||||
switch *s.Name {
|
|
||||||
case "Interface":
|
|
||||||
if _, found := fields.InterfaceAllowedDuplicateFields[property.Key.Name]; found {
|
|
||||||
skipCheck = true
|
|
||||||
}
|
|
||||||
case "Peer":
|
|
||||||
if _, found := fields.PeerAllowedDuplicateFields[property.Key.Name]; found {
|
|
||||||
skipCheck = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if skipCheck {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if existingLineNumber, found := existingProperties[property.Key.Name]; found {
|
|
||||||
severity := protocol.DiagnosticSeverityError
|
|
||||||
diagnostics = append(diagnostics, protocol.Diagnostic{
|
|
||||||
Message: fmt.Sprintf("Property '%s' is already defined on line %d", property.Key.Name, existingLineNumber+1),
|
|
||||||
Severity: &severity,
|
|
||||||
Range: protocol.Range{
|
|
||||||
Start: protocol.Position{
|
|
||||||
Line: currentLineNumber,
|
|
||||||
Character: 0,
|
|
||||||
},
|
|
||||||
End: protocol.Position{
|
|
||||||
Line: currentLineNumber,
|
|
||||||
Character: 99999,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
existingProperties[property.Key.Name] = currentLineNumber
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
type propertyWithLine struct {
|
|
||||||
Line uint32
|
|
||||||
Property parser.WireguardProperty
|
|
||||||
IpPrefix netip.Prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapAllowedIPsToMasks(p parser.WireguardParser) map[uint8][]propertyWithLine {
|
|
||||||
ips := make(map[uint8][]propertyWithLine)
|
|
||||||
|
|
||||||
for _, section := range p.GetSectionsByName("Peer") {
|
|
||||||
for lineNumber, property := range section.Properties {
|
|
||||||
if property.Key.Name == "AllowedIPs" {
|
|
||||||
ipAddress, err := netip.ParsePrefix(property.Value.Value)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// This should not happen...
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
hostBits := uint8(ipAddress.Bits())
|
|
||||||
|
|
||||||
if _, found := ips[uint8(hostBits)]; !found {
|
|
||||||
ips[hostBits] = make([]propertyWithLine, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
ips[hostBits] = append(ips[hostBits], propertyWithLine{
|
|
||||||
Line: uint32(lineNumber),
|
|
||||||
Property: property,
|
|
||||||
IpPrefix: ipAddress,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ips
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy
|
|
||||||
// Simply compare the host bits of the IP addresses.
|
|
||||||
// Use a binary tree to store the host bits.
|
|
||||||
func analyzeDuplicateAllowedIPs(p parser.WireguardParser) []protocol.Diagnostic {
|
|
||||||
diagnostics := make([]protocol.Diagnostic, 0)
|
|
||||||
|
|
||||||
maskedIPs := mapAllowedIPsToMasks(p)
|
|
||||||
hostBits := utils.KeysOfMap(maskedIPs)
|
|
||||||
slices.Sort(hostBits)
|
|
||||||
|
|
||||||
ipHostSet := utils.CreateIPv4HostSet()
|
|
||||||
|
|
||||||
for _, hostBit := range hostBits {
|
|
||||||
ips := maskedIPs[hostBit]
|
|
||||||
|
|
||||||
for _, ipInfo := range ips {
|
|
||||||
if ctx, _ := ipHostSet.ContainsIP(ipInfo.IpPrefix); ctx != nil {
|
|
||||||
severity := protocol.DiagnosticSeverityError
|
|
||||||
definedLine := (*ctx).Value("line").(uint32)
|
|
||||||
|
|
||||||
diagnostics = append(diagnostics, protocol.Diagnostic{
|
|
||||||
Message: fmt.Sprintf("This IP range is already covered on line %d", definedLine),
|
|
||||||
Severity: &severity,
|
|
||||||
Range: protocol.Range{
|
|
||||||
Start: protocol.Position{
|
|
||||||
Line: ipInfo.Line,
|
|
||||||
Character: ipInfo.Property.Key.Location.Start,
|
|
||||||
},
|
|
||||||
End: protocol.Position{
|
|
||||||
Line: ipInfo.Line,
|
|
||||||
Character: ipInfo.Property.Value.Location.End,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
humanLineNumber := ipInfo.Line + 1
|
|
||||||
ctx := context.WithValue(context.Background(), "line", humanLineNumber)
|
|
||||||
|
|
||||||
ipHostSet.AddIP(
|
|
||||||
ipInfo.IpPrefix,
|
|
||||||
ctx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
func analyzeSymmetricPropertiesExist(
|
|
||||||
p parser.WireguardParser,
|
|
||||||
) []protocol.Diagnostic {
|
|
||||||
diagnostics := make([]protocol.Diagnostic, 0, 4)
|
|
||||||
severity := protocol.DiagnosticSeverityHint
|
|
||||||
|
|
||||||
for _, section := range p.GetSectionsByName("Interface") {
|
|
||||||
preUpLine, preUpProperty := section.FetchFirstProperty("PreUp")
|
|
||||||
preDownLine, preDownProperty := section.FetchFirstProperty("PreDown")
|
|
||||||
|
|
||||||
postUpLine, postUpProperty := section.FetchFirstProperty("PostUp")
|
|
||||||
postDownLine, postDownProperty := section.FetchFirstProperty("PostDown")
|
|
||||||
|
|
||||||
if preUpProperty != nil && preDownProperty == nil {
|
|
||||||
diagnostics = append(diagnostics, protocol.Diagnostic{
|
|
||||||
Message: "PreUp is set, but PreDown is not. It is recommended to set both properties symmetrically",
|
|
||||||
Range: preUpProperty.GetLineRange(*preUpLine),
|
|
||||||
Severity: &severity,
|
|
||||||
})
|
|
||||||
} else if preUpProperty == nil && preDownProperty != nil {
|
|
||||||
diagnostics = append(diagnostics, protocol.Diagnostic{
|
|
||||||
Message: "PreDown is set, but PreUp is not. It is recommended to set both properties symmetrically",
|
|
||||||
Range: preDownProperty.GetLineRange(*preDownLine),
|
|
||||||
Severity: &severity,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if postUpProperty != nil && postDownProperty == nil {
|
|
||||||
diagnostics = append(diagnostics, protocol.Diagnostic{
|
|
||||||
Message: "PostUp is set, but PostDown is not. It is recommended to set both properties symmetrically",
|
|
||||||
Range: postUpProperty.GetLineRange(*postUpLine),
|
|
||||||
Severity: &severity,
|
|
||||||
})
|
|
||||||
} else if postUpProperty == nil && postDownProperty != nil {
|
|
||||||
diagnostics = append(diagnostics, protocol.Diagnostic{
|
|
||||||
Message: "PostDown is set, but PostUp is not. It is recommended to set both properties symmetrically",
|
|
||||||
Range: postDownProperty.GetLineRange(*postDownLine),
|
|
||||||
Severity: &severity,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return diagnostics
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"config-lsp/handlers/wireguard/parser"
|
|
||||||
"config-lsp/utils"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMultipleIntefaces(t *testing.T) {
|
|
||||||
content := utils.Dedent(`
|
|
||||||
[Interface]
|
|
||||||
PrivateKey = abc
|
|
||||||
|
|
||||||
[Interface]
|
|
||||||
PrivateKey = def
|
|
||||||
`)
|
|
||||||
p := parser.CreateWireguardParser()
|
|
||||||
p.ParseFromString(content)
|
|
||||||
|
|
||||||
diagnostics := Analyze(p)
|
|
||||||
|
|
||||||
if len(diagnostics) == 0 {
|
|
||||||
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInvalidValue(t *testing.T) {
|
|
||||||
content := utils.Dedent(`
|
|
||||||
[Interface]
|
|
||||||
DNS = nope
|
|
||||||
`)
|
|
||||||
p := parser.CreateWireguardParser()
|
|
||||||
p.ParseFromString(content)
|
|
||||||
|
|
||||||
diagnostics := Analyze(p)
|
|
||||||
|
|
||||||
if len(diagnostics) == 0 {
|
|
||||||
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDuplicateProperties(t *testing.T) {
|
|
||||||
content := utils.Dedent(`
|
|
||||||
[Interface]
|
|
||||||
PrivateKey = abc
|
|
||||||
DNS = 1.1.1.1
|
|
||||||
PrivateKey = def
|
|
||||||
`)
|
|
||||||
p := parser.CreateWireguardParser()
|
|
||||||
p.ParseFromString(content)
|
|
||||||
|
|
||||||
diagnostics := Analyze(p)
|
|
||||||
|
|
||||||
if len(diagnostics) == 0 {
|
|
||||||
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,9 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
|
/*
|
||||||
import (
|
import (
|
||||||
|
"config-lsp/handlers/wireguard/ast"
|
||||||
wgcommands "config-lsp/handlers/wireguard/commands"
|
wgcommands "config-lsp/handlers/wireguard/commands"
|
||||||
"config-lsp/handlers/wireguard/parser"
|
|
||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type CodeAction interface {
|
type CodeAction interface {
|
||||||
RunCommand(*parser.WireguardParser) (*protocol.ApplyWorkspaceEditParams, error)
|
RunCommand(*ast.WGConfig) (*protocol.ApplyWorkspaceEditParams, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CodeActionArgs interface{}
|
type CodeActionArgs interface{}
|
||||||
@ -32,7 +33,7 @@ func CodeActionGeneratePrivateKeyArgsFromArguments(arguments map[string]any) Cod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (args CodeActionGeneratePrivateKeyArgs) RunCommand(p *parser.WireguardParser) (*protocol.ApplyWorkspaceEditParams, error) {
|
func (args CodeActionGeneratePrivateKeyArgs) RunCommand(p *ast.WGConfig) (*protocol.ApplyWorkspaceEditParams, error) {
|
||||||
privateKey, err := wgcommands.CreateNewPrivateKey()
|
privateKey, err := wgcommands.CreateNewPrivateKey()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -73,7 +74,7 @@ func CodeActionGeneratePresharedKeyArgsFromArguments(arguments map[string]any) C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (args CodeActionGeneratePresharedKeyArgs) RunCommand(p *parser.WireguardParser) (*protocol.ApplyWorkspaceEditParams, error) {
|
func (args CodeActionGeneratePresharedKeyArgs) RunCommand(p *ast.WGConfig) (*protocol.ApplyWorkspaceEditParams, error) {
|
||||||
presharedKey, err := wgcommands.CreatePresharedKey()
|
presharedKey, err := wgcommands.CreatePresharedKey()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -114,7 +115,7 @@ func CodeActionAddKeepaliveArgsFromArguments(arguments map[string]any) CodeActio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (args CodeActionAddKeepaliveArgs) RunCommand(p *parser.WireguardParser) (*protocol.ApplyWorkspaceEditParams, error) {
|
func (args CodeActionAddKeepaliveArgs) RunCommand(p *ast.WGConfig) (*protocol.ApplyWorkspaceEditParams, error) {
|
||||||
section := p.Sections[args.SectionIndex]
|
section := p.Sections[args.SectionIndex]
|
||||||
|
|
||||||
label := "Add PersistentKeepalive"
|
label := "Add PersistentKeepalive"
|
||||||
@ -141,3 +142,4 @@ func (args CodeActionAddKeepaliveArgs) RunCommand(p *parser.WireguardParser) (*p
|
|||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
@ -1,160 +1,34 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
docvalues "config-lsp/doc-values"
|
"config-lsp/handlers/wireguard"
|
||||||
"config-lsp/handlers/wireguard/fields"
|
|
||||||
"config-lsp/handlers/wireguard/parser"
|
|
||||||
"config-lsp/utils"
|
|
||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
"maps"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getHeaderCompletion(name string, documentation string) protocol.CompletionItem {
|
func SuggestCompletions(
|
||||||
textFormat := protocol.InsertTextFormatPlainText
|
d *wireguard.WGDocument,
|
||||||
kind := protocol.CompletionItemKindEnum
|
params *protocol.CompletionParams,
|
||||||
|
|
||||||
insertText := "[" + name + "]\n"
|
|
||||||
|
|
||||||
return protocol.CompletionItem{
|
|
||||||
Label: "[" + name + "]",
|
|
||||||
InsertTextFormat: &textFormat,
|
|
||||||
InsertText: &insertText,
|
|
||||||
Kind: &kind,
|
|
||||||
Documentation: &documentation,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetRootCompletionsForEmptyLine(
|
|
||||||
p parser.WireguardParser,
|
|
||||||
) ([]protocol.CompletionItem, error) {
|
) ([]protocol.CompletionItem, error) {
|
||||||
completions := make([]protocol.CompletionItem, 0)
|
lineNumber := params.Position.Line
|
||||||
|
|
||||||
if _, found := p.GetInterfaceSection(); !found {
|
if _, found := d.Config.CommentLines[lineNumber]; found {
|
||||||
completions = append(completions, getHeaderCompletion("Interface", fields.HeaderInterfaceEnum.Documentation))
|
|
||||||
}
|
|
||||||
|
|
||||||
completions = append(completions, getHeaderCompletion("Peer", fields.HeaderPeerEnum.Documentation))
|
|
||||||
|
|
||||||
return completions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCompletionsForSectionEmptyLine(
|
|
||||||
s parser.WireguardSection,
|
|
||||||
) ([]protocol.CompletionItem, error) {
|
|
||||||
if s.Name == nil {
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
options := make(map[string]docvalues.DocumentationValue)
|
section := d.Config.FindSectionByLine(lineNumber)
|
||||||
|
property := d.Config.FindPropertyByLine(lineNumber)
|
||||||
|
|
||||||
switch *s.Name {
|
if section == nil {
|
||||||
case "Interface":
|
// First, the user needs to define a section header
|
||||||
maps.Copy(options, fields.InterfaceOptions)
|
if property == nil {
|
||||||
|
return GetSectionHeaderCompletions(d)
|
||||||
// Remove existing options
|
|
||||||
for _, property := range s.Properties {
|
|
||||||
if _, found := fields.InterfaceAllowedDuplicateFields[property.Key.Name]; found {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(options, property.Key.Name)
|
|
||||||
}
|
|
||||||
case "Peer":
|
|
||||||
maps.Copy(options, fields.PeerOptions)
|
|
||||||
|
|
||||||
// Remove existing options
|
|
||||||
for _, property := range s.Properties {
|
|
||||||
if _, found := fields.PeerAllowedDuplicateFields[property.Key.Name]; found {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(options, property.Key.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kind := protocol.CompletionItemKindProperty
|
|
||||||
|
|
||||||
return utils.MapMapToSlice(
|
|
||||||
options,
|
|
||||||
func(optionName string, value docvalues.DocumentationValue) protocol.CompletionItem {
|
|
||||||
insertText := optionName + " = "
|
|
||||||
|
|
||||||
return protocol.CompletionItem{
|
|
||||||
Kind: &kind,
|
|
||||||
Documentation: value.Documentation,
|
|
||||||
Label: optionName,
|
|
||||||
InsertText: &insertText,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSeparatorCompletion(property parser.WireguardProperty, character uint32) ([]protocol.CompletionItem, error) {
|
|
||||||
var insertText string
|
|
||||||
|
|
||||||
if character == property.Key.Location.End {
|
|
||||||
insertText = property.Key.Name + " = "
|
|
||||||
} else {
|
|
||||||
insertText = "= "
|
|
||||||
}
|
|
||||||
|
|
||||||
kind := protocol.CompletionItemKindValue
|
|
||||||
|
|
||||||
return []protocol.CompletionItem{
|
|
||||||
{
|
|
||||||
Label: insertText,
|
|
||||||
InsertText: &insertText,
|
|
||||||
Kind: &kind,
|
|
||||||
},
|
|
||||||
}, parser.PropertyNotFullyTypedError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCompletionsForSectionPropertyLine(
|
|
||||||
s parser.WireguardSection,
|
|
||||||
lineNumber uint32,
|
|
||||||
character uint32,
|
|
||||||
) ([]protocol.CompletionItem, error) {
|
|
||||||
property, err := s.GetPropertyByLine(lineNumber)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Name == nil {
|
|
||||||
return nil, parser.PropertyNotFoundError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
options, found := fields.OptionsHeaderMap[*s.Name]
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
return nil, parser.PropertyNotFoundError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if property.Separator == nil {
|
|
||||||
if _, found := options[property.Key.Name]; found {
|
|
||||||
return GetSeparatorCompletion(*property, character)
|
|
||||||
}
|
|
||||||
// Get empty line completions
|
|
||||||
return nil, parser.PropertyNotFullyTypedError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
option, found := options[property.Key.Name]
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
if character < property.Separator.Location.Start {
|
|
||||||
return nil, parser.PropertyNotFullyTypedError{}
|
|
||||||
} else {
|
} else {
|
||||||
return nil, parser.PropertyNotFoundError{}
|
// However, if they start typing a property - we should not
|
||||||
|
// show anything to signal them that they can't write a property yet.
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return GetSectionBodyCompletions(d, *section, property, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
if property.Value == nil {
|
|
||||||
if character >= property.Separator.Location.End {
|
|
||||||
return option.DeprecatedFetchCompletions("", 0), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
relativeCursor := character - property.Value.Location.Start
|
|
||||||
|
|
||||||
return option.DeprecatedFetchCompletions(property.Value.Value, relativeCursor), nil
|
|
||||||
}
|
}
|
||||||
|
182
server/handlers/wireguard/handlers/completions_body.go
Normal file
182
server/handlers/wireguard/handlers/completions_body.go
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
docvalues "config-lsp/doc-values"
|
||||||
|
"config-lsp/handlers/wireguard"
|
||||||
|
"config-lsp/handlers/wireguard/ast"
|
||||||
|
"config-lsp/handlers/wireguard/fields"
|
||||||
|
"config-lsp/utils"
|
||||||
|
"maps"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetSectionBodyCompletions(
|
||||||
|
d *wireguard.WGDocument,
|
||||||
|
section ast.WGSection,
|
||||||
|
property *ast.WGProperty,
|
||||||
|
params *protocol.CompletionParams,
|
||||||
|
) ([]protocol.CompletionItem, error) {
|
||||||
|
// These are the possible scenarios:
|
||||||
|
// | = Cursor position
|
||||||
|
/*
|
||||||
|
[Inter|
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
[Interface]
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
[Interface]
|
||||||
|
Add|
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
[Interface]
|
||||||
|
Address = 10.0.0.1/24
|
||||||
|
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// First scenario, user is typing the section name
|
||||||
|
if params.Position.Line == section.Start.Line {
|
||||||
|
return GetSectionHeaderCompletions(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third and fourth scenarios, the user wants to add a new property
|
||||||
|
completions, err := getPropertyCompletions(d, section, property, params)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// Something weird happened
|
||||||
|
return completions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fourth scenario may arrive here, the user is typing a property name, but the previous line is empty.
|
||||||
|
// In this case, the user may want to add a property or add a new section.
|
||||||
|
// We should therefore suggest both options.
|
||||||
|
|
||||||
|
// Check if previous line is empty
|
||||||
|
previousLineProperty := d.Config.FindPropertyByLine(params.Position.Line - 1)
|
||||||
|
|
||||||
|
if previousLineProperty == nil && params.Position.Line-1 != section.Start.Line {
|
||||||
|
sectionCompletions, err := GetSectionHeaderCompletions(d)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return sectionCompletions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
completions = append(completions, sectionCompletions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return completions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPropertyCompletions(
|
||||||
|
d *wireguard.WGDocument,
|
||||||
|
section ast.WGSection,
|
||||||
|
property *ast.WGProperty,
|
||||||
|
params *protocol.CompletionParams,
|
||||||
|
) ([]protocol.CompletionItem, error) {
|
||||||
|
// These are the possible scenarios:
|
||||||
|
/* Empty line / Key started / Separator missing:
|
||||||
|
Add|
|
||||||
|
Address |
|
||||||
|
*/
|
||||||
|
/* Value missing or started:
|
||||||
|
Address = 10.|
|
||||||
|
*/
|
||||||
|
|
||||||
|
if property == nil || property.Separator == nil {
|
||||||
|
// First scenario
|
||||||
|
return getKeyCompletions(section), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the cursor it outside the value
|
||||||
|
position := common.LSPCharacterAsCursorPosition(params.Position.Character)
|
||||||
|
if property.Value != nil && property.Value.IsPositionAfterEnd(position) {
|
||||||
|
// Then we don't show anything
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, suggest value completions
|
||||||
|
return getValueCompletions(section, *property, position), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKeyCompletions(
|
||||||
|
section ast.WGSection,
|
||||||
|
) []protocol.CompletionItem {
|
||||||
|
options := make(map[string]docvalues.DocumentationValue)
|
||||||
|
|
||||||
|
switch section.Header.Name {
|
||||||
|
case "Interface":
|
||||||
|
maps.Copy(options, fields.InterfaceOptions)
|
||||||
|
|
||||||
|
// Remove existing, non-duplicate options
|
||||||
|
for _, property := range section.Properties {
|
||||||
|
if _, found := fields.InterfaceAllowedDuplicateFields[property.Key.Name]; found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(options, property.Key.Name)
|
||||||
|
}
|
||||||
|
case "Peer":
|
||||||
|
maps.Copy(options, fields.PeerOptions)
|
||||||
|
|
||||||
|
// Remove existing, non-duplicate options
|
||||||
|
for _, property := range section.Properties {
|
||||||
|
if _, found := fields.PeerAllowedDuplicateFields[property.Key.Name]; found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(options, property.Key.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kind := protocol.CompletionItemKindField
|
||||||
|
|
||||||
|
return utils.MapMapToSlice(
|
||||||
|
options,
|
||||||
|
func(optionName string, value docvalues.DocumentationValue) protocol.CompletionItem {
|
||||||
|
insertText := optionName + " = "
|
||||||
|
|
||||||
|
return protocol.CompletionItem{
|
||||||
|
Kind: &kind,
|
||||||
|
Documentation: value.Documentation,
|
||||||
|
Label: optionName,
|
||||||
|
InsertText: &insertText,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getValueCompletions(
|
||||||
|
section ast.WGSection,
|
||||||
|
property ast.WGProperty,
|
||||||
|
cursorPosition common.CursorPosition,
|
||||||
|
) []protocol.CompletionItem {
|
||||||
|
// TODO: Normalize section header name
|
||||||
|
options, found := fields.OptionsHeaderMap[section.Header.Name]
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
option, found := options[property.Key.Name]
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if property.Value == nil {
|
||||||
|
return option.DeprecatedFetchCompletions("", 0)
|
||||||
|
} else {
|
||||||
|
return option.DeprecatedFetchCompletions(
|
||||||
|
property.Value.Value,
|
||||||
|
common.DeprecatedImprovedCursorToIndex(
|
||||||
|
cursorPosition,
|
||||||
|
property.Value.Value,
|
||||||
|
property.Value.Start.Character,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
46
server/handlers/wireguard/handlers/completions_header.go
Normal file
46
server/handlers/wireguard/handlers/completions_header.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/handlers/wireguard"
|
||||||
|
"config-lsp/handlers/wireguard/fields"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getHeaderCompletion(name string, documentation string) protocol.CompletionItem {
|
||||||
|
textFormat := protocol.InsertTextFormatPlainText
|
||||||
|
kind := protocol.CompletionItemKindEnum
|
||||||
|
|
||||||
|
insertText := "[" + name + "]\n"
|
||||||
|
|
||||||
|
return protocol.CompletionItem{
|
||||||
|
Label: "[" + name + "]",
|
||||||
|
InsertTextFormat: &textFormat,
|
||||||
|
InsertText: &insertText,
|
||||||
|
Kind: &kind,
|
||||||
|
Documentation: &documentation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSectionHeaderCompletions(
|
||||||
|
d *wireguard.WGDocument,
|
||||||
|
) ([]protocol.CompletionItem, error) {
|
||||||
|
completions := make([]protocol.CompletionItem, 0)
|
||||||
|
|
||||||
|
containsInterfaceSection := false
|
||||||
|
|
||||||
|
for _, section := range d.Config.Sections {
|
||||||
|
if section.Header.Name == "Interface" {
|
||||||
|
containsInterfaceSection = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !containsInterfaceSection {
|
||||||
|
completions = append(completions, getHeaderCompletion("Interface", fields.HeaderInterfaceEnum.Documentation))
|
||||||
|
}
|
||||||
|
|
||||||
|
completions = append(completions, getHeaderCompletion("Peer", fields.HeaderPeerEnum.Documentation))
|
||||||
|
|
||||||
|
return completions, nil
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"config-lsp/handlers/wireguard"
|
||||||
|
"config-lsp/handlers/wireguard/ast"
|
||||||
"config-lsp/handlers/wireguard/fields"
|
"config-lsp/handlers/wireguard/fields"
|
||||||
"config-lsp/handlers/wireguard/parser"
|
|
||||||
"config-lsp/utils"
|
"config-lsp/utils"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSimplePropertyInInterface(
|
func TestSimplePropertyInInterface(
|
||||||
@ -14,17 +17,33 @@ func TestSimplePropertyInInterface(
|
|||||||
[Interface]
|
[Interface]
|
||||||
|
|
||||||
`)
|
`)
|
||||||
p := parser.CreateWireguardParser()
|
p := ast.NewWGConfig()
|
||||||
p.ParseFromString(sample)
|
parseErrors := p.Parse(sample)
|
||||||
|
|
||||||
completions, err := GetCompletionsForSectionEmptyLine(*p.Sections[0])
|
if len(parseErrors) > 0 {
|
||||||
|
t.Fatalf("Parser failed with error %v", parseErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := &wireguard.WGDocument{
|
||||||
|
Config: p,
|
||||||
|
}
|
||||||
|
|
||||||
|
params := &protocol.CompletionParams{
|
||||||
|
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
|
||||||
|
Position: protocol.Position{
|
||||||
|
Line: 1,
|
||||||
|
Character: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
completions, err := SuggestCompletions(d, params)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err)
|
t.Errorf("getCompletionsForEmptyLine failed with error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(completions) != len(fields.InterfaceOptions) {
|
if len(completions) != len(fields.InterfaceOptions) {
|
||||||
t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(fields.InterfaceOptions), len(completions))
|
t.Errorf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(fields.InterfaceOptions), len(completions))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,10 +55,26 @@ func TestSimpleOneExistingPropertyInInterface(
|
|||||||
PrivateKey = 1234567890
|
PrivateKey = 1234567890
|
||||||
|
|
||||||
`)
|
`)
|
||||||
p := parser.CreateWireguardParser()
|
p := ast.NewWGConfig()
|
||||||
p.ParseFromString(sample)
|
parseErrors := p.Parse(sample)
|
||||||
|
|
||||||
completions, err := GetCompletionsForSectionEmptyLine(*p.Sections[0])
|
if len(parseErrors) > 0 {
|
||||||
|
t.Fatalf("Parser failed with error %v", parseErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := &wireguard.WGDocument{
|
||||||
|
Config: p,
|
||||||
|
}
|
||||||
|
|
||||||
|
params := &protocol.CompletionParams{
|
||||||
|
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
|
||||||
|
Position: protocol.Position{
|
||||||
|
Line: 2,
|
||||||
|
Character: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
completions, err := SuggestCompletions(d, params)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err)
|
t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err)
|
||||||
@ -51,124 +86,151 @@ PrivateKey = 1234567890
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyRootCompletionsWork(
|
func TestEmptyCompletions(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
) {
|
) {
|
||||||
sample := utils.Dedent(`
|
sample := utils.Dedent(`
|
||||||
`)
|
|
||||||
|
|
||||||
p := parser.CreateWireguardParser()
|
`)
|
||||||
p.ParseFromString(sample)
|
p := ast.NewWGConfig()
|
||||||
|
parseErrors := p.Parse(sample)
|
||||||
|
|
||||||
completions, _ := GetRootCompletionsForEmptyLine(p)
|
if len(parseErrors) > 0 {
|
||||||
|
t.Fatalf("Parser failed with error %v", parseErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := &wireguard.WGDocument{
|
||||||
|
Config: p,
|
||||||
|
}
|
||||||
|
|
||||||
|
params := &protocol.CompletionParams{
|
||||||
|
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
|
||||||
|
Position: protocol.Position{
|
||||||
|
Line: 0,
|
||||||
|
Character: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
completions, err := SuggestCompletions(d, params)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if len(completions) != 2 {
|
if len(completions) != 2 {
|
||||||
t.Fatalf("getRootCompletionsForEmptyLine: Expected 2 completions, but got %v", len(completions))
|
t.Fatalf("getRootCompletionsForEmptyLine: Expected 2 completions, but got %v", len(completions))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInterfaceSectionRootCompletionsBeforeWork(
|
func TestIncompletePropertyCompletions(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
) {
|
) {
|
||||||
sample := utils.Dedent(`
|
sample := utils.Dedent(`
|
||||||
|
|
||||||
[Interface]
|
|
||||||
`)
|
|
||||||
p := parser.CreateWireguardParser()
|
|
||||||
p.ParseFromString(sample)
|
|
||||||
|
|
||||||
completions, _ := GetRootCompletionsForEmptyLine(p)
|
|
||||||
|
|
||||||
if len(completions) != 1 {
|
|
||||||
t.Fatalf("getRootCompletionsForEmptyLine: Expected 1 completions, but got %v", len(completions))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInterfaceAndPeerSectionRootCompletionsWork(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
[Interface]
|
|
||||||
|
|
||||||
[Peer]
|
[Peer]
|
||||||
|
Add
|
||||||
`)
|
`)
|
||||||
p := parser.CreateWireguardParser()
|
p := ast.NewWGConfig()
|
||||||
p.ParseFromString(sample)
|
parseErrors := p.Parse(sample)
|
||||||
|
|
||||||
completions, _ := GetRootCompletionsForEmptyLine(p)
|
if len(parseErrors) > 0 {
|
||||||
|
t.Fatalf("Parser failed with error %v", parseErrors)
|
||||||
if len(completions) != 1 {
|
|
||||||
t.Fatalf("getRootCompletionsForEmptyLine: Expected 1 completions, but got %v", len(completions))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPropertyNoSepatorShouldCompleteSeparator(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
[Interface]
|
|
||||||
DNS
|
|
||||||
`)
|
|
||||||
p := parser.CreateWireguardParser()
|
|
||||||
p.ParseFromString(sample)
|
|
||||||
|
|
||||||
completions, err := GetCompletionsForSectionPropertyLine(*p.Sections[0], 1, 3)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("getCompletionsForPropertyLine err is nil but should not be")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(completions) != 1 {
|
d := &wireguard.WGDocument{
|
||||||
t.Fatalf("getCompletionsForPropertyLine: Expected 1 completion, but got %v", len(completions))
|
Config: p,
|
||||||
}
|
}
|
||||||
|
|
||||||
if *completions[0].InsertText != "DNS = " {
|
params := &protocol.CompletionParams{
|
||||||
t.Fatalf("getCompletionsForPropertyLine: Expected completion to be 'DNS = ', but got '%v'", completions[0].Label)
|
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
|
||||||
|
Position: protocol.Position{
|
||||||
|
Line: 1,
|
||||||
|
Character: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
completions, err := SuggestCompletions(d, params)
|
||||||
|
|
||||||
func TestPropertyNoSeparatorWithSpaceShouldCompleteSeparator(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
[Interface]
|
|
||||||
DNS
|
|
||||||
`)
|
|
||||||
p := parser.CreateWireguardParser()
|
|
||||||
p.ParseFromString(sample)
|
|
||||||
|
|
||||||
completions, err := GetCompletionsForSectionPropertyLine(*p.Sections[0], 1, 4)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("getCompletionsForPropertyLine err is nil but should not be")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(completions) != 1 {
|
|
||||||
t.Fatalf("getCompletionsForPropertyLine: Expected 1 completion, but got %v", len(completions))
|
|
||||||
}
|
|
||||||
|
|
||||||
if *completions[0].InsertText != "= " {
|
|
||||||
t.Fatalf("getCompletionsForPropertyLine: Expected completion to be '= ', but got '%v'", completions[0].Label)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHeaderButNoProperty(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
[Interface]
|
|
||||||
|
|
||||||
`)
|
|
||||||
p := parser.CreateWireguardParser()
|
|
||||||
p.ParseFromString(sample)
|
|
||||||
|
|
||||||
completions, err := GetCompletionsForSectionEmptyLine(*p.Sections[0])
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err)
|
t.Errorf("getCompletionsForEmptyLine failed with error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(completions) != len(fields.InterfaceOptions) {
|
if len(completions) != len(fields.PeerOptions) {
|
||||||
t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(fields.InterfaceOptions), len(completions))
|
t.Errorf("getRootCompletionsForEmptyLine: Expected 1 completions, but got %v", len(completions))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPropertyBeforeLineIsEmpty(
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
sample := utils.Dedent(`
|
||||||
|
[Interface]
|
||||||
|
DNS = 1.1.1.1
|
||||||
|
|
||||||
|
|
||||||
|
`)
|
||||||
|
p := ast.NewWGConfig()
|
||||||
|
parseErrors := p.Parse(sample)
|
||||||
|
|
||||||
|
if len(parseErrors) > 0 {
|
||||||
|
t.Fatalf("Parser failed with error %v", parseErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := &wireguard.WGDocument{
|
||||||
|
Config: p,
|
||||||
|
}
|
||||||
|
|
||||||
|
params := &protocol.CompletionParams{
|
||||||
|
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
|
||||||
|
Position: protocol.Position{
|
||||||
|
Line: 3,
|
||||||
|
Character: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
completions, err := SuggestCompletions(d, params)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("getCompletionsForPropertyLine failed with error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(completions) != len(fields.InterfaceOptions)+1-1 {
|
||||||
|
t.Errorf("getCompletionsForPropertyLine: Expected completions, but got %v", len(completions))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPropertyValueCompletions(
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
sample := utils.Dedent(`
|
||||||
|
[Interface]
|
||||||
|
Table =
|
||||||
|
`)
|
||||||
|
p := ast.NewWGConfig()
|
||||||
|
parseErrors := p.Parse(sample)
|
||||||
|
|
||||||
|
if len(parseErrors) > 0 {
|
||||||
|
t.Fatalf("Parser failed with error %v", parseErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := &wireguard.WGDocument{
|
||||||
|
Config: p,
|
||||||
|
}
|
||||||
|
|
||||||
|
params := &protocol.CompletionParams{
|
||||||
|
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
|
||||||
|
Position: protocol.Position{
|
||||||
|
Line: 1,
|
||||||
|
Character: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
completions, err := SuggestCompletions(d, params)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("getCompletionsForPropertyLine failed with error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(len(completions) == 2) {
|
||||||
|
t.Errorf("getCompletionsForPropertyLine: Expected completions, but got %v", len(completions))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
|
/*
|
||||||
import (
|
import (
|
||||||
|
"config-lsp/handlers/wireguard/ast"
|
||||||
"config-lsp/handlers/wireguard/commands"
|
"config-lsp/handlers/wireguard/commands"
|
||||||
"config-lsp/handlers/wireguard/parser"
|
|
||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetKeepaliveCodeActions(
|
func GetKeepaliveCodeActions(
|
||||||
p *parser.WireguardParser,
|
p *ast.WGConfig,
|
||||||
params *protocol.CodeActionParams,
|
params *protocol.CodeActionParams,
|
||||||
) []protocol.CodeAction {
|
) []protocol.CodeAction {
|
||||||
line := params.Range.Start.Line
|
line := params.Range.Start.Line
|
||||||
|
|
||||||
for index, section := range p.Sections {
|
for index, section := range p.Sections {
|
||||||
if section.StartLine >= line && line <= section.EndLine && section.Name != nil && *section.Name == "Peer" {
|
if section.StartLine >= line && line <= section.EndLine && section.Header != nil && *section.Header == "Peer" {
|
||||||
if section.ExistsProperty("Endpoint") && !section.ExistsProperty("PersistentKeepalive") {
|
if section.ExistsProperty("Endpoint") && !section.ExistsProperty("PersistentKeepalive") {
|
||||||
commandID := "wireguard." + CodeActionAddKeepalive
|
commandID := "wireguard." + CodeActionAddKeepalive
|
||||||
command := protocol.Command{
|
command := protocol.Command{
|
||||||
@ -41,7 +42,7 @@ func GetKeepaliveCodeActions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetKeyGenerationCodeActions(
|
func GetKeyGenerationCodeActions(
|
||||||
p *parser.WireguardParser,
|
p *ast.WGConfig,
|
||||||
params *protocol.CodeActionParams,
|
params *protocol.CodeActionParams,
|
||||||
) []protocol.CodeAction {
|
) []protocol.CodeAction {
|
||||||
line := params.Range.Start.Line
|
line := params.Range.Start.Line
|
||||||
@ -102,3 +103,4 @@ func GetKeyGenerationCodeActions(
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
|
/*
|
||||||
import (
|
import (
|
||||||
docvalues "config-lsp/doc-values"
|
docvalues "config-lsp/doc-values"
|
||||||
|
"config-lsp/handlers/wireguard/ast"
|
||||||
"config-lsp/handlers/wireguard/fields"
|
"config-lsp/handlers/wireguard/fields"
|
||||||
"config-lsp/handlers/wireguard/parser"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getPropertyInfo(
|
func getPropertyInfo(
|
||||||
p parser.WireguardProperty,
|
p ast.WGProperty,
|
||||||
cursor uint32,
|
cursor uint32,
|
||||||
section parser.WireguardSection,
|
section ast.WGSection,
|
||||||
) []string {
|
) []string {
|
||||||
if cursor <= p.Key.Location.End {
|
if cursor <= p.Key.Location.End {
|
||||||
options, found := fields.OptionsHeaderMap[*section.Name]
|
options, found := fields.OptionsHeaderMap[*section.Header]
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return []string{}
|
return []string{}
|
||||||
@ -29,7 +30,7 @@ func getPropertyInfo(
|
|||||||
return strings.Split(option.Documentation, "\n")
|
return strings.Split(option.Documentation, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
options, found := fields.OptionsHeaderMap[*section.Name]
|
options, found := fields.OptionsHeaderMap[*section.Header]
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return []string{}
|
return []string{}
|
||||||
@ -42,19 +43,19 @@ func getPropertyInfo(
|
|||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSectionInfo(s parser.WireguardSection) []string {
|
func getSectionInfo(s ast.WGSection) []string {
|
||||||
if s.Name == nil {
|
if s.Header == nil {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
contents := []string{
|
contents := []string{
|
||||||
fmt.Sprintf("## [%s]", *s.Name),
|
fmt.Sprintf("## [%s]", *s.Header),
|
||||||
"",
|
"",
|
||||||
}
|
}
|
||||||
|
|
||||||
var option *docvalues.EnumString = nil
|
var option *docvalues.EnumString = nil
|
||||||
|
|
||||||
switch *s.Name {
|
switch *s.Header {
|
||||||
case "Interface":
|
case "Interface":
|
||||||
option = &fields.HeaderInterfaceEnum
|
option = &fields.HeaderInterfaceEnum
|
||||||
case "Peer":
|
case "Peer":
|
||||||
@ -71,7 +72,7 @@ func getSectionInfo(s parser.WireguardSection) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetHoverContent(
|
func GetHoverContent(
|
||||||
p parser.WireguardParser,
|
p ast.WGConfig,
|
||||||
line uint32,
|
line uint32,
|
||||||
cursor uint32,
|
cursor uint32,
|
||||||
) []string {
|
) []string {
|
||||||
@ -103,3 +104,4 @@ func GetHoverContent(
|
|||||||
|
|
||||||
return contents
|
return contents
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
@ -1,60 +1,18 @@
|
|||||||
package lsp
|
package lsp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"config-lsp/handlers/wireguard"
|
||||||
"config-lsp/handlers/wireguard/handlers"
|
"config-lsp/handlers/wireguard/handlers"
|
||||||
"config-lsp/handlers/wireguard/parser"
|
"regexp"
|
||||||
|
|
||||||
"github.com/tliron/glsp"
|
"github.com/tliron/glsp"
|
||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var headerPattern = regexp.MustCompile(`^\s*\[(\w+)?]?`)
|
||||||
|
|
||||||
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) {
|
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) {
|
||||||
p := documentParserMap[params.TextDocument.URI]
|
d := wireguard.DocumentParserMap[params.TextDocument.URI]
|
||||||
|
|
||||||
lineNumber := params.Position.Line
|
return handlers.SuggestCompletions(d, params)
|
||||||
|
|
||||||
section := p.GetBelongingSectionByLine(lineNumber)
|
|
||||||
lineType := p.GetTypeByLine(lineNumber)
|
|
||||||
|
|
||||||
switch lineType {
|
|
||||||
case parser.LineTypeComment:
|
|
||||||
return nil, nil
|
|
||||||
case parser.LineTypeHeader:
|
|
||||||
return handlers.GetRootCompletionsForEmptyLine(*p)
|
|
||||||
case parser.LineTypeEmpty:
|
|
||||||
if section.Name == nil {
|
|
||||||
// Root completions
|
|
||||||
return handlers.GetRootCompletionsForEmptyLine(*p)
|
|
||||||
}
|
|
||||||
|
|
||||||
completions, err := handlers.GetCompletionsForSectionEmptyLine(*section)
|
|
||||||
|
|
||||||
// === Smart rules ===
|
|
||||||
|
|
||||||
// If previous line is empty too, maybe new section?
|
|
||||||
if lineNumber >= 1 && p.GetTypeByLine(lineNumber-1) == parser.LineTypeEmpty && len(p.GetBelongingSectionByLine(lineNumber).Properties) > 0 {
|
|
||||||
rootCompletions, err := handlers.GetRootCompletionsForEmptyLine(*p)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
completions = append(completions, rootCompletions...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return completions, err
|
|
||||||
case parser.LineTypeProperty:
|
|
||||||
completions, err := handlers.GetCompletionsForSectionPropertyLine(*section, lineNumber, params.Position.Character)
|
|
||||||
|
|
||||||
if completions == nil && err != nil {
|
|
||||||
switch err.(type) {
|
|
||||||
// Ignore
|
|
||||||
case parser.PropertyNotFullyTypedError:
|
|
||||||
return handlers.GetCompletionsForSectionEmptyLine(*section)
|
|
||||||
default:
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return completions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
panic("TextDocumentCompletion: unexpected line type")
|
|
||||||
}
|
}
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
package parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"config-lsp/utils"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetLineTypeWorksCorrectly(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
# A comment at the very top
|
|
||||||
Test=Hello
|
|
||||||
|
|
||||||
[Interface]
|
|
||||||
PrivateKey = 1234567890 # Some comment
|
|
||||||
Address = 10.0.0.1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Peer]
|
|
||||||
PublicKey = 1234567890
|
|
||||||
|
|
||||||
; I'm a comment
|
|
||||||
`)
|
|
||||||
|
|
||||||
parser := CreateWireguardParser()
|
|
||||||
parser.ParseFromString(sample)
|
|
||||||
|
|
||||||
lineType := parser.GetTypeByLine(0)
|
|
||||||
if lineType != LineTypeComment {
|
|
||||||
t.Fatalf("getTypeByLine: Expected line 0 to be a comment, but it is %v", lineType)
|
|
||||||
}
|
|
||||||
|
|
||||||
lineType = parser.GetTypeByLine(1)
|
|
||||||
if lineType != LineTypeProperty {
|
|
||||||
t.Fatalf("getTypeByLine: Expected line 1 to be a property, but it is %v", lineType)
|
|
||||||
}
|
|
||||||
|
|
||||||
lineType = parser.GetTypeByLine(2)
|
|
||||||
if lineType != LineTypeEmpty {
|
|
||||||
t.Fatalf("getTypeByLine: Expected line 2 to be empty, but it is %v", lineType)
|
|
||||||
}
|
|
||||||
|
|
||||||
lineType = parser.GetTypeByLine(3)
|
|
||||||
if lineType != LineTypeHeader {
|
|
||||||
t.Fatalf("getTypeByLine: Expected line 3 to be a header, but it is %v", lineType)
|
|
||||||
}
|
|
||||||
|
|
||||||
lineType = parser.GetTypeByLine(4)
|
|
||||||
if lineType != LineTypeProperty {
|
|
||||||
t.Fatalf("getTypeByLine: Expected line 4 to be a property, but it is %v", lineType)
|
|
||||||
}
|
|
||||||
|
|
||||||
lineType = parser.GetTypeByLine(12)
|
|
||||||
if lineType != LineTypeComment {
|
|
||||||
t.Fatalf("getTypeByLine: Expected line 12 to be a comment, but it is %v", lineType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBelongingSectionWorksCorrectly(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
# A comment at the very top
|
|
||||||
Test=Hello
|
|
||||||
|
|
||||||
[Interface]
|
|
||||||
PrivateKey = 1234567890 # Some comment
|
|
||||||
Address = 10.0.0.1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Peer]
|
|
||||||
PublicKey = 1234567890
|
|
||||||
|
|
||||||
; I'm a comment
|
|
||||||
`)
|
|
||||||
|
|
||||||
parser := CreateWireguardParser()
|
|
||||||
parser.ParseFromString(sample)
|
|
||||||
|
|
||||||
section := parser.GetBelongingSectionByLine(0)
|
|
||||||
|
|
||||||
// Comment
|
|
||||||
if section != nil {
|
|
||||||
t.Fatalf("getBelongingSectionByLine: Expected line 0 to be in no section, but it is in %v", section)
|
|
||||||
}
|
|
||||||
|
|
||||||
section = parser.GetBelongingSectionByLine(1)
|
|
||||||
|
|
||||||
if section != parser.Sections[1] {
|
|
||||||
t.Fatalf("getBelongingSectionByLine: Expected line 1 to be in global section, but it is in %v", section)
|
|
||||||
}
|
|
||||||
|
|
||||||
section = parser.GetBelongingSectionByLine(2)
|
|
||||||
if section != parser.Sections[1] {
|
|
||||||
t.Fatalf("getBelongingSectionByLine: Expected line 2 to be in global section, but it is in %v", section)
|
|
||||||
}
|
|
||||||
|
|
||||||
section = parser.GetBelongingSectionByLine(3)
|
|
||||||
if section != parser.Sections[2] {
|
|
||||||
t.Fatalf("getBelongingSectionByLine: Expected line 3 to be in section Interface, but it is in %v", section)
|
|
||||||
}
|
|
||||||
|
|
||||||
section = parser.GetBelongingSectionByLine(4)
|
|
||||||
if section != parser.Sections[2] {
|
|
||||||
t.Fatalf("getBelongingSectionByLine: Expected line 4 to be in section Interface, but it is in %v", section)
|
|
||||||
}
|
|
||||||
|
|
||||||
section = parser.GetBelongingSectionByLine(6)
|
|
||||||
if section != parser.Sections[2] {
|
|
||||||
t.Fatalf("getBelongingSectionByLine: Expected line 6 to be in section Interface, but it is in %v", section)
|
|
||||||
}
|
|
||||||
|
|
||||||
section = parser.GetBelongingSectionByLine(10)
|
|
||||||
if section != parser.Sections[3] {
|
|
||||||
t.Fatalf("getBelongingSectionByLine: Expected line 10 to be in section Peer, but it is in %v", section)
|
|
||||||
}
|
|
||||||
|
|
||||||
section = parser.GetBelongingSectionByLine(12)
|
|
||||||
|
|
||||||
// Comment
|
|
||||||
if section != nil {
|
|
||||||
t.Fatalf("getBelongingSectionByLine: Expected line 12 to be in no section, but it is in %v", section)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,258 +0,0 @@
|
|||||||
package parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"config-lsp/common"
|
|
||||||
"config-lsp/handlers/wireguard/ast"
|
|
||||||
"regexp"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commentPattern = regexp.MustCompile(`^\s*(;|#)`)
|
|
||||||
var emptyLinePattern = regexp.MustCompile(`^\s*$`)
|
|
||||||
var headerPattern = regexp.MustCompile(`^\s*\[`)
|
|
||||||
|
|
||||||
func (p *ast.WGConfig) ParseFromString(input string) []common.ParseError {
|
|
||||||
var errors []common.ParseError
|
|
||||||
lines := strings.Split(
|
|
||||||
input,
|
|
||||||
"\n",
|
|
||||||
)
|
|
||||||
|
|
||||||
slices.Reverse(lines)
|
|
||||||
|
|
||||||
collectedProperties := WireguardProperties{}
|
|
||||||
var lastPropertyLine *uint32
|
|
||||||
|
|
||||||
for index, line := range lines {
|
|
||||||
currentLineNumber := uint32(len(lines) - index - 1)
|
|
||||||
lineType := getLineType(line)
|
|
||||||
|
|
||||||
switch lineType {
|
|
||||||
case LineTypeComment:
|
|
||||||
p.commentLines[currentLineNumber] = struct{}{}
|
|
||||||
p.linesIndexes[currentLineNumber] = wireguardLineIndex{
|
|
||||||
Type: LineTypeComment,
|
|
||||||
BelongingSection: nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
case LineTypeEmpty:
|
|
||||||
continue
|
|
||||||
|
|
||||||
case LineTypeProperty:
|
|
||||||
err := collectedProperties.AddLine(currentLineNumber, line)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
errors = append(errors, common.ParseError{
|
|
||||||
Line: currentLineNumber,
|
|
||||||
Err: err,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if lastPropertyLine == nil {
|
|
||||||
lastPropertyLine = ¤tLineNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
case LineTypeHeader:
|
|
||||||
var lastLine uint32
|
|
||||||
|
|
||||||
if lastPropertyLine == nil {
|
|
||||||
// Current line
|
|
||||||
lastLine = currentLineNumber
|
|
||||||
} else {
|
|
||||||
lastLine = *lastPropertyLine
|
|
||||||
}
|
|
||||||
|
|
||||||
section := CreateWireguardSection(
|
|
||||||
currentLineNumber,
|
|
||||||
lastLine,
|
|
||||||
line,
|
|
||||||
collectedProperties,
|
|
||||||
)
|
|
||||||
|
|
||||||
p.Sections = append(p.Sections, §ion)
|
|
||||||
|
|
||||||
// Add indexes
|
|
||||||
for lineNumber := range collectedProperties {
|
|
||||||
p.linesIndexes[lineNumber] = wireguardLineIndex{
|
|
||||||
Type: LineTypeProperty,
|
|
||||||
BelongingSection: §ion,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.linesIndexes[currentLineNumber] = wireguardLineIndex{
|
|
||||||
Type: LineTypeHeader,
|
|
||||||
BelongingSection: §ion,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset
|
|
||||||
collectedProperties = WireguardProperties{}
|
|
||||||
lastPropertyLine = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var emptySection *ast.WGSection
|
|
||||||
|
|
||||||
if len(collectedProperties) > 0 {
|
|
||||||
var endLine uint32
|
|
||||||
|
|
||||||
if len(p.Sections) == 0 {
|
|
||||||
endLine = uint32(len(lines))
|
|
||||||
} else {
|
|
||||||
endLine = p.Sections[len(p.Sections)-1].StartLine
|
|
||||||
}
|
|
||||||
|
|
||||||
emptySection = &ast.WGSection{
|
|
||||||
StartLine: 0,
|
|
||||||
EndLine: endLine,
|
|
||||||
Properties: collectedProperties,
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Sections = append(p.Sections, emptySection)
|
|
||||||
|
|
||||||
for lineNumber := range collectedProperties {
|
|
||||||
p.linesIndexes[lineNumber] = wireguardLineIndex{
|
|
||||||
Type: LineTypeProperty,
|
|
||||||
BelongingSection: emptySection,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.Sections = append(p.Sections, emptySection)
|
|
||||||
} else {
|
|
||||||
// Add empty section
|
|
||||||
var endLine = uint32(len(lines))
|
|
||||||
|
|
||||||
if len(p.Sections) > 0 {
|
|
||||||
endLine = p.Sections[len(p.Sections)-1].StartLine
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add empty section
|
|
||||||
if endLine != 0 {
|
|
||||||
emptySection = &ast.WGSection{
|
|
||||||
StartLine: 0,
|
|
||||||
EndLine: endLine,
|
|
||||||
Properties: collectedProperties,
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Sections = append(p.Sections, emptySection)
|
|
||||||
|
|
||||||
for newLine := uint32(0); newLine < endLine; newLine++ {
|
|
||||||
if _, found := p.linesIndexes[newLine]; found {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
p.linesIndexes[newLine] = wireguardLineIndex{
|
|
||||||
Type: LineTypeEmpty,
|
|
||||||
BelongingSection: emptySection,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since we parse the content from bottom to top, we need to reverse the sections
|
|
||||||
// so its in correct order
|
|
||||||
slices.Reverse(p.Sections)
|
|
||||||
|
|
||||||
// Fill empty lines between sections
|
|
||||||
for lineNumber, section := range p.Sections {
|
|
||||||
var endLine uint32
|
|
||||||
|
|
||||||
if len(p.Sections) > lineNumber+1 {
|
|
||||||
nextSection := p.Sections[lineNumber+1]
|
|
||||||
endLine = nextSection.StartLine
|
|
||||||
} else {
|
|
||||||
endLine = uint32(len(lines))
|
|
||||||
}
|
|
||||||
|
|
||||||
for newLine := section.StartLine; newLine < endLine; newLine++ {
|
|
||||||
if _, found := p.linesIndexes[newLine]; found {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
p.linesIndexes[newLine] = wireguardLineIndex{
|
|
||||||
Type: LineTypeEmpty,
|
|
||||||
BelongingSection: section,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ast.WGConfig) GetSectionByLine(line uint32) *ast.WGSection {
|
|
||||||
for _, section := range p.Sections {
|
|
||||||
if section.StartLine <= line && section.EndLine >= line {
|
|
||||||
return section
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for a property by name
|
|
||||||
// Returns (line number, property)
|
|
||||||
func (p *ast.WGConfig) FindFirstPropertyByName(name string) (*uint32, *ast.WGProperty) {
|
|
||||||
for _, section := range p.Sections {
|
|
||||||
for lineNumber, property := range section.Properties {
|
|
||||||
if property.Key.Name == name {
|
|
||||||
return &lineNumber, &property
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ast.WGConfig) GetInterfaceSection() (*ast.WGSection, bool) {
|
|
||||||
for _, section := range p.Sections {
|
|
||||||
if section.Header != nil && *section.Header == "Interface" {
|
|
||||||
return section, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ast.WGConfig) GetTypeByLine(line uint32) LineType {
|
|
||||||
// Check if line is a comment
|
|
||||||
if _, found := p.commentLines[line]; found {
|
|
||||||
return LineTypeComment
|
|
||||||
}
|
|
||||||
|
|
||||||
if info, found := p.linesIndexes[line]; found {
|
|
||||||
return info.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
return LineTypeEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateWireguardParser() ast.WGConfig {
|
|
||||||
parser := ast.WGConfig{}
|
|
||||||
parser.Clear()
|
|
||||||
|
|
||||||
return parser
|
|
||||||
}
|
|
||||||
|
|
||||||
type LineType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
LineTypeComment LineType = "comment"
|
|
||||||
LineTypeEmpty LineType = "empty"
|
|
||||||
LineTypeHeader LineType = "header"
|
|
||||||
LineTypeProperty LineType = "property"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getLineType(line string) LineType {
|
|
||||||
if commentPattern.MatchString(line) {
|
|
||||||
return LineTypeComment
|
|
||||||
}
|
|
||||||
|
|
||||||
if emptyLinePattern.MatchString(line) {
|
|
||||||
return LineTypeEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
if headerPattern.MatchString(line) {
|
|
||||||
return LineTypeHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
return LineTypeProperty
|
|
||||||
}
|
|
@ -1,357 +0,0 @@
|
|||||||
package parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"config-lsp/utils"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/k0kubun/pp"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestValidWildTestWorksFine(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
[Interface]
|
|
||||||
PrivateKey = 1234567890
|
|
||||||
Address = 192.168.1.0/24
|
|
||||||
|
|
||||||
# I'm a comment
|
|
||||||
[Peer]
|
|
||||||
PublicKey = 1234567890
|
|
||||||
Endpoint = 1.2.3.4 ; I'm just a comment
|
|
||||||
|
|
||||||
[Peer]
|
|
||||||
PublicKey = 5555
|
|
||||||
`)
|
|
||||||
|
|
||||||
parser := CreateWireguardParser()
|
|
||||||
errors := parser.ParseFromString(sample)
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
t.Fatalf("parseFromString failed with error %v", errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.commentLines) == 1 && utils.KeyExists(parser.commentLines, 4)) {
|
|
||||||
t.Fatalf("parseFromString failed to collect comment lines %v", parser.commentLines)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !((len(parser.Sections) == 3) && (*parser.Sections[0].Header == "Interface") && (*parser.Sections[1].Header == "Peer") && (*parser.Sections[2].Header == "Peer")) {
|
|
||||||
t.Fatalf("parseFromString failed to collect sections %v", parser.Sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(parser.Sections[0].StartLine == 0 && parser.Sections[0].EndLine == 2 && parser.Sections[1].StartLine == 5 && parser.Sections[1].EndLine == 7 && parser.Sections[2].StartLine == 9 && parser.Sections[2].EndLine == 10) {
|
|
||||||
t.Fatalf("parseFromString: Invalid start and end lines %v", parser.Sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !((len(parser.Sections[0].Properties) == 2) && (len(parser.Sections[1].Properties) == 2) && (len(parser.Sections[2].Properties) == 1)) {
|
|
||||||
t.Fatalf("parseFromString: Invalid amount of properties %v", parser.Sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !((parser.Sections[0].Properties[1].Key.Name == "PrivateKey") && (parser.Sections[0].Properties[2].Key.Name == "Address")) {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties of section 0 %v", parser.Sections[0].Properties)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !((parser.Sections[1].Properties[6].Key.Name == "PublicKey") && (parser.Sections[1].Properties[7].Key.Name == "Endpoint")) {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties of section 1 %v", parser.Sections[1].Properties)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(parser.Sections[2].Properties[10].Key.Name == "PublicKey") {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties of section 2 %v", parser.Sections[2].Properties)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if line indexes are correct
|
|
||||||
if !(parser.linesIndexes[0].Type == LineTypeHeader &&
|
|
||||||
parser.linesIndexes[1].Type == LineTypeProperty &&
|
|
||||||
parser.linesIndexes[2].Type == LineTypeProperty &&
|
|
||||||
parser.linesIndexes[3].Type == LineTypeEmpty &&
|
|
||||||
parser.linesIndexes[4].Type == LineTypeComment &&
|
|
||||||
parser.linesIndexes[5].Type == LineTypeHeader &&
|
|
||||||
parser.linesIndexes[6].Type == LineTypeProperty &&
|
|
||||||
parser.linesIndexes[7].Type == LineTypeProperty &&
|
|
||||||
parser.linesIndexes[8].Type == LineTypeEmpty &&
|
|
||||||
parser.linesIndexes[9].Type == LineTypeHeader &&
|
|
||||||
parser.linesIndexes[10].Type == LineTypeProperty) {
|
|
||||||
pp.Println(parser.linesIndexes)
|
|
||||||
t.Fatal("parseFromString: Invalid line indexes")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptySectionAtStartWorksFine(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
[Interface]
|
|
||||||
|
|
||||||
[Peer]
|
|
||||||
PublicKey = 1234567890
|
|
||||||
`)
|
|
||||||
|
|
||||||
parser := CreateWireguardParser()
|
|
||||||
errors := parser.ParseFromString(sample)
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
t.Fatalf("parseFromString failed with error %v", errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !((len(parser.Sections) == 2) && (*parser.Sections[0].Header == "Interface") && (*parser.Sections[1].Header == "Peer")) {
|
|
||||||
t.Fatalf("parseFromString failed to collect sections %v", parser.Sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.Sections[0].Properties) == 0 && len(parser.Sections[1].Properties) == 1) {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties %v", parser.Sections)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptySectionAtEndWorksFine(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
[Inteface]
|
|
||||||
PrivateKey = 1234567890
|
|
||||||
|
|
||||||
[Peer]
|
|
||||||
# Just sneaking in here, hehe
|
|
||||||
`)
|
|
||||||
|
|
||||||
parser := CreateWireguardParser()
|
|
||||||
errors := parser.ParseFromString(sample)
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
t.Fatalf("parseFromString failed with error %v", errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !((len(parser.Sections) == 2) && (*parser.Sections[0].Header == "Inteface") && (*parser.Sections[1].Header == "Peer")) {
|
|
||||||
t.Fatalf("parseFromString failed to collect sections %v", parser.Sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.Sections[0].Properties) == 1 && len(parser.Sections[1].Properties) == 0) {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties %v", parser.Sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.commentLines) == 1 && utils.KeyExists(parser.commentLines, 4)) {
|
|
||||||
t.Fatalf("parseFromString failed to collect comment lines %v", parser.commentLines)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmptyFileWorksFine(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
`)
|
|
||||||
|
|
||||||
parser := CreateWireguardParser()
|
|
||||||
errors := parser.ParseFromString(sample)
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
t.Fatalf("parseFromString failed with error %v", errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.Sections) == 1) {
|
|
||||||
t.Fatalf("parseFromString failed to collect sections %v", parser.Sections)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPartialSectionWithNoPropertiesWorksFine(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
[Inte
|
|
||||||
|
|
||||||
[Peer]
|
|
||||||
PublicKey = 1234567890
|
|
||||||
`)
|
|
||||||
|
|
||||||
parser := CreateWireguardParser()
|
|
||||||
errors := parser.ParseFromString(sample)
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
t.Fatalf("parseFromString failed with error %v", errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !((len(parser.Sections) == 2) && (*parser.Sections[0].Header == "Inte") && (*parser.Sections[1].Header == "Peer")) {
|
|
||||||
t.Fatalf("parseFromString failed to collect sections: %v", parser.Sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.Sections[0].Properties) == 0 && len(parser.Sections[1].Properties) == 1) {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties: %v", parser.Sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.commentLines) == 0) {
|
|
||||||
t.Fatalf("parseFromString failed to collect comment lines: %v", parser.commentLines)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(parser.Sections[1].Properties[3].Key.Name == "PublicKey") {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties of section 1: %v", parser.Sections[1].Properties)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPartialSectionWithPropertiesWorksFine(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
[Inte
|
|
||||||
PrivateKey = 1234567890
|
|
||||||
|
|
||||||
[Peer]
|
|
||||||
`)
|
|
||||||
|
|
||||||
parser := CreateWireguardParser()
|
|
||||||
errors := parser.ParseFromString(sample)
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
t.Fatalf("parseFromString failed with error: %v", errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !((len(parser.Sections) == 2) && (*parser.Sections[0].Header == "Inte") && (*parser.Sections[1].Header == "Peer")) {
|
|
||||||
t.Fatalf("parseFromString failed to collect sections: %v", parser.Sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.Sections[0].Properties) == 1 && len(parser.Sections[1].Properties) == 0) {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties: %v", parser.Sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(parser.Sections[0].Properties[1].Key.Name == "PrivateKey") {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFileWithOnlyComments(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
# This is a comment
|
|
||||||
# Another comment
|
|
||||||
`)
|
|
||||||
parser := CreateWireguardParser()
|
|
||||||
errors := parser.ParseFromString(sample)
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
t.Fatalf("parseFromString failed with error: %v", errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.Sections) == 1) {
|
|
||||||
t.Fatalf("parseFromString failed to collect sections: %v", parser.Sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.commentLines) == 2) {
|
|
||||||
t.Fatalf("parseFromString failed to collect comment lines: %v", parser.commentLines)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(utils.KeyExists(parser.commentLines, 0) && utils.KeyExists(parser.commentLines, 1)) {
|
|
||||||
t.Fatalf("parseFromString failed to collect comment lines: %v", parser.commentLines)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultipleSectionsNoProperties(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
[Interface]
|
|
||||||
[Peer]
|
|
||||||
[Peer]
|
|
||||||
`)
|
|
||||||
|
|
||||||
parser := CreateWireguardParser()
|
|
||||||
errors := parser.ParseFromString(sample)
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
t.Fatalf("parseFromString failed with error: %v", errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.Sections) == 3) {
|
|
||||||
t.Fatalf("parseFromString failed to collect sections: %v", parser.Sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, section := range parser.Sections {
|
|
||||||
if len(section.Properties) != 0 {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties: %v", section.Properties)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWildTest1WorksCorrectly(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
[Interface]
|
|
||||||
DNS=1.1.1.1
|
|
||||||
|
|
||||||
|
|
||||||
`)
|
|
||||||
parser := CreateWireguardParser()
|
|
||||||
errors := parser.ParseFromString(sample)
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
t.Fatalf("parseFromString failed with error: %v", errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.Sections) == 1) {
|
|
||||||
t.Fatalf("parseFromString failed to collect sections: %v", parser.Sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.Sections[0].Properties) == 1) {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties: %v", parser.Sections[0].Properties)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(parser.Sections[0].Properties[1].Key.Name == "DNS") {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(parser.Sections[0].Properties[1].Value.Value == "1.1.1.1") {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(len(parser.commentLines) == 0) {
|
|
||||||
t.Fatalf("parseFromString failed to collect comment lines: %v", parser.commentLines)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(parser.Sections[0].StartLine == 0 && parser.Sections[0].EndLine == 1) {
|
|
||||||
t.Fatalf("parseFromString: Invalid start and end lines %v", parser.Sections)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPartialKeyWorksCorrectly(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
[Interface]
|
|
||||||
DNS
|
|
||||||
`)
|
|
||||||
parser := CreateWireguardParser()
|
|
||||||
errors := parser.ParseFromString(sample)
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
t.Fatalf("parseFromString failed with error: %v", errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(parser.Sections[0].Properties[1].Key.Name == "DNS") {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(parser.Sections[0].Properties[1].Separator == nil) {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPartialValueWithSeparatorWorksCorrectly(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
sample := utils.Dedent(`
|
|
||||||
[Interface]
|
|
||||||
DNS=
|
|
||||||
`)
|
|
||||||
parser := CreateWireguardParser()
|
|
||||||
errors := parser.ParseFromString(sample)
|
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
t.Fatalf("parseFromString failed with error: %v", errors)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(parser.Sections[0].Properties[1].Value == nil) {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(parser.Sections[0].Properties[1].Separator != nil) {
|
|
||||||
t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
package parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
docvalues "config-lsp/doc-values"
|
|
||||||
"config-lsp/handlers/wireguard/ast"
|
|
||||||
"config-lsp/utils"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
|
||||||
)
|
|
||||||
|
|
||||||
var linePattern = regexp.MustCompile(`^\s*(?P<key>.+?)\s*(?P<separator>=)\s*(?P<value>\S.*?)?\s*(?:(?:;|#).*)?\s*$`)
|
|
||||||
|
|
||||||
// WireguardProperties [<line number>]: <property>
|
|
||||||
type WireguardProperties map[uint32]ast.WGProperty
|
|
||||||
|
|
||||||
func (p *WireguardProperties) AddLine(lineNumber uint32, line string) error {
|
|
||||||
property, err := CreateWireguardProperty(line)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
(*p)[lineNumber] = *property
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateWireguardProperty(line string) (*ast.WGProperty, error) {
|
|
||||||
if !strings.Contains(line, "=") {
|
|
||||||
indexes := utils.GetTrimIndex(line)
|
|
||||||
|
|
||||||
if indexes == nil {
|
|
||||||
// weird, should not happen
|
|
||||||
return nil, &docvalues.MalformedLineError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ast.WGProperty{
|
|
||||||
Key: ast.WGPropertyKey{
|
|
||||||
Name: line[indexes[0]:indexes[1]],
|
|
||||||
Location: CharacterLocation{
|
|
||||||
Start: uint32(indexes[0]),
|
|
||||||
End: uint32(indexes[1]),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
indexes := linePattern.FindStringSubmatchIndex(line)
|
|
||||||
|
|
||||||
if indexes == nil || len(indexes) == 0 {
|
|
||||||
return nil, &docvalues.MalformedLineError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
keyStart := uint32(indexes[2])
|
|
||||||
keyEnd := uint32(indexes[3])
|
|
||||||
key := ast.WGPropertyKey{
|
|
||||||
Location: CharacterLocation{
|
|
||||||
Start: keyStart,
|
|
||||||
End: keyEnd,
|
|
||||||
},
|
|
||||||
Name: line[keyStart:keyEnd],
|
|
||||||
}
|
|
||||||
|
|
||||||
separatorStart := uint32(indexes[4])
|
|
||||||
separatorEnd := uint32(indexes[5])
|
|
||||||
separator := ast.WGPropertySeparator{
|
|
||||||
Location: CharacterLocation{
|
|
||||||
Start: separatorStart,
|
|
||||||
End: separatorEnd,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var value *ast.WGPropertyValue
|
|
||||||
|
|
||||||
if indexes[6] != -1 && indexes[7] != -1 {
|
|
||||||
// value exists
|
|
||||||
valueStart := uint32(indexes[6])
|
|
||||||
valueEnd := uint32(indexes[7])
|
|
||||||
|
|
||||||
value = &ast.WGPropertyValue{
|
|
||||||
Location: CharacterLocation{
|
|
||||||
Start: valueStart,
|
|
||||||
End: valueEnd,
|
|
||||||
},
|
|
||||||
Value: line[valueStart:valueEnd],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ast.WGProperty{
|
|
||||||
Key: key,
|
|
||||||
Separator: &separator,
|
|
||||||
Value: value,
|
|
||||||
}, nil
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
package parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"config-lsp/handlers/wireguard/ast"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PropertyNotFoundError struct{}
|
|
||||||
|
|
||||||
func (e PropertyNotFoundError) Error() string {
|
|
||||||
return "Property not found"
|
|
||||||
}
|
|
||||||
|
|
||||||
type PropertyNotFullyTypedError struct{}
|
|
||||||
|
|
||||||
func (e PropertyNotFullyTypedError) Error() string {
|
|
||||||
return "Property not fully typed"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s ast.WGSection) FetchFirstProperty(name string) (*uint32, *ast.WGProperty) {
|
|
||||||
for line, property := range s.Properties {
|
|
||||||
if property.Key.Name == name {
|
|
||||||
return &line, &property
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s ast.WGSection) ExistsProperty(name string) bool {
|
|
||||||
_, property := s.FetchFirstProperty(name)
|
|
||||||
|
|
||||||
return property != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s ast.WGSection) GetPropertyByLine(lineNumber uint32) (*ast.WGProperty, error) {
|
|
||||||
property, found := s.Properties[lineNumber]
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
return nil, PropertyNotFoundError{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &property, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var validHeaderPattern = regexp.MustCompile(`^\s*\[(?P<header>.+?)\]\s*$`)
|
|
||||||
|
|
||||||
// Create a new create section
|
|
||||||
// Return (<name>, <new section>)
|
|
||||||
func CreateWireguardSection(
|
|
||||||
startLine uint32,
|
|
||||||
endLine uint32,
|
|
||||||
headerLine string,
|
|
||||||
props WireguardProperties,
|
|
||||||
) ast.WGSection {
|
|
||||||
match := validHeaderPattern.FindStringSubmatch(headerLine)
|
|
||||||
|
|
||||||
var header string
|
|
||||||
|
|
||||||
if match == nil {
|
|
||||||
// Still typing it
|
|
||||||
header = headerLine[1:]
|
|
||||||
} else {
|
|
||||||
header = match[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return ast.WGSection{
|
|
||||||
Header: &header,
|
|
||||||
StartLine: startLine,
|
|
||||||
EndLine: endLine,
|
|
||||||
Properties: props,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +1,14 @@
|
|||||||
package wireguard
|
package wireguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/handlers/wireguard/ast"
|
||||||
|
"config-lsp/handlers/wireguard/indexes"
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WGDocument struct {
|
||||||
|
Config *ast.WGConfig
|
||||||
|
Indexes *indexes.WGIndexes
|
||||||
|
}
|
||||||
|
|
||||||
|
var DocumentParserMap = map[protocol.DocumentUri]*WGDocument{}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user