mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 15:05:28 +02:00
feat(ssh_config): Add analyzer; Overall improvements
This commit is contained in:
parent
82301cf0cb
commit
e19ad01fd8
@ -1,6 +1,8 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"config-lsp/utils"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
@ -30,3 +32,12 @@ type SyntaxError struct {
|
||||
func (s SyntaxError) Error() string {
|
||||
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
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
sshconfig "config-lsp/handlers/ssh_config"
|
||||
"config-lsp/handlers/ssh_config/indexes"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func Analyze(
|
||||
d *sshconfig.SSHDocument,
|
||||
) []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) {
|
||||
case *ast.SSHOption:
|
||||
errs = append(errs, checkOption(entry.(*ast.SSHOption), nil)...)
|
||||
errs = append(errs, checkOption(d, entry.(*ast.SSHOption), nil)...)
|
||||
case *ast.SSHMatchBlock:
|
||||
matchBlock := entry.(*ast.SSHMatchBlock)
|
||||
errs = append(errs, checkMatchBlock(matchBlock)...)
|
||||
errs = append(errs, checkMatchBlock(d, matchBlock)...)
|
||||
case *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(
|
||||
d *sshconfig.SSHDocument,
|
||||
option *ast.SSHOption,
|
||||
block ast.SSHBlock,
|
||||
) []common.LSPError {
|
||||
@ -78,13 +79,10 @@ func checkOption(
|
||||
} else {
|
||||
errs = append(errs, checkIsUsingDoubleQuotes(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,
|
||||
utils.Map(
|
||||
invalidValues,
|
||||
docOption.CheckIsValid(option.OptionValue.Value.Value),
|
||||
func(invalidValue *docvalues.InvalidValue) common.LSPError {
|
||||
err := docvalues.LSPErrorFromInvalidValue(option.Start.Line, *invalidValue)
|
||||
err.ShiftCharacter(option.OptionValue.Start.Character)
|
||||
@ -109,6 +107,7 @@ func checkOption(
|
||||
}
|
||||
|
||||
func checkMatchBlock(
|
||||
d *sshconfig.SSHDocument,
|
||||
matchBlock *ast.SSHMatchBlock,
|
||||
) []common.LSPError {
|
||||
errs := make([]common.LSPError, 0)
|
||||
@ -117,13 +116,14 @@ func checkMatchBlock(
|
||||
for it.Next() {
|
||||
option := it.Value().(*ast.SSHOption)
|
||||
|
||||
errs = append(errs, checkOption(option, matchBlock)...)
|
||||
errs = append(errs, checkOption(d, option, matchBlock)...)
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func checkHostBlock(
|
||||
d *sshconfig.SSHDocument,
|
||||
hostBlock *ast.SSHHostBlock,
|
||||
) []common.LSPError {
|
||||
errs := make([]common.LSPError, 0)
|
||||
@ -132,7 +132,7 @@ func checkHostBlock(
|
||||
for it.Next() {
|
||||
option := it.Value().(*ast.SSHOption)
|
||||
|
||||
errs = append(errs, checkOption(option, hostBlock)...)
|
||||
errs = append(errs, checkOption(d, option, hostBlock)...)
|
||||
}
|
||||
|
||||
return errs
|
||||
|
@ -60,3 +60,36 @@ func TestIncompleteQuotesExample(
|
||||
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) == '' ]]"
|
||||
HostName laptop.sdn
|
||||
|
||||
|
||||
`)
|
||||
|
||||
p := NewSSHConfig()
|
||||
@ -51,4 +53,9 @@ Match originalhost laptop exec "[[ $(/usr/bin/dig +short laptop.lan) == '' ]]"
|
||||
if !(thirdBlock.GetLocation().Start.Line == 5) {
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
it := c.Options.Iterator()
|
||||
for it.Next() {
|
||||
it.End()
|
||||
|
||||
for it.Prev() {
|
||||
entry := it.Value().(SSHEntry)
|
||||
|
||||
if entry.GetType() == SSHTypeOption {
|
||||
@ -119,7 +129,7 @@ func (c SSHConfig) FindBlock(line uint32) SSHBlock {
|
||||
|
||||
block := entry.(SSHBlock)
|
||||
|
||||
if block.GetLocation().Start.Line <= line && block.GetLocation().End.Line >= line {
|
||||
if line >= block.GetLocation().Start.Line {
|
||||
return block
|
||||
}
|
||||
}
|
||||
|
@ -29,3 +29,10 @@ func (d SSHDocument) FindOptionsByName(
|
||||
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": {},
|
||||
}
|
||||
|
||||
var GlobalDisallowedOptions = map[string]struct{}{}
|
||||
|
||||
|
@ -22,7 +22,7 @@ func GetRootCompletions(
|
||||
|
||||
for key, option := range fields.Options {
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -30,6 +30,10 @@ func GetRootCompletions(
|
||||
continue
|
||||
}
|
||||
|
||||
if parentBlock == nil && utils.KeyExists(fields.GlobalDisallowedOptions, key) {
|
||||
continue
|
||||
}
|
||||
|
||||
availableOptions[key] = option
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user