mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +02:00
feat(ssh_config): Add analyzer; Overall improvements
This commit is contained in:
parent
82301cf0cb
commit
e19ad01fd8
@ -1,6 +1,8 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"config-lsp/utils"
|
||||||
|
|
||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,3 +32,12 @@ type SyntaxError struct {
|
|||||||
func (s SyntaxError) Error() string {
|
func (s SyntaxError) Error() string {
|
||||||
return s.Message
|
return s.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ErrsToDiagnostics(errs []LSPError) []protocol.Diagnostic {
|
||||||
|
return utils.Map(
|
||||||
|
errs,
|
||||||
|
func(err LSPError) protocol.Diagnostic {
|
||||||
|
return err.ToDiagnostic()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,12 +1,33 @@
|
|||||||
package analyzer
|
package analyzer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
sshconfig "config-lsp/handlers/ssh_config"
|
sshconfig "config-lsp/handlers/ssh_config"
|
||||||
|
"config-lsp/handlers/ssh_config/indexes"
|
||||||
|
|
||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Analyze(
|
func Analyze(
|
||||||
d *sshconfig.SSHDocument,
|
d *sshconfig.SSHDocument,
|
||||||
) []protocol.Diagnostic {
|
) []protocol.Diagnostic {
|
||||||
return nil
|
errors := analyzeStructureIsValid(d)
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return common.ErrsToDiagnostics(errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
i, indexErrors := indexes.CreateIndexes(*d.Config)
|
||||||
|
|
||||||
|
d.Indexes = i
|
||||||
|
|
||||||
|
errors = append(errors, indexErrors...)
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return common.ErrsToDiagnostics(errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
errors = append(errors, analyzeDependents(d)...)
|
||||||
|
|
||||||
|
return common.ErrsToDiagnostics(errors)
|
||||||
}
|
}
|
||||||
|
55
handlers/ssh_config/analyzer/dependents.go
Normal file
55
handlers/ssh_config/analyzer/dependents.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package analyzer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
sshconfig "config-lsp/handlers/ssh_config"
|
||||||
|
"config-lsp/handlers/ssh_config/ast"
|
||||||
|
"config-lsp/handlers/ssh_config/fields"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func analyzeDependents(
|
||||||
|
d *sshconfig.SSHDocument,
|
||||||
|
) []common.LSPError {
|
||||||
|
errs := make([]common.LSPError, 0)
|
||||||
|
|
||||||
|
for _, option := range d.Config.GetAllOptions() {
|
||||||
|
errs = append(errs, checkIsDependent(d, option.Option.Key, option.Block)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkIsDependent(
|
||||||
|
d *sshconfig.SSHDocument,
|
||||||
|
key *ast.SSHKey,
|
||||||
|
block ast.SSHBlock,
|
||||||
|
) []common.LSPError {
|
||||||
|
errs := make([]common.LSPError, 0)
|
||||||
|
|
||||||
|
dependentOptions, found := fields.DependentFields[key.Key]
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dependentOption := range dependentOptions {
|
||||||
|
if opts, found := d.Indexes.AllOptionsPerName[dependentOption]; found {
|
||||||
|
_, existsInBlock := opts[block]
|
||||||
|
_, existsInGlobal := opts[nil]
|
||||||
|
|
||||||
|
if existsInBlock || existsInGlobal {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errs = append(errs, common.LSPError{
|
||||||
|
Range: key.LocationRange,
|
||||||
|
Err: errors.New(fmt.Sprintf("Option '%s' requires option '%s' to be present", key.Key, dependentOption)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
@ -22,13 +22,13 @@ func analyzeStructureIsValid(
|
|||||||
|
|
||||||
switch entry.(type) {
|
switch entry.(type) {
|
||||||
case *ast.SSHOption:
|
case *ast.SSHOption:
|
||||||
errs = append(errs, checkOption(entry.(*ast.SSHOption), nil)...)
|
errs = append(errs, checkOption(d, entry.(*ast.SSHOption), nil)...)
|
||||||
case *ast.SSHMatchBlock:
|
case *ast.SSHMatchBlock:
|
||||||
matchBlock := entry.(*ast.SSHMatchBlock)
|
matchBlock := entry.(*ast.SSHMatchBlock)
|
||||||
errs = append(errs, checkMatchBlock(matchBlock)...)
|
errs = append(errs, checkMatchBlock(d, matchBlock)...)
|
||||||
case *ast.SSHHostBlock:
|
case *ast.SSHHostBlock:
|
||||||
hostBlock := entry.(*ast.SSHHostBlock)
|
hostBlock := entry.(*ast.SSHHostBlock)
|
||||||
errs = append(errs, checkHostBlock(hostBlock)...)
|
errs = append(errs, checkHostBlock(d, hostBlock)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -37,6 +37,7 @@ func analyzeStructureIsValid(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkOption(
|
func checkOption(
|
||||||
|
d *sshconfig.SSHDocument,
|
||||||
option *ast.SSHOption,
|
option *ast.SSHOption,
|
||||||
block ast.SSHBlock,
|
block ast.SSHBlock,
|
||||||
) []common.LSPError {
|
) []common.LSPError {
|
||||||
@ -78,13 +79,10 @@ func checkOption(
|
|||||||
} else {
|
} else {
|
||||||
errs = append(errs, checkIsUsingDoubleQuotes(option.OptionValue.Value, option.OptionValue.LocationRange)...)
|
errs = append(errs, checkIsUsingDoubleQuotes(option.OptionValue.Value, option.OptionValue.LocationRange)...)
|
||||||
errs = append(errs, checkQuotesAreClosed(option.OptionValue.Value, option.OptionValue.LocationRange)...)
|
errs = append(errs, checkQuotesAreClosed(option.OptionValue.Value, option.OptionValue.LocationRange)...)
|
||||||
|
|
||||||
invalidValues := docOption.CheckIsValid(option.OptionValue.Value.Value)
|
|
||||||
|
|
||||||
errs = append(
|
errs = append(
|
||||||
errs,
|
errs,
|
||||||
utils.Map(
|
utils.Map(
|
||||||
invalidValues,
|
docOption.CheckIsValid(option.OptionValue.Value.Value),
|
||||||
func(invalidValue *docvalues.InvalidValue) common.LSPError {
|
func(invalidValue *docvalues.InvalidValue) common.LSPError {
|
||||||
err := docvalues.LSPErrorFromInvalidValue(option.Start.Line, *invalidValue)
|
err := docvalues.LSPErrorFromInvalidValue(option.Start.Line, *invalidValue)
|
||||||
err.ShiftCharacter(option.OptionValue.Start.Character)
|
err.ShiftCharacter(option.OptionValue.Start.Character)
|
||||||
@ -109,6 +107,7 @@ func checkOption(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func checkMatchBlock(
|
func checkMatchBlock(
|
||||||
|
d *sshconfig.SSHDocument,
|
||||||
matchBlock *ast.SSHMatchBlock,
|
matchBlock *ast.SSHMatchBlock,
|
||||||
) []common.LSPError {
|
) []common.LSPError {
|
||||||
errs := make([]common.LSPError, 0)
|
errs := make([]common.LSPError, 0)
|
||||||
@ -117,13 +116,14 @@ func checkMatchBlock(
|
|||||||
for it.Next() {
|
for it.Next() {
|
||||||
option := it.Value().(*ast.SSHOption)
|
option := it.Value().(*ast.SSHOption)
|
||||||
|
|
||||||
errs = append(errs, checkOption(option, matchBlock)...)
|
errs = append(errs, checkOption(d, option, matchBlock)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkHostBlock(
|
func checkHostBlock(
|
||||||
|
d *sshconfig.SSHDocument,
|
||||||
hostBlock *ast.SSHHostBlock,
|
hostBlock *ast.SSHHostBlock,
|
||||||
) []common.LSPError {
|
) []common.LSPError {
|
||||||
errs := make([]common.LSPError, 0)
|
errs := make([]common.LSPError, 0)
|
||||||
@ -132,7 +132,7 @@ func checkHostBlock(
|
|||||||
for it.Next() {
|
for it.Next() {
|
||||||
option := it.Value().(*ast.SSHOption)
|
option := it.Value().(*ast.SSHOption)
|
||||||
|
|
||||||
errs = append(errs, checkOption(option, hostBlock)...)
|
errs = append(errs, checkOption(d, option, hostBlock)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
|
@ -60,3 +60,36 @@ func TestIncompleteQuotesExample(
|
|||||||
t.Errorf("Expected 1 error, got %v", len(errors))
|
t.Errorf("Expected 1 error, got %v", len(errors))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDependentOptionsExample(
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
d := testutils_test.DocumentFromInput(t, `
|
||||||
|
Port 1234
|
||||||
|
CanonicalDomains example.com
|
||||||
|
`)
|
||||||
|
|
||||||
|
option := d.FindOptionsByName("CanonicalDomains")[0]
|
||||||
|
errors := checkIsDependent(d, option.Option.Key, option.Block)
|
||||||
|
|
||||||
|
if !(len(errors) == 1) {
|
||||||
|
t.Errorf("Expected 1 error, got %v", len(errors))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidDependentOptionsExample(
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
d := testutils_test.DocumentFromInput(t, `
|
||||||
|
Port 1234
|
||||||
|
CanonicalizeHostname yes
|
||||||
|
CanonicalDomains example.com
|
||||||
|
`)
|
||||||
|
|
||||||
|
option := d.FindOptionsByName("CanonicalDomains")[0]
|
||||||
|
errors := checkIsDependent(d, option.Option.Key, option.Block)
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
t.Errorf("Expected no errors, got %v", len(errors))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -16,6 +16,8 @@ Host laptop
|
|||||||
|
|
||||||
Match originalhost laptop exec "[[ $(/usr/bin/dig +short laptop.lan) == '' ]]"
|
Match originalhost laptop exec "[[ $(/usr/bin/dig +short laptop.lan) == '' ]]"
|
||||||
HostName laptop.sdn
|
HostName laptop.sdn
|
||||||
|
|
||||||
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
p := NewSSHConfig()
|
p := NewSSHConfig()
|
||||||
@ -51,4 +53,9 @@ Match originalhost laptop exec "[[ $(/usr/bin/dig +short laptop.lan) == '' ]]"
|
|||||||
if !(thirdBlock.GetLocation().Start.Line == 5) {
|
if !(thirdBlock.GetLocation().Start.Line == 5) {
|
||||||
t.Errorf("Expected line 3, got %v", thirdBlock.GetLocation().Start.Line)
|
t.Errorf("Expected line 3, got %v", thirdBlock.GetLocation().Start.Line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fourthOption, fourthBlock := p.FindOption(8)
|
||||||
|
if !(fourthOption == nil && fourthBlock == thirdBlock) {
|
||||||
|
t.Errorf("Expected no option and same block, got %v and %v", fourthOption, fourthBlock)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,9 +108,19 @@ func (b *SSHHostBlock) GetOption() *SSHOption {
|
|||||||
return b.HostOption
|
return b.HostOption
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindBlock Gets the block based on the line number
|
||||||
|
// Note: This does not find the block strictly.
|
||||||
|
// This means an empty line will belong to the previous block
|
||||||
|
// However, this is required for example for completions, as the
|
||||||
|
// user is about to type the new option, and we therefore need to know
|
||||||
|
// which block this new option will belong to.
|
||||||
|
//
|
||||||
|
// You will probably need this in most cases
|
||||||
func (c SSHConfig) FindBlock(line uint32) SSHBlock {
|
func (c SSHConfig) FindBlock(line uint32) SSHBlock {
|
||||||
it := c.Options.Iterator()
|
it := c.Options.Iterator()
|
||||||
for it.Next() {
|
it.End()
|
||||||
|
|
||||||
|
for it.Prev() {
|
||||||
entry := it.Value().(SSHEntry)
|
entry := it.Value().(SSHEntry)
|
||||||
|
|
||||||
if entry.GetType() == SSHTypeOption {
|
if entry.GetType() == SSHTypeOption {
|
||||||
@ -119,7 +129,7 @@ func (c SSHConfig) FindBlock(line uint32) SSHBlock {
|
|||||||
|
|
||||||
block := entry.(SSHBlock)
|
block := entry.(SSHBlock)
|
||||||
|
|
||||||
if block.GetLocation().Start.Line <= line && block.GetLocation().End.Line >= line {
|
if line >= block.GetLocation().Start.Line {
|
||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,3 +29,10 @@ func (d SSHDocument) FindOptionsByName(
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d SSHDocument) DoesOptionExist(
|
||||||
|
name string,
|
||||||
|
block ast.SSHBlock,
|
||||||
|
) bool {
|
||||||
|
return d.FindOptionByNameAndBlock(name, block) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -17,3 +17,5 @@ var HostDisallowedOptions = map[string]struct{}{
|
|||||||
"EnableSSHKeysign": {},
|
"EnableSSHKeysign": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var GlobalDisallowedOptions = map[string]struct{}{}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ func GetRootCompletions(
|
|||||||
|
|
||||||
for key, option := range fields.Options {
|
for key, option := range fields.Options {
|
||||||
// Check for duplicates
|
// Check for duplicates
|
||||||
if d.FindOptionByNameAndBlock(key, parentBlock) != nil && !utils.KeyExists(fields.AllowedDuplicateOptions, key) {
|
if d.DoesOptionExist(key, parentBlock) && !utils.KeyExists(fields.AllowedDuplicateOptions, key) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +30,10 @@ func GetRootCompletions(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if parentBlock == nil && utils.KeyExists(fields.GlobalDisallowedOptions, key) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
availableOptions[key] = option
|
availableOptions[key] = option
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user