mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +02:00
commit
0c520addf5
5
common-documentation/rfc-5322.go
Normal file
5
common-documentation/rfc-5322.go
Normal file
@ -0,0 +1,5 @@
|
||||
package commondocumentation
|
||||
|
||||
import "regexp"
|
||||
|
||||
var EmailRegex = regexp.MustCompile(`([!#-'*+/-9=?A-Z^-~-]+(\.[!#-'*+/-9=?A-Z^-~-]+)*|"([]!#-[^-~ \t]|(\\[\t -~]))+")@([!#-'*+/-9=?A-Z^-~-]+(\.[!#-'*+/-9=?A-Z^-~-]+)*|\[[\t -Z^-~]*])`)
|
@ -24,20 +24,3 @@ func SendDiagnostics(context *glsp.Context, uri protocol.DocumentUri, diagnostic
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func DiagnoseOption(
|
||||
context *glsp.Context,
|
||||
uri protocol.DocumentUri,
|
||||
parser *SimpleConfigParser,
|
||||
optionName string,
|
||||
checkerFunc func(string, SimpleConfigPosition) []protocol.Diagnostic,
|
||||
) []protocol.Diagnostic {
|
||||
option, err := parser.GetOption(optionName)
|
||||
|
||||
if err != nil {
|
||||
// Nothing to diagnose
|
||||
return nil
|
||||
}
|
||||
|
||||
return checkerFunc(option.Value, option.Position)
|
||||
}
|
||||
|
@ -16,6 +16,13 @@ func (l LSPError) ToDiagnostic() protocol.Diagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
func (l LSPError) ShiftCharacter(offset uint32) LSPError {
|
||||
return LSPError{
|
||||
Range: l.Range.ShiftHorizontal(offset),
|
||||
Err: l.Err,
|
||||
}
|
||||
}
|
||||
|
||||
type SyntaxError struct {
|
||||
Message string
|
||||
}
|
||||
|
94
common/fetchers.go
Normal file
94
common/fetchers.go
Normal file
@ -0,0 +1,94 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PasswdInfo struct {
|
||||
Name string
|
||||
UID string
|
||||
GID string
|
||||
HomePath string
|
||||
Line uint32
|
||||
}
|
||||
|
||||
var _cachedPasswdInfo []PasswdInfo
|
||||
|
||||
func FetchPasswdInfo() ([]PasswdInfo, error) {
|
||||
if len(_cachedPasswdInfo) > 0 {
|
||||
return _cachedPasswdInfo, nil
|
||||
}
|
||||
|
||||
readBytes, err := os.ReadFile("/etc/passwd")
|
||||
|
||||
if err != nil {
|
||||
return []PasswdInfo{}, err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(readBytes), "\n")
|
||||
infos := make([]PasswdInfo, 0)
|
||||
|
||||
for lineNumber, line := range lines {
|
||||
splitted := strings.Split(line, ":")
|
||||
|
||||
if len(splitted) < 6 {
|
||||
continue
|
||||
}
|
||||
|
||||
info := PasswdInfo{
|
||||
Name: splitted[0],
|
||||
UID: splitted[2],
|
||||
GID: splitted[3],
|
||||
HomePath: splitted[5],
|
||||
Line: uint32(lineNumber),
|
||||
}
|
||||
|
||||
infos = append(infos, info)
|
||||
}
|
||||
|
||||
_cachedPasswdInfo = infos
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
type GroupInfo struct {
|
||||
Name string
|
||||
GID string
|
||||
}
|
||||
|
||||
var _cachedGroupInfo []GroupInfo
|
||||
|
||||
func FetchGroupInfo() ([]GroupInfo, error) {
|
||||
if len(_cachedGroupInfo) > 0 {
|
||||
return _cachedGroupInfo, nil
|
||||
}
|
||||
|
||||
readBytes, err := os.ReadFile("/etc/group")
|
||||
|
||||
if err != nil {
|
||||
return []GroupInfo{}, err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(readBytes), "\n")
|
||||
infos := make([]GroupInfo, 0)
|
||||
|
||||
for _, line := range lines {
|
||||
splitted := strings.Split(line, ":")
|
||||
|
||||
if len(splitted) < 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
info := GroupInfo{
|
||||
Name: splitted[0],
|
||||
GID: splitted[2],
|
||||
}
|
||||
|
||||
infos = append(infos, info)
|
||||
}
|
||||
|
||||
_cachedGroupInfo = infos
|
||||
|
||||
return infos, nil
|
||||
}
|
@ -1 +0,0 @@
|
||||
package common
|
@ -1,6 +1,11 @@
|
||||
package common
|
||||
|
||||
import protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
type Location struct {
|
||||
Line uint32
|
||||
@ -12,6 +17,38 @@ type LocationRange struct {
|
||||
End Location
|
||||
}
|
||||
|
||||
func (l LocationRange) ShiftHorizontal(offset uint32) LocationRange {
|
||||
return LocationRange{
|
||||
Start: Location{
|
||||
Line: l.Start.Line,
|
||||
Character: l.Start.Character + offset,
|
||||
},
|
||||
End: Location{
|
||||
Line: l.End.Line,
|
||||
Character: l.End.Character + offset,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (l LocationRange) String() string {
|
||||
if l.Start.Line == l.End.Line {
|
||||
return fmt.Sprintf("%d:%d-%d", l.Start.Line, l.Start.Character, l.End.Character)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%d:%d-%d:%d", l.Start.Line, l.Start.Character, l.End.Line, l.End.Character)
|
||||
}
|
||||
|
||||
var GlobalLocationRange = LocationRange{
|
||||
Start: Location{
|
||||
Line: 0,
|
||||
Character: 0,
|
||||
},
|
||||
End: Location{
|
||||
Line: 0,
|
||||
Character: 0,
|
||||
},
|
||||
}
|
||||
|
||||
func (l LocationRange) ToLSPRange() protocol.Range {
|
||||
return protocol.Range{
|
||||
Start: protocol.Position{
|
||||
@ -63,3 +100,22 @@ func CreateSingleCharRange(line uint32, character uint32) LocationRange {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CharacterRangeFromCtx(
|
||||
ctx antlr.BaseParserRuleContext,
|
||||
) LocationRange {
|
||||
line := uint32(ctx.GetStart().GetLine())
|
||||
start := uint32(ctx.GetStart().GetStart())
|
||||
end := uint32(ctx.GetStop().GetStop())
|
||||
|
||||
return LocationRange{
|
||||
Start: Location{
|
||||
Line: line,
|
||||
Character: start,
|
||||
},
|
||||
End: Location{
|
||||
Line: line,
|
||||
Character: end + 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
147
common/parser.go
147
common/parser.go
@ -1,17 +1,9 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
docvalues "config-lsp/doc-values"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Parser interface {
|
||||
ParseFromContent(content string) []ParseError
|
||||
AnalyzeValues() []protocol.Diagnostic
|
||||
}
|
||||
|
||||
type ParseError struct {
|
||||
Line uint32
|
||||
Err error
|
||||
@ -37,142 +29,3 @@ func (e ParseError) ToDiagnostic() protocol.Diagnostic {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type SimpleConfigPosition struct {
|
||||
Line uint32
|
||||
}
|
||||
|
||||
type SimpleConfigLine struct {
|
||||
Value string
|
||||
Separator string
|
||||
Position SimpleConfigPosition
|
||||
}
|
||||
|
||||
// Get the character positions of [Option End, Separator End, Value End]
|
||||
func (l SimpleConfigLine) GetCharacterPositions(optionName string) [3]int {
|
||||
return [3]int{len(optionName), len(optionName + l.Separator), len(optionName + l.Separator + l.Value)}
|
||||
}
|
||||
|
||||
type SimpleConfigOptions struct {
|
||||
Separator regexp.Regexp
|
||||
IgnorePattern regexp.Regexp
|
||||
// This is the separator that will be used when adding a new line
|
||||
IdealSeparator string
|
||||
AvailableOptions *map[string]Option
|
||||
}
|
||||
|
||||
type SimpleConfigParser struct {
|
||||
Lines map[string]SimpleConfigLine
|
||||
Options SimpleConfigOptions
|
||||
}
|
||||
|
||||
func (p *SimpleConfigParser) AddLine(line string, lineNumber uint32) (string, error) {
|
||||
var option string
|
||||
var separator string
|
||||
var value string
|
||||
|
||||
re := p.Options.Separator
|
||||
matches := re.FindStringSubmatch(line)
|
||||
|
||||
if len(matches) == 0 {
|
||||
return "", docvalues.MalformedLineError{}
|
||||
}
|
||||
|
||||
optionIndex := re.SubexpIndex("OptionName")
|
||||
if optionIndex == -1 {
|
||||
return "", docvalues.MalformedLineError{}
|
||||
}
|
||||
option = matches[optionIndex]
|
||||
|
||||
if _, exists := (*p.Options.AvailableOptions)[option]; !exists {
|
||||
return option, docvalues.OptionUnknownError{}
|
||||
}
|
||||
|
||||
separatorIndex := re.SubexpIndex("Separator")
|
||||
if separatorIndex == -1 {
|
||||
return option, docvalues.MalformedLineError{}
|
||||
}
|
||||
separator = matches[separatorIndex]
|
||||
|
||||
valueIndex := re.SubexpIndex("Value")
|
||||
if valueIndex == -1 {
|
||||
return option, docvalues.MalformedLineError{}
|
||||
}
|
||||
value = matches[valueIndex]
|
||||
|
||||
if _, exists := p.Lines[option]; exists {
|
||||
return option, docvalues.OptionAlreadyExistsError{
|
||||
AlreadyLine: p.Lines[option].Position.Line,
|
||||
}
|
||||
}
|
||||
|
||||
p.Lines[option] = SimpleConfigLine{
|
||||
Value: value,
|
||||
Separator: separator,
|
||||
Position: SimpleConfigPosition{
|
||||
Line: lineNumber,
|
||||
},
|
||||
}
|
||||
|
||||
return option, nil
|
||||
|
||||
}
|
||||
|
||||
func (p *SimpleConfigParser) ReplaceOption(option string, value string) {
|
||||
p.Lines[option] = SimpleConfigLine{
|
||||
Value: value,
|
||||
Position: SimpleConfigPosition{
|
||||
Line: p.Lines[option].Position.Line,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *SimpleConfigParser) RemoveOption(option string) {
|
||||
delete(p.Lines, option)
|
||||
}
|
||||
|
||||
func (p *SimpleConfigParser) GetOption(option string) (SimpleConfigLine, error) {
|
||||
if _, exists := p.Lines[option]; exists {
|
||||
return p.Lines[option], nil
|
||||
}
|
||||
|
||||
return SimpleConfigLine{}, docvalues.OptionUnknownError{}
|
||||
}
|
||||
|
||||
func (p *SimpleConfigParser) ParseFromFile(content string) []docvalues.OptionError {
|
||||
lines := strings.Split(content, "\n")
|
||||
errors := make([]docvalues.OptionError, 0)
|
||||
|
||||
for index, line := range lines {
|
||||
if p.Options.IgnorePattern.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
option, err := p.AddLine(line, uint32(index))
|
||||
|
||||
if err != nil {
|
||||
errors = append(errors, docvalues.OptionError{
|
||||
Line: uint32(index),
|
||||
ProvidedOption: option,
|
||||
DocError: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func (p *SimpleConfigParser) Clear() {
|
||||
clear(p.Lines)
|
||||
}
|
||||
|
||||
// TODO: Use better approach: Store an extra array of lines in order; with references to the SimpleConfigLine
|
||||
func (p *SimpleConfigParser) FindByLineNumber(lineNumber uint32) (string, SimpleConfigLine, error) {
|
||||
for option, line := range p.Lines {
|
||||
if line.Position.Line == lineNumber {
|
||||
return option, line, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", SimpleConfigLine{Value: "", Position: SimpleConfigPosition{Line: 0}}, docvalues.LineNotFoundError{}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package docvalues
|
||||
|
||||
import (
|
||||
"config-lsp/utils"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package docvalues
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"fmt"
|
||||
"unicode/utf8"
|
||||
|
||||
@ -95,3 +96,22 @@ type LineNotFoundError struct{}
|
||||
func (e LineNotFoundError) Error() string {
|
||||
return "Line not found"
|
||||
}
|
||||
|
||||
func LSPErrorFromInvalidValue(
|
||||
line uint32,
|
||||
invaludValue InvalidValue,
|
||||
) common.LSPError {
|
||||
return common.LSPError{
|
||||
Range: common.LocationRange{
|
||||
Start: common.Location{
|
||||
Line: line,
|
||||
Character: invaludValue.Start,
|
||||
},
|
||||
End: common.Location{
|
||||
Line: line,
|
||||
Character: invaludValue.End,
|
||||
},
|
||||
},
|
||||
Err: invaludValue.Err,
|
||||
}
|
||||
}
|
||||
|
@ -1,63 +1,17 @@
|
||||
package docvalues
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/utils"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type passwdInfo struct {
|
||||
Name string
|
||||
UID string
|
||||
GID string
|
||||
HomePath string
|
||||
}
|
||||
|
||||
var _cachedPasswdInfo []passwdInfo
|
||||
|
||||
func fetchPasswdInfo() ([]passwdInfo, error) {
|
||||
if len(_cachedPasswdInfo) > 0 {
|
||||
return _cachedPasswdInfo, nil
|
||||
}
|
||||
|
||||
readBytes, err := os.ReadFile("/etc/passwd")
|
||||
|
||||
if err != nil {
|
||||
return []passwdInfo{}, err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(readBytes), "\n")
|
||||
infos := make([]passwdInfo, 0)
|
||||
|
||||
for _, line := range lines {
|
||||
splitted := strings.Split(line, ":")
|
||||
|
||||
if len(splitted) < 6 {
|
||||
continue
|
||||
}
|
||||
|
||||
info := passwdInfo{
|
||||
Name: splitted[0],
|
||||
UID: splitted[2],
|
||||
GID: splitted[3],
|
||||
HomePath: splitted[5],
|
||||
}
|
||||
|
||||
infos = append(infos, info)
|
||||
}
|
||||
|
||||
_cachedPasswdInfo = infos
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
// UserValue returns a Value that fetches user names from /etc/passwd
|
||||
// if `separatorForMultiple` is not empty, it will return an ArrayValue
|
||||
func UserValue(separatorForMultiple string, enforceValues bool) Value {
|
||||
return CustomValue{
|
||||
FetchValue: func(context CustomValueContext) Value {
|
||||
infos, err := fetchPasswdInfo()
|
||||
infos, err := common.FetchPasswdInfo()
|
||||
|
||||
if err != nil {
|
||||
return StringValue{}
|
||||
@ -65,7 +19,7 @@ func UserValue(separatorForMultiple string, enforceValues bool) Value {
|
||||
|
||||
enumValues := EnumValue{
|
||||
EnforceValues: enforceValues,
|
||||
Values: utils.Map(infos, func(info passwdInfo) EnumString {
|
||||
Values: utils.Map(infos, func(info common.PasswdInfo) EnumString {
|
||||
return CreateEnumString(info.Name)
|
||||
}),
|
||||
}
|
||||
@ -83,51 +37,10 @@ func UserValue(separatorForMultiple string, enforceValues bool) Value {
|
||||
}
|
||||
}
|
||||
|
||||
type groupInfo struct {
|
||||
Name string
|
||||
GID string
|
||||
}
|
||||
|
||||
var _cachedGroupInfo []groupInfo
|
||||
|
||||
func fetchGroupInfo() ([]groupInfo, error) {
|
||||
if len(_cachedGroupInfo) > 0 {
|
||||
return _cachedGroupInfo, nil
|
||||
}
|
||||
|
||||
readBytes, err := os.ReadFile("/etc/group")
|
||||
|
||||
if err != nil {
|
||||
return []groupInfo{}, err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(readBytes), "\n")
|
||||
infos := make([]groupInfo, 0)
|
||||
|
||||
for _, line := range lines {
|
||||
splitted := strings.Split(line, ":")
|
||||
|
||||
if len(splitted) < 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
info := groupInfo{
|
||||
Name: splitted[0],
|
||||
GID: splitted[2],
|
||||
}
|
||||
|
||||
infos = append(infos, info)
|
||||
}
|
||||
|
||||
_cachedGroupInfo = infos
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func GroupValue(separatorForMultiple string, enforceValues bool) Value {
|
||||
return CustomValue{
|
||||
FetchValue: func(context CustomValueContext) Value {
|
||||
infos, err := fetchGroupInfo()
|
||||
infos, err := common.FetchGroupInfo()
|
||||
|
||||
if err != nil {
|
||||
return StringValue{}
|
||||
@ -135,7 +48,7 @@ func GroupValue(separatorForMultiple string, enforceValues bool) Value {
|
||||
|
||||
enumValues := EnumValue{
|
||||
EnforceValues: enforceValues,
|
||||
Values: utils.Map(infos, func(info groupInfo) EnumString {
|
||||
Values: utils.Map(infos, func(info common.GroupInfo) EnumString {
|
||||
return CreateEnumString(info.Name)
|
||||
}),
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package docvalues
|
||||
|
||||
import (
|
||||
"config-lsp/utils"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package docvalues
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/utils"
|
||||
"fmt"
|
||||
"strconv"
|
||||
@ -54,7 +55,7 @@ func (v GIDValue) CheckIsValid(value string) []*InvalidValue {
|
||||
}
|
||||
|
||||
if v.EnforceUsingExisting {
|
||||
infos, err := fetchPasswdInfo()
|
||||
infos, err := common.FetchPasswdInfo()
|
||||
|
||||
if err != nil {
|
||||
return []*InvalidValue{}
|
||||
@ -90,7 +91,7 @@ var defaultGIDsExplanation = []EnumString{
|
||||
}
|
||||
|
||||
func (v GIDValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem {
|
||||
infos, err := fetchGroupInfo()
|
||||
infos, err := common.FetchGroupInfo()
|
||||
|
||||
if err != nil {
|
||||
return utils.Map(defaultUIDsExplanation, func(enum EnumString) protocol.CompletionItem {
|
||||
@ -134,7 +135,7 @@ func (v GIDValue) FetchHoverInfo(line string, cursor uint32) []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
infos, err := fetchGroupInfo()
|
||||
infos, err := common.FetchGroupInfo()
|
||||
|
||||
if err != nil {
|
||||
return []string{}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package docvalues
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/utils"
|
||||
"fmt"
|
||||
"strconv"
|
||||
@ -54,7 +55,7 @@ func (v UIDValue) CheckIsValid(value string) []*InvalidValue {
|
||||
}
|
||||
|
||||
if v.EnforceUsingExisting {
|
||||
infos, err := fetchPasswdInfo()
|
||||
infos, err := common.FetchPasswdInfo()
|
||||
|
||||
if err != nil {
|
||||
return []*InvalidValue{}
|
||||
@ -90,7 +91,7 @@ var defaultUIDsExplanation = []EnumString{
|
||||
}
|
||||
|
||||
func (v UIDValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem {
|
||||
infos, err := fetchPasswdInfo()
|
||||
infos, err := common.FetchPasswdInfo()
|
||||
|
||||
if err != nil {
|
||||
return utils.Map(defaultUIDsExplanation, func(enum EnumString) protocol.CompletionItem {
|
||||
@ -135,7 +136,7 @@ func (v UIDValue) FetchHoverInfo(line string, cursor uint32) []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
infos, err := fetchPasswdInfo()
|
||||
infos, err := common.FetchPasswdInfo()
|
||||
|
||||
if err != nil {
|
||||
return []string{}
|
||||
|
@ -42,7 +42,10 @@
|
||||
};
|
||||
};
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = inputs;
|
||||
buildInputs = inputs ++ (with pkgs; [
|
||||
mailutils
|
||||
swaks
|
||||
]);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
1
go.mod
1
go.mod
@ -10,6 +10,7 @@ require (
|
||||
require (
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/iancoleman/strcase v0.3.0 // indirect
|
||||
github.com/k0kubun/pp v3.0.1+incompatible // indirect
|
||||
|
2
go.sum
2
go.sum
@ -2,6 +2,8 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
|
106
handlers/aliases/Aliases.g4
Normal file
106
handlers/aliases/Aliases.g4
Normal file
@ -0,0 +1,106 @@
|
||||
grammar Aliases;
|
||||
|
||||
lineStatement
|
||||
: entry EOF
|
||||
;
|
||||
|
||||
entry
|
||||
: SEPARATOR? key SEPARATOR? separator SEPARATOR? values
|
||||
;
|
||||
|
||||
separator
|
||||
: COLON
|
||||
;
|
||||
|
||||
key
|
||||
: STRING
|
||||
;
|
||||
|
||||
// // Values // //
|
||||
values
|
||||
: (value COMMA SEPARATOR)* value
|
||||
;
|
||||
|
||||
value
|
||||
: (user | file | command | include | email | error)
|
||||
;
|
||||
|
||||
user
|
||||
: STRING
|
||||
;
|
||||
|
||||
file
|
||||
: SLASH (STRING SLASH)* STRING?
|
||||
;
|
||||
|
||||
command
|
||||
: VERTLINE STRING?
|
||||
;
|
||||
|
||||
include
|
||||
: COLON INCLUDE COLON file?
|
||||
;
|
||||
|
||||
comment
|
||||
: NUMBER_SIGN (SEPARATOR? STRING)+ SEPARATOR?
|
||||
;
|
||||
|
||||
email
|
||||
: STRING AT STRING
|
||||
;
|
||||
|
||||
error
|
||||
: ERROR COLON errorCode? SEPARATOR? errorMessage?
|
||||
;
|
||||
|
||||
errorCode
|
||||
: DIGITS
|
||||
;
|
||||
|
||||
errorMessage
|
||||
: (STRING SEPARATOR)* STRING
|
||||
;
|
||||
|
||||
DIGITS
|
||||
: [0-9]+
|
||||
;
|
||||
|
||||
ERROR
|
||||
: 'e' 'r' 'r' 'o' 'r'
|
||||
;
|
||||
|
||||
SEPARATOR
|
||||
: [ \t]+
|
||||
;
|
||||
|
||||
AT
|
||||
: '@'
|
||||
;
|
||||
|
||||
INCLUDE
|
||||
: 'i' 'n' 'c' 'l' 'u' 'd' 'e'
|
||||
;
|
||||
|
||||
VERTLINE
|
||||
: '|'
|
||||
;
|
||||
|
||||
COLON
|
||||
: ':'
|
||||
;
|
||||
|
||||
COMMA
|
||||
: ','
|
||||
;
|
||||
|
||||
NUMBER_SIGN
|
||||
: '#'
|
||||
;
|
||||
|
||||
SLASH
|
||||
: '/'
|
||||
;
|
||||
|
||||
STRING
|
||||
: ~(' ' | '\t' | '\n' | '\r' | ':' | ',' | '#' | '@' | '|' | '/')+
|
||||
;
|
37
handlers/aliases/analyzer/analyzer.go
Normal file
37
handlers/aliases/analyzer/analyzer.go
Normal file
@ -0,0 +1,37 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/utils"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func Analyze(
|
||||
d *aliases.AliasesDocument,
|
||||
) []protocol.Diagnostic {
|
||||
// Double keys must be checked first so
|
||||
// that the index is populated for the
|
||||
// other checks
|
||||
errors := analyzeDoubleKeys(d)
|
||||
errors = append(errors, analyzeValuesAreValid(d)...)
|
||||
|
||||
if len(errors) > 0 {
|
||||
return utils.Map(
|
||||
errors,
|
||||
func(err common.LSPError) protocol.Diagnostic {
|
||||
return err.ToDiagnostic()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
errors = append(errors, analyzeContainsRequiredKeys(*d)...)
|
||||
errors = append(errors, analyzeContainsNoDoubleValues(*d.Parser)...)
|
||||
|
||||
return utils.Map(
|
||||
errors,
|
||||
func(err common.LSPError) protocol.Diagnostic {
|
||||
return err.ToDiagnostic()
|
||||
},
|
||||
)
|
||||
}
|
21
handlers/aliases/analyzer/double_keys.go
Normal file
21
handlers/aliases/analyzer/double_keys.go
Normal file
@ -0,0 +1,21 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/handlers/aliases/indexes"
|
||||
)
|
||||
|
||||
func analyzeDoubleKeys(
|
||||
d *aliases.AliasesDocument,
|
||||
) []common.LSPError {
|
||||
indexes, errors := indexes.CreateIndexes(*d.Parser)
|
||||
|
||||
d.Indexes = &indexes
|
||||
|
||||
if len(errors) > 0 {
|
||||
return errors
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
73
handlers/aliases/analyzer/double_keys_test.go
Normal file
73
handlers/aliases/analyzer/double_keys_test.go
Normal file
@ -0,0 +1,73 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWorks(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
support: michael
|
||||
marketing: john
|
||||
support: jane
|
||||
`)
|
||||
p := ast.NewAliasesParser()
|
||||
errors := p.Parse(input)
|
||||
|
||||
d := aliases.AliasesDocument{
|
||||
Parser: &p,
|
||||
}
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Errorf("Expected no errors, got %v", errors)
|
||||
}
|
||||
|
||||
errors = analyzeDoubleKeys(&d)
|
||||
|
||||
if !(len(errors) == 1) {
|
||||
t.Errorf("Expected 1 error, got %v", errors)
|
||||
}
|
||||
|
||||
if d.Indexes == nil {
|
||||
t.Errorf("Expected indexes to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidWorks(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
support: michael
|
||||
marketing: john
|
||||
supportgroup: jane
|
||||
suppor: jane
|
||||
`)
|
||||
p := ast.NewAliasesParser()
|
||||
errors := p.Parse(input)
|
||||
|
||||
d := aliases.AliasesDocument{
|
||||
Parser: &p,
|
||||
}
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Errorf("Expected no errors, got %v", errors)
|
||||
}
|
||||
|
||||
errors = analyzeDoubleKeys(&d)
|
||||
|
||||
if !(len(errors) == 0) {
|
||||
t.Errorf("Expected 0 errors, got %v", errors)
|
||||
}
|
||||
|
||||
if d.Indexes == nil {
|
||||
t.Errorf("Expected indexes to be set")
|
||||
}
|
||||
|
||||
if d.Indexes.Keys["support"] == nil {
|
||||
t.Errorf("Expected support to be in indexes")
|
||||
}
|
||||
}
|
193
handlers/aliases/analyzer/double_values.go
Normal file
193
handlers/aliases/analyzer/double_values.go
Normal file
@ -0,0 +1,193 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/indexes"
|
||||
"config-lsp/utils"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var valueHandlerMap = map[string]func(
|
||||
rawValue []ast.AliasValueInterface,
|
||||
) []common.LSPError{
|
||||
"AliasValueUser": analyzeValueUser,
|
||||
"AliasValueEmail": analyzeValueEmail,
|
||||
"AliasValueCommand": analyzeValueCommand,
|
||||
"AliasValueFile": analyzeValueFile,
|
||||
"AliasValueInclude": analyzeValueInclude,
|
||||
"AliasValueError": analyzeValueError,
|
||||
}
|
||||
|
||||
func analyzeContainsNoDoubleValues(
|
||||
p ast.AliasesParser,
|
||||
) []common.LSPError {
|
||||
errors := make([]common.LSPError, 0)
|
||||
|
||||
it := p.Aliases.Iterator()
|
||||
|
||||
for it.Next() {
|
||||
entry := it.Value().(*ast.AliasEntry)
|
||||
|
||||
valuesPerType := utils.Group(
|
||||
entry.Values.Values,
|
||||
func(entry ast.AliasValueInterface) string {
|
||||
return entry.GetStructName()
|
||||
},
|
||||
)
|
||||
|
||||
for valueType, values := range valuesPerType {
|
||||
handler := valueHandlerMap[valueType]
|
||||
|
||||
newErrors := handler(values)
|
||||
errors = append(errors, newErrors...)
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func analyzeValueUser(
|
||||
rawValues []ast.AliasValueInterface,
|
||||
) []common.LSPError {
|
||||
users := make(map[string]struct{})
|
||||
errs := make([]common.LSPError, 0)
|
||||
|
||||
// Simple double value check
|
||||
for _, rawValue := range rawValues {
|
||||
value := rawValue.(ast.AliasValueUser)
|
||||
key := indexes.NormalizeKey(value.Value)
|
||||
|
||||
if _, found := users[key]; found {
|
||||
errs = append(errs, common.LSPError{
|
||||
Range: value.Location,
|
||||
Err: errors.New(fmt.Sprintf("User '%s' is defined multiple times", key)),
|
||||
})
|
||||
} else {
|
||||
users[key] = struct{}{}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func analyzeValueEmail(
|
||||
rawValues []ast.AliasValueInterface,
|
||||
) []common.LSPError {
|
||||
emails := make(map[string]struct{})
|
||||
errs := make([]common.LSPError, 0)
|
||||
|
||||
for _, rawValue := range rawValues {
|
||||
value := rawValue.(ast.AliasValueEmail)
|
||||
|
||||
// Simple double value check
|
||||
if _, found := emails[value.Value]; found {
|
||||
errs = append(errs, common.LSPError{
|
||||
Range: value.Location,
|
||||
Err: errors.New(fmt.Sprintf("Email '%s' is defined multiple times", value.Value)),
|
||||
})
|
||||
} else {
|
||||
emails[value.Value] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func analyzeValueCommand(
|
||||
rawValues []ast.AliasValueInterface,
|
||||
) []common.LSPError {
|
||||
commands := make(map[string]struct{})
|
||||
errs := make([]common.LSPError, 0)
|
||||
|
||||
for _, rawValue := range rawValues {
|
||||
value := rawValue.(ast.AliasValueCommand)
|
||||
command := value.Command
|
||||
|
||||
// Simple double value check
|
||||
if _, found := commands[command]; found {
|
||||
errs = append(errs, common.LSPError{
|
||||
Range: value.Location,
|
||||
Err: errors.New(fmt.Sprintf("Command '%s' is defined multiple times", command)),
|
||||
})
|
||||
} else {
|
||||
commands[command] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func analyzeValueFile(
|
||||
rawValues []ast.AliasValueInterface,
|
||||
) []common.LSPError {
|
||||
files := make(map[string]struct{})
|
||||
errs := make([]common.LSPError, 0)
|
||||
|
||||
for _, rawValue := range rawValues {
|
||||
value := rawValue.(ast.AliasValueFile)
|
||||
path := string(value.Path)
|
||||
|
||||
// Simple double value check
|
||||
if _, found := files[path]; found {
|
||||
errs = append(errs, common.LSPError{
|
||||
Range: value.Location,
|
||||
Err: errors.New(fmt.Sprintf("File '%s' is defined multiple times", path)),
|
||||
})
|
||||
} else {
|
||||
files[path] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func analyzeValueInclude(
|
||||
rawValues []ast.AliasValueInterface,
|
||||
) []common.LSPError {
|
||||
files := make(map[string]struct{})
|
||||
errs := make([]common.LSPError, 0)
|
||||
|
||||
for _, rawValue := range rawValues {
|
||||
value := rawValue.(ast.AliasValueInclude)
|
||||
path := string(value.Path.Path)
|
||||
|
||||
// Simple double value check
|
||||
if _, found := files[path]; found {
|
||||
errs = append(errs, common.LSPError{
|
||||
Range: value.Location,
|
||||
Err: errors.New(fmt.Sprintf("Inclusion '%s' is included multiple times", path)),
|
||||
})
|
||||
} else {
|
||||
files[path] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func analyzeValueError(
|
||||
rawValues []ast.AliasValueInterface,
|
||||
) []common.LSPError {
|
||||
codes := make(map[uint16]struct{})
|
||||
errs := make([]common.LSPError, 0)
|
||||
|
||||
for _, rawValue := range rawValues {
|
||||
value := rawValue.(ast.AliasValueError)
|
||||
code := value.Code.ErrorCodeAsInt()
|
||||
|
||||
// Simple double value check
|
||||
if _, found := codes[code]; found {
|
||||
errs = append(errs, common.LSPError{
|
||||
Range: value.Location,
|
||||
Err: errors.New(fmt.Sprintf("Error code '%d' is defined multiple times", code)),
|
||||
})
|
||||
} else {
|
||||
codes[code] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
119
handlers/aliases/analyzer/double_values_test.go
Normal file
119
handlers/aliases/analyzer/double_values_test.go
Normal file
@ -0,0 +1,119 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestContainsDoubleUsers(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
alice: root
|
||||
master: alice, alice
|
||||
`)
|
||||
p := ast.NewAliasesParser()
|
||||
errors := p.Parse(input)
|
||||
|
||||
// d := aliases.AliasesDocument{
|
||||
// Parser: &p,
|
||||
// }
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("Expected no errors, got %v", errors)
|
||||
}
|
||||
|
||||
errors = analyzeContainsNoDoubleValues(p)
|
||||
|
||||
if !(len(errors) == 1) {
|
||||
t.Errorf("Expected errors, got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsDoubleEmails(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
alice: root@localhost, some, noise, here, root@localhost
|
||||
`)
|
||||
p := ast.NewAliasesParser()
|
||||
errors := p.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("Expected no errors, got %v", errors)
|
||||
}
|
||||
|
||||
errors = analyzeContainsNoDoubleValues(p)
|
||||
|
||||
if !(len(errors) == 1) {
|
||||
t.Errorf("Expected errors, got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsDoubleCommands(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
alice: |echo, |test, |echo
|
||||
`)
|
||||
p := ast.NewAliasesParser()
|
||||
errors := p.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("Expected no errors, got %v", errors)
|
||||
}
|
||||
|
||||
errors = analyzeContainsNoDoubleValues(p)
|
||||
|
||||
if !(len(errors) == 1) {
|
||||
t.Errorf("Expected errors, got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsDoubleErrors(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
alice: error:450 Nonono, error:450 Some other message
|
||||
root: error:450 Nonon, error:451 This is not okay
|
||||
`)
|
||||
p := ast.NewAliasesParser()
|
||||
errors := p.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("Expected no errors, got %v", errors)
|
||||
}
|
||||
|
||||
errors = analyzeContainsNoDoubleValues(p)
|
||||
|
||||
if !(len(errors) == 1) {
|
||||
t.Errorf("Expected no errors, got %v", errors)
|
||||
}
|
||||
|
||||
if !(errors[0].Range.Start.Line == 0 && errors[0].Range.End.Line == 0) {
|
||||
t.Errorf("Expected error to be on line 0, got %v-%v", errors[0].Range.Start.Line, errors[0].Range.End.Line)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComplexExampleContainsNoDoubleValues(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
alice: root@localhost, user@localhost
|
||||
master: alice, root
|
||||
noreply: error:450 Nonono
|
||||
`)
|
||||
p := ast.NewAliasesParser()
|
||||
errors := p.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("Expected no errors, got %v", errors)
|
||||
}
|
||||
|
||||
errors = analyzeContainsNoDoubleValues(p)
|
||||
|
||||
if !(len(errors) == 0) {
|
||||
t.Errorf("Expected no errors, got %v", errors)
|
||||
}
|
||||
}
|
33
handlers/aliases/analyzer/required_keys.go
Normal file
33
handlers/aliases/analyzer/required_keys.go
Normal file
@ -0,0 +1,33 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/handlers/aliases/indexes"
|
||||
"fmt"
|
||||
|
||||
ers "errors"
|
||||
)
|
||||
|
||||
var RequiredAliases = []string{
|
||||
indexes.NormalizeKey("mailer-daemon"),
|
||||
indexes.NormalizeKey("hostmaster"),
|
||||
indexes.NormalizeKey("postmaster"),
|
||||
}
|
||||
|
||||
func analyzeContainsRequiredKeys(
|
||||
d aliases.AliasesDocument,
|
||||
) []common.LSPError {
|
||||
errors := make([]common.LSPError, 0)
|
||||
|
||||
for _, requiredField := range RequiredAliases {
|
||||
if _, found := d.Indexes.Keys[requiredField]; !found {
|
||||
errors = append(errors, common.LSPError{
|
||||
Range: common.GlobalLocationRange,
|
||||
Err: ers.New(fmt.Sprintf("Please add the alias '%s'. It is required by the aliases file.", requiredField)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
146
handlers/aliases/analyzer/value_valid.go
Normal file
146
handlers/aliases/analyzer/value_valid.go
Normal file
@ -0,0 +1,146 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/fetchers"
|
||||
"config-lsp/handlers/aliases/indexes"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
ers "errors"
|
||||
)
|
||||
|
||||
func analyzeValuesAreValid(
|
||||
d *aliases.AliasesDocument,
|
||||
) []common.LSPError {
|
||||
errors := make([]common.LSPError, 0)
|
||||
|
||||
it := d.Parser.Aliases.Iterator()
|
||||
|
||||
for it.Next() {
|
||||
entry := it.Value().(*ast.AliasEntry)
|
||||
|
||||
if entry.Key == nil {
|
||||
errors = append(errors, common.LSPError{
|
||||
Range: entry.Location,
|
||||
Err: ers.New("An alias is required"),
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if entry.Separator == nil {
|
||||
errors = append(errors, common.LSPError{
|
||||
Range: entry.Location,
|
||||
Err: ers.New("A ':' is required as a separator"),
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if entry.Values == nil || len(entry.Values.Values) == 0 {
|
||||
errors = append(errors, common.LSPError{
|
||||
Range: entry.Location,
|
||||
Err: ers.New("A value is required"),
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
for _, value := range entry.Values.Values {
|
||||
newErrors := checkValue(d.Indexes, value)
|
||||
|
||||
errors = append(errors, newErrors...)
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func checkValue(
|
||||
i *indexes.AliasesIndexes,
|
||||
value ast.AliasValueInterface,
|
||||
) []common.LSPError {
|
||||
switch value.(type) {
|
||||
case ast.AliasValueUser:
|
||||
aliasValue := value.(ast.AliasValueUser)
|
||||
|
||||
users := fetchers.GetAvailableUserValues(i)
|
||||
|
||||
if _, found := users[aliasValue.Value]; !found {
|
||||
return []common.LSPError{{
|
||||
Range: aliasValue.Location,
|
||||
Err: ers.New(fmt.Sprintf("User '%s' not found", aliasValue.Value)),
|
||||
}}
|
||||
}
|
||||
case ast.AliasValueEmail:
|
||||
emailValue := value.(ast.AliasValueEmail)
|
||||
|
||||
if _, error := mail.ParseAddress(emailValue.Value); error != nil {
|
||||
return []common.LSPError{{
|
||||
Range: emailValue.Location,
|
||||
Err: ers.New(fmt.Sprintf("This does not seem to be a valid email: %s", error.Error())),
|
||||
}}
|
||||
}
|
||||
case ast.AliasValueFile:
|
||||
fileValue := value.(ast.AliasValueFile)
|
||||
|
||||
// I'm not sure if the path really needs to be absolute
|
||||
// The docs say:
|
||||
// "Append messages to file, specified by its absolute pathname."
|
||||
//
|
||||
if !path.IsAbs(fileValue.Value) {
|
||||
return []common.LSPError{{
|
||||
Range: fileValue.Location,
|
||||
Err: ers.New("This path must be absolute"),
|
||||
}}
|
||||
}
|
||||
case ast.AliasValueError:
|
||||
errorValue := value.(ast.AliasValueError)
|
||||
|
||||
if errorValue.Code == nil {
|
||||
return []common.LSPError{{
|
||||
Range: errorValue.Location,
|
||||
Err: ers.New("An error code in the form of 4XX or 5XX is required"),
|
||||
}}
|
||||
}
|
||||
|
||||
errorCode, err := strconv.Atoi(errorValue.Code.Value)
|
||||
|
||||
if err != nil || (errorCode < 400 || errorCode > 599) {
|
||||
return []common.LSPError{{
|
||||
Range: errorValue.Code.Location,
|
||||
Err: ers.New("This error code is invalid. It must be in the form of 4XX or 5XX"),
|
||||
}}
|
||||
}
|
||||
|
||||
if errorValue.Message == nil || errorValue.Message.Value == "" {
|
||||
return []common.LSPError{{
|
||||
Range: errorValue.Location,
|
||||
Err: ers.New("An error message is required"),
|
||||
}}
|
||||
}
|
||||
case ast.AliasValueInclude:
|
||||
incldueValue := value.(ast.AliasValueInclude)
|
||||
|
||||
if incldueValue.Path == nil {
|
||||
return []common.LSPError{{
|
||||
Range: incldueValue.Location,
|
||||
Err: ers.New("A path is required"),
|
||||
}}
|
||||
}
|
||||
|
||||
// Again, I'm not sure if the path really needs to be absolute
|
||||
if !path.IsAbs(string(incldueValue.Path.Path)) {
|
||||
return []common.LSPError{{
|
||||
Range: incldueValue.Path.Location,
|
||||
Err: ers.New("This path must be absolute"),
|
||||
}}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
41
handlers/aliases/ast/aliases.go
Normal file
41
handlers/aliases/ast/aliases.go
Normal file
@ -0,0 +1,41 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"github.com/emirpasic/gods/maps/treemap"
|
||||
)
|
||||
|
||||
// Procedure
|
||||
// Save options in fields
|
||||
// Each type, such as Include, User etc is own type
|
||||
// Each type inherits interface
|
||||
// This interface has methods such as:
|
||||
// - CheckIsUser()
|
||||
// For user, this checks if the user is listed in passwd
|
||||
// For include, this includes the file and parses it and validates it
|
||||
//
|
||||
|
||||
// Parse content manually as the /etc/aliases file is so simple
|
||||
|
||||
type AliasKey struct {
|
||||
Location common.LocationRange
|
||||
Value string
|
||||
}
|
||||
|
||||
type AliasValues struct {
|
||||
Location common.LocationRange
|
||||
Values []AliasValueInterface
|
||||
}
|
||||
|
||||
type AliasEntry struct {
|
||||
Location common.LocationRange
|
||||
Key *AliasKey
|
||||
Separator *common.LocationRange
|
||||
Values *AliasValues
|
||||
}
|
||||
|
||||
type AliasesParser struct {
|
||||
// uint32 -> *AliasEntry
|
||||
Aliases *treemap.Map
|
||||
CommentLines map[uint32]struct{}
|
||||
}
|
298
handlers/aliases/ast/listener.go
Normal file
298
handlers/aliases/ast/listener.go
Normal file
@ -0,0 +1,298 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/aliases/parser"
|
||||
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
)
|
||||
|
||||
type aliasesListenerContext struct {
|
||||
line uint32
|
||||
currentIncludeIndex *uint32
|
||||
currentErrorValueIndex *uint32
|
||||
}
|
||||
|
||||
type aliasesParserListener struct {
|
||||
*parser.BaseAliasesListener
|
||||
Parser *AliasesParser
|
||||
Errors []common.LSPError
|
||||
aliasContext aliasesListenerContext
|
||||
}
|
||||
|
||||
func (s *aliasesParserListener) EnterEntry(ctx *parser.EntryContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.aliasContext.line)
|
||||
|
||||
s.Parser.Aliases.Put(
|
||||
location.Start.Line,
|
||||
&AliasEntry{
|
||||
Location: location,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (s *aliasesParserListener) EnterSeparator(ctx *parser.SeparatorContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.aliasContext.line)
|
||||
|
||||
rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line)
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
|
||||
entry.Separator = &location
|
||||
}
|
||||
|
||||
func (s *aliasesParserListener) EnterKey(ctx *parser.KeyContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.aliasContext.line)
|
||||
|
||||
rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line)
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
|
||||
entry.Key = &AliasKey{
|
||||
Location: location,
|
||||
Value: ctx.GetText(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *aliasesParserListener) EnterValues(ctx *parser.ValuesContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.aliasContext.line)
|
||||
|
||||
rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line)
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
|
||||
entry.Values = &AliasValues{
|
||||
Location: location,
|
||||
Values: make([]AliasValueInterface, 0, 5),
|
||||
}
|
||||
}
|
||||
|
||||
// === Value === //
|
||||
|
||||
func (s *aliasesParserListener) EnterUser(ctx *parser.UserContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.aliasContext.line)
|
||||
|
||||
user := AliasValueUser{
|
||||
AliasValue: AliasValue{
|
||||
Location: location,
|
||||
Value: ctx.GetText(),
|
||||
},
|
||||
}
|
||||
|
||||
rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line)
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
|
||||
entry.Values.Values = append(entry.Values.Values, user)
|
||||
}
|
||||
|
||||
func (s *aliasesParserListener) EnterFile(ctx *parser.FileContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.aliasContext.line)
|
||||
|
||||
rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line)
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
|
||||
if s.aliasContext.currentIncludeIndex != nil {
|
||||
// This `file` is inside an `include`, so we need to set the path on the include
|
||||
values := entry.Values
|
||||
rawValue := values.Values[*s.aliasContext.currentIncludeIndex]
|
||||
|
||||
// Set the path
|
||||
include := rawValue.(AliasValueInclude)
|
||||
include.Path = &AliasValueIncludePath{
|
||||
Location: location,
|
||||
Path: path(ctx.GetText()),
|
||||
}
|
||||
values.Values[*s.aliasContext.currentIncludeIndex] = include
|
||||
|
||||
// Clean up
|
||||
s.aliasContext.currentIncludeIndex = nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Raw file, process it
|
||||
file := AliasValueFile{
|
||||
AliasValue: AliasValue{
|
||||
Location: location,
|
||||
Value: ctx.GetText(),
|
||||
},
|
||||
Path: path(ctx.GetText()),
|
||||
}
|
||||
|
||||
entry.Values.Values = append(entry.Values.Values, file)
|
||||
}
|
||||
|
||||
func (s *aliasesParserListener) EnterCommand(ctx *parser.CommandContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.aliasContext.line)
|
||||
|
||||
command := AliasValueCommand{
|
||||
AliasValue: AliasValue{
|
||||
Location: location,
|
||||
Value: ctx.GetText(),
|
||||
},
|
||||
Command: ctx.GetText()[1:],
|
||||
}
|
||||
|
||||
rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line)
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
|
||||
entry.Values.Values = append(entry.Values.Values, command)
|
||||
}
|
||||
|
||||
func (s *aliasesParserListener) EnterInclude(ctx *parser.IncludeContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.aliasContext.line)
|
||||
|
||||
include := AliasValueInclude{
|
||||
AliasValue: AliasValue{
|
||||
Location: location,
|
||||
Value: ctx.GetText(),
|
||||
},
|
||||
}
|
||||
|
||||
rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line)
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
|
||||
entry.Values.Values = append(entry.Values.Values, include)
|
||||
|
||||
index := uint32(len(entry.Values.Values) - 1)
|
||||
s.aliasContext.currentIncludeIndex = &index
|
||||
}
|
||||
|
||||
func (s *aliasesParserListener) EnterEmail(ctx *parser.EmailContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.aliasContext.line)
|
||||
|
||||
email := AliasValueEmail{
|
||||
AliasValue: AliasValue{
|
||||
Location: location,
|
||||
Value: ctx.GetText(),
|
||||
},
|
||||
}
|
||||
|
||||
rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line)
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
|
||||
entry.Values.Values = append(entry.Values.Values, email)
|
||||
}
|
||||
|
||||
func (s *aliasesParserListener) EnterError(ctx *parser.ErrorContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.aliasContext.line)
|
||||
|
||||
errorValue := AliasValueError{
|
||||
AliasValue: AliasValue{
|
||||
Location: location,
|
||||
Value: ctx.GetText(),
|
||||
},
|
||||
}
|
||||
|
||||
rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line)
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
|
||||
entry.Values.Values = append(entry.Values.Values, errorValue)
|
||||
|
||||
index := uint32(len(entry.Values.Values) - 1)
|
||||
s.aliasContext.currentErrorValueIndex = &index
|
||||
}
|
||||
|
||||
func (s *aliasesParserListener) ExitError(ctx *parser.ErrorContext) {
|
||||
s.aliasContext.currentErrorValueIndex = nil
|
||||
}
|
||||
|
||||
// EnterErrorCode is called when production errorCode is entered.
|
||||
func (s *aliasesParserListener) EnterErrorCode(ctx *parser.ErrorCodeContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.aliasContext.line)
|
||||
|
||||
rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line)
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
|
||||
values := entry.Values.Values
|
||||
|
||||
rawValue := values[*s.aliasContext.currentErrorValueIndex]
|
||||
value := rawValue.(AliasValueError)
|
||||
|
||||
value.Code = &AliasValueErrorCode{
|
||||
AliasValue: AliasValue{
|
||||
Location: location,
|
||||
Value: ctx.GetText(),
|
||||
},
|
||||
}
|
||||
|
||||
values[*s.aliasContext.currentErrorValueIndex] = value
|
||||
}
|
||||
|
||||
// EnterErrorMessage is called when production errorMessage is entered.
|
||||
func (s *aliasesParserListener) EnterErrorMessage(ctx *parser.ErrorMessageContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.aliasContext.line)
|
||||
|
||||
rawEntry, _ := s.Parser.Aliases.Get(location.Start.Line)
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
|
||||
values := entry.Values.Values
|
||||
|
||||
rawValue := values[*s.aliasContext.currentErrorValueIndex]
|
||||
value := rawValue.(AliasValueError)
|
||||
|
||||
value.Message = &AliasValueErrorMessage{
|
||||
AliasValue: AliasValue{
|
||||
Location: location,
|
||||
Value: ctx.GetText(),
|
||||
},
|
||||
}
|
||||
|
||||
values[*s.aliasContext.currentErrorValueIndex] = value
|
||||
}
|
||||
|
||||
func createListener(
|
||||
parser *AliasesParser,
|
||||
line uint32,
|
||||
) aliasesParserListener {
|
||||
return aliasesParserListener{
|
||||
Parser: parser,
|
||||
aliasContext: aliasesListenerContext{
|
||||
line: line,
|
||||
},
|
||||
Errors: make([]common.LSPError, 0),
|
||||
}
|
||||
}
|
||||
|
||||
type errorListener struct {
|
||||
*antlr.DefaultErrorListener
|
||||
Errors []common.LSPError
|
||||
context aliasesListenerContext
|
||||
}
|
||||
|
||||
func (d *errorListener) SyntaxError(
|
||||
recognizer antlr.Recognizer,
|
||||
offendingSymbol interface{},
|
||||
_ int,
|
||||
character int,
|
||||
message string,
|
||||
error antlr.RecognitionException,
|
||||
) {
|
||||
line := d.context.line
|
||||
d.Errors = append(d.Errors, common.LSPError{
|
||||
Range: common.CreateSingleCharRange(uint32(line), uint32(character)),
|
||||
Err: common.SyntaxError{
|
||||
Message: message,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func createErrorListener(
|
||||
line uint32,
|
||||
) errorListener {
|
||||
return errorListener{
|
||||
Errors: make([]common.LSPError, 0),
|
||||
context: aliasesListenerContext{
|
||||
line: line,
|
||||
},
|
||||
}
|
||||
}
|
82
handlers/aliases/ast/parser.go
Normal file
82
handlers/aliases/ast/parser.go
Normal file
@ -0,0 +1,82 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/aliases/parser"
|
||||
"config-lsp/utils"
|
||||
"regexp"
|
||||
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
"github.com/emirpasic/gods/maps/treemap"
|
||||
gods "github.com/emirpasic/gods/utils"
|
||||
)
|
||||
|
||||
func NewAliasesParser() AliasesParser {
|
||||
p := AliasesParser{}
|
||||
p.Clear()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *AliasesParser) Clear() {
|
||||
p.CommentLines = make(map[uint32]struct{})
|
||||
p.Aliases = treemap.NewWith(gods.UInt32Comparator)
|
||||
}
|
||||
|
||||
var commentPattern = regexp.MustCompile(`^\s*#.*$`)
|
||||
var emptyPattern = regexp.MustCompile(`^\s*$`)
|
||||
|
||||
func (p *AliasesParser) Parse(input string) []common.LSPError {
|
||||
errors := make([]common.LSPError, 0)
|
||||
lines := utils.SplitIntoLines(input)
|
||||
|
||||
for rawLineNumber, line := range lines {
|
||||
lineNumber := uint32(rawLineNumber)
|
||||
|
||||
if commentPattern.MatchString(line) {
|
||||
p.CommentLines[lineNumber] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
if emptyPattern.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
errors = append(
|
||||
errors,
|
||||
p.parseStatement(lineNumber, line)...,
|
||||
)
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
func (p *AliasesParser) parseStatement(
|
||||
line uint32,
|
||||
input string,
|
||||
) []common.LSPError {
|
||||
stream := antlr.NewInputStream(input)
|
||||
|
||||
lexerErrorListener := createErrorListener(line)
|
||||
lexer := parser.NewAliasesLexer(stream)
|
||||
lexer.RemoveErrorListeners()
|
||||
lexer.AddErrorListener(&lexerErrorListener)
|
||||
|
||||
tokenStream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
|
||||
|
||||
parserErrorListener := createErrorListener(line)
|
||||
antlrParser := parser.NewAliasesParser(tokenStream)
|
||||
antlrParser.RemoveErrorListeners()
|
||||
antlrParser.AddErrorListener(&parserErrorListener)
|
||||
|
||||
listener := createListener(p, line)
|
||||
antlr.ParseTreeWalkerDefault.Walk(
|
||||
&listener,
|
||||
antlrParser.LineStatement(),
|
||||
)
|
||||
|
||||
errors := lexerErrorListener.Errors
|
||||
errors = append(errors, parserErrorListener.Errors...)
|
||||
|
||||
return errors
|
||||
}
|
151
handlers/aliases/ast/parser_test.go
Normal file
151
handlers/aliases/ast/parser_test.go
Normal file
@ -0,0 +1,151 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"config-lsp/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimpleExampleWorks(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
postmaster: root
|
||||
`)
|
||||
|
||||
parser := NewAliasesParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("Expected no errors, got %v", errors)
|
||||
}
|
||||
|
||||
if !(parser.Aliases.Size() == 1) {
|
||||
t.Fatalf("Expected 1 alias, got %v", parser.Aliases.Size())
|
||||
}
|
||||
|
||||
rawEntry, _ := parser.Aliases.Get(uint32(0))
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
if !(entry.Key.Value == "postmaster") {
|
||||
t.Fatalf("Expected key to be 'postmaster', got %v", entry.Key.Value)
|
||||
}
|
||||
|
||||
userValue := entry.Values.Values[0].(AliasValueUser)
|
||||
if !(userValue.Value == "root") {
|
||||
t.Fatalf("Expected value to be 'root', got %v", userValue.Value)
|
||||
}
|
||||
|
||||
if !(userValue.Location.Start.Line == 0) {
|
||||
t.Fatalf("Expected start line to be 0, got %v", userValue.Location.Start.Line)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleValuesWorks(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
heinz: root, goli
|
||||
michel: raiks@example.com
|
||||
`)
|
||||
parser := NewAliasesParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("Expected no errors, got %v", errors)
|
||||
}
|
||||
|
||||
if !(parser.Aliases.Size() == 2) {
|
||||
t.Fatalf("Expected 2 aliases, got %v", parser.Aliases.Size())
|
||||
}
|
||||
|
||||
rawEntry, _ := parser.Aliases.Get(uint32(0))
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
if !(entry.Key.Value == "heinz") {
|
||||
t.Fatalf("Expected key to be 'heinz', got %v", entry.Key.Value)
|
||||
}
|
||||
|
||||
rawEntry, _ = parser.Aliases.Get(uint32(1))
|
||||
entry = rawEntry.(*AliasEntry)
|
||||
if !(entry.Key.Value == "michel") {
|
||||
t.Fatalf("Expected key to be 'michel', got %v", entry.Key.Value)
|
||||
}
|
||||
|
||||
emailValue := entry.Values.Values[0].(AliasValueEmail)
|
||||
|
||||
if !(emailValue.Value == "raiks@example.com") {
|
||||
t.Fatalf("Expected value to be 'raiks@example.com', got %v", emailValue.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncludeWorks(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
luke: :include:/etc/other_aliases
|
||||
`)
|
||||
parser := NewAliasesParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("Expected no errors, got %v", errors)
|
||||
}
|
||||
|
||||
if !(parser.Aliases.Size() == 1) {
|
||||
t.Fatalf("Expected 1 alias, got %v", parser.Aliases.Size())
|
||||
}
|
||||
|
||||
rawEntry, _ := parser.Aliases.Get(uint32(0))
|
||||
entry := rawEntry.(*AliasEntry)
|
||||
if !(entry.Key.Value == "luke") {
|
||||
t.Fatalf("Expected key to be 'luke', got %v", entry.Key.Value)
|
||||
}
|
||||
|
||||
includeValue := entry.Values.Values[0].(AliasValueInclude)
|
||||
|
||||
if !(includeValue.Path.Path == "/etc/other_aliases") {
|
||||
t.Fatalf("Expected path to be '/etc/other_aliases', got %v", includeValue.Path.Path)
|
||||
}
|
||||
|
||||
if !(includeValue.Location.Start.Character == 6 && includeValue.Location.End.Character == 33) {
|
||||
t.Fatalf("Expected location to be 6-33, got %v-%v", includeValue.Location.Start.Character, includeValue.Location.End.Character)
|
||||
}
|
||||
|
||||
if !(includeValue.Path.Location.Start.Character == 15 && includeValue.Path.Location.End.Character == 33) {
|
||||
t.Fatalf("Expected path location to be 15-33, got %v-%v", includeValue.Path.Location.Start.Character, includeValue.Path.Location.End.Character)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidWithEmptyValuesWorks(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
luke:
|
||||
`)
|
||||
parser := NewAliasesParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) == 0 {
|
||||
t.Fatalf("Expected 1 error, got %v", errors)
|
||||
}
|
||||
|
||||
if !(errors[0].Range.Start.Character == 5 && errors[0].Range.End.Character == 5) {
|
||||
t.Fatalf("Expected error to be at 6, got %v", errors[0].Range.Start.Character)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidWithEmptyKeyWorks(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
: root
|
||||
`)
|
||||
parser := NewAliasesParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) == 0 {
|
||||
t.Fatalf("Expected 1 error, got %v", errors)
|
||||
}
|
||||
|
||||
if !(errors[0].Range.Start.Character == 0 && errors[0].Range.End.Character == 0) {
|
||||
t.Fatalf("Expected error to be at 0, got %v", errors[0].Range.Start.Character)
|
||||
}
|
||||
}
|
129
handlers/aliases/ast/values.go
Normal file
129
handlers/aliases/ast/values.go
Normal file
@ -0,0 +1,129 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
docvalues "config-lsp/doc-values"
|
||||
"config-lsp/handlers/aliases/fields"
|
||||
"config-lsp/utils"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type AliasValueInterface interface {
|
||||
GetAliasValue() AliasValue
|
||||
GetStructName() string
|
||||
}
|
||||
|
||||
func (a AliasValue) String() string {
|
||||
return fmt.Sprintf("%s %s", a.Location, a.Value)
|
||||
}
|
||||
|
||||
func (a AliasValue) GetAliasValue() AliasValue {
|
||||
return a
|
||||
}
|
||||
|
||||
func (a AliasValue) GetStructName() string {
|
||||
return "AliasValue"
|
||||
}
|
||||
|
||||
type AliasValue struct {
|
||||
Location common.LocationRange
|
||||
Value string
|
||||
}
|
||||
|
||||
type AliasValueUser struct {
|
||||
AliasValue
|
||||
}
|
||||
|
||||
func (a AliasValueUser) GetStructName() string {
|
||||
return "AliasValueUser"
|
||||
}
|
||||
|
||||
type path string
|
||||
|
||||
type AliasValueFile struct {
|
||||
AliasValue
|
||||
Path path
|
||||
}
|
||||
|
||||
func (a AliasValueFile) GetStructName() string {
|
||||
return "AliasValueFile"
|
||||
}
|
||||
|
||||
type AliasValueCommand struct {
|
||||
AliasValue
|
||||
Command string
|
||||
}
|
||||
|
||||
func (a AliasValueCommand) GetStructName() string {
|
||||
return "AliasValueCommand"
|
||||
}
|
||||
|
||||
type AliasValueIncludePath struct {
|
||||
Location common.LocationRange
|
||||
Path path
|
||||
}
|
||||
|
||||
type AliasValueInclude struct {
|
||||
AliasValue
|
||||
Path *AliasValueIncludePath
|
||||
}
|
||||
|
||||
func (a AliasValueInclude) CheckIsValid() []common.LSPError {
|
||||
return utils.Map(
|
||||
fields.PathField.CheckIsValid(string(a.Path.Path)),
|
||||
func(invalidValue *docvalues.InvalidValue) common.LSPError {
|
||||
return docvalues.LSPErrorFromInvalidValue(a.Location.Start.Line, *invalidValue)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (a AliasValueInclude) GetStructName() string {
|
||||
return "AliasValueInclude"
|
||||
}
|
||||
|
||||
type AliasValueEmail struct {
|
||||
AliasValue
|
||||
}
|
||||
|
||||
func (a AliasValueEmail) CheckIsValid() []common.LSPError {
|
||||
return utils.Map(
|
||||
fields.PathField.CheckIsValid(a.Value),
|
||||
func(invalidValue *docvalues.InvalidValue) common.LSPError {
|
||||
return docvalues.LSPErrorFromInvalidValue(a.Location.Start.Line, *invalidValue)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (a AliasValueEmail) GetStructName() string {
|
||||
return "AliasValueEmail"
|
||||
}
|
||||
|
||||
type AliasValueError struct {
|
||||
AliasValue
|
||||
|
||||
Code *AliasValueErrorCode
|
||||
Message *AliasValueErrorMessage
|
||||
}
|
||||
|
||||
type AliasValueErrorCode struct {
|
||||
AliasValue
|
||||
}
|
||||
|
||||
func (a AliasValueError) GetStructName() string {
|
||||
return "AliasValueError"
|
||||
}
|
||||
|
||||
func (a AliasValueErrorCode) ErrorCodeAsInt() uint16 {
|
||||
code, err := strconv.Atoi(a.Value)
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return uint16(code)
|
||||
}
|
||||
|
||||
type AliasValueErrorMessage struct {
|
||||
AliasValue
|
||||
}
|
68
handlers/aliases/fetchers/values.go
Normal file
68
handlers/aliases/fetchers/values.go
Normal file
@ -0,0 +1,68 @@
|
||||
package fetchers
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/aliases/indexes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type aliasesUser struct {
|
||||
DefinedOnLine uint32
|
||||
}
|
||||
|
||||
type User struct {
|
||||
PasswdInfo *common.PasswdInfo
|
||||
|
||||
AliasInfo *aliasesUser
|
||||
}
|
||||
|
||||
func (u User) Documentation() string {
|
||||
if u.PasswdInfo != nil {
|
||||
return strings.Join(
|
||||
[]string{
|
||||
fmt.Sprintf("%s (%s:%s)", u.PasswdInfo.Name, u.PasswdInfo.UID, u.PasswdInfo.GID),
|
||||
fmt.Sprintf("Home: `%s`", u.PasswdInfo.HomePath),
|
||||
},
|
||||
"\n",
|
||||
)
|
||||
}
|
||||
|
||||
if u.AliasInfo != nil {
|
||||
return fmt.Sprintf("Defined on line %d", u.AliasInfo.DefinedOnLine+1)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Returns a map of [username]user
|
||||
// The username is normalized
|
||||
func GetAvailableUserValues(
|
||||
i *indexes.AliasesIndexes,
|
||||
) map[string]User {
|
||||
users := make(map[string]User)
|
||||
|
||||
passwdUsers, err := common.FetchPasswdInfo()
|
||||
|
||||
if err == nil {
|
||||
for _, info := range passwdUsers {
|
||||
key := indexes.NormalizeKey(info.Name)
|
||||
users[key] = User{
|
||||
PasswdInfo: &info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i != nil && i.Keys != nil {
|
||||
for name, key := range i.Keys {
|
||||
// Indexes keys are already normalized
|
||||
users[name] = User{
|
||||
AliasInfo: &aliasesUser{
|
||||
DefinedOnLine: key.Location.Start.Line,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return users
|
||||
}
|
54
handlers/aliases/fields/fields.go
Normal file
54
handlers/aliases/fields/fields.go
Normal file
@ -0,0 +1,54 @@
|
||||
package fields
|
||||
|
||||
import (
|
||||
commondocumentation "config-lsp/common-documentation"
|
||||
docvalues "config-lsp/doc-values"
|
||||
)
|
||||
|
||||
var UserField = docvalues.DocumentationValue{
|
||||
Documentation: "A user on the host machine. The user must have a valid entry in the passwd(5) database file.",
|
||||
Value: docvalues.UserValue("", false),
|
||||
}
|
||||
|
||||
var UserDeclaration = "`user`"
|
||||
|
||||
var PathField = docvalues.DocumentationValue{
|
||||
Documentation: "Append messages to file, specified by its absolute pathname",
|
||||
Value: docvalues.PathValue{
|
||||
RequiredType: docvalues.PathTypeFile,
|
||||
},
|
||||
}
|
||||
|
||||
var PathDeclaration = "`/path/to/file`"
|
||||
|
||||
var CommandField = docvalues.DocumentationValue{
|
||||
Documentation: "Pipe the message to command on its standard input. The command is run under the privileges of the daemon's unprivileged account.",
|
||||
Value: docvalues.StringValue{},
|
||||
}
|
||||
|
||||
var CommandDeclaration = "`|command`"
|
||||
|
||||
var EmailField = docvalues.DocumentationValue{
|
||||
Documentation: "An email address in RFC 5322 format. If an address extension is appended to the user-part, it is first compared for an exact match. It is then stripped so that an address such as user+ext@example.com will only use the part that precedes ‘+’ as a key.",
|
||||
Value: docvalues.RegexValue{
|
||||
Regex: *commondocumentation.EmailRegex,
|
||||
},
|
||||
}
|
||||
|
||||
var EmailDeclaration = "`user-part@domain-part`"
|
||||
|
||||
var IncludeField = docvalues.DocumentationValue{
|
||||
Documentation: "Include any definitions in file as alias entries. The format of the file is identical to this one.",
|
||||
Value: docvalues.PathValue{
|
||||
RequiredType: docvalues.PathTypeFile,
|
||||
},
|
||||
}
|
||||
|
||||
var IncludeDeclaration = "`include:/path/to/file`"
|
||||
|
||||
var ErrorMessageField = docvalues.DocumentationValue{
|
||||
Documentation: "A status code and message to return. The code must be 3 digits, starting 4XX (TempFail) or 5XX (PermFail). The message must be present and can be freely chosen.",
|
||||
Value: docvalues.StringValue{},
|
||||
}
|
||||
|
||||
var ErrorDeclaration = "`error:code message`"
|
206
handlers/aliases/handlers/completions.go
Normal file
206
handlers/aliases/handlers/completions.go
Normal file
@ -0,0 +1,206 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases/analyzer"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/fetchers"
|
||||
"config-lsp/handlers/aliases/indexes"
|
||||
"fmt"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func GetAliasesCompletions(
|
||||
i *indexes.AliasesIndexes,
|
||||
) []protocol.CompletionItem {
|
||||
completions := make([]protocol.CompletionItem, 0)
|
||||
aliases := analyzer.RequiredAliases
|
||||
|
||||
kind := protocol.CompletionItemKindValue
|
||||
|
||||
for _, alias := range aliases {
|
||||
if i != nil {
|
||||
if _, found := i.Keys[alias]; found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
text := fmt.Sprintf("%s: ", alias)
|
||||
completions = append(completions, protocol.CompletionItem{
|
||||
Label: alias,
|
||||
Kind: &kind,
|
||||
InsertText: &text,
|
||||
Documentation: "This alias is required by the aliases file",
|
||||
})
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
func GetCompletionsForEntry(
|
||||
cursor uint32,
|
||||
entry *ast.AliasEntry,
|
||||
i *indexes.AliasesIndexes,
|
||||
) ([]protocol.CompletionItem, error) {
|
||||
completions := make([]protocol.CompletionItem, 0)
|
||||
|
||||
if entry.Key == nil {
|
||||
return completions, nil
|
||||
}
|
||||
|
||||
value := GetValueAtCursor(cursor, entry)
|
||||
relativeCursor := cursor - entry.Key.Location.Start.Character
|
||||
|
||||
excludedUsers := getUsersFromEntry(entry)
|
||||
|
||||
if value == nil {
|
||||
completions = append(completions, getCommandCompletion())
|
||||
completions = append(completions, getIncludeCompletion())
|
||||
completions = append(completions, getErrorCompletion())
|
||||
|
||||
completions = append(completions, getUserCompletions(
|
||||
i,
|
||||
excludedUsers,
|
||||
"",
|
||||
0,
|
||||
)...)
|
||||
|
||||
return completions, nil
|
||||
}
|
||||
|
||||
switch (*value).(type) {
|
||||
case ast.AliasValueUser:
|
||||
userValue := (*value).(ast.AliasValueUser)
|
||||
|
||||
return getUserCompletions(
|
||||
i,
|
||||
excludedUsers,
|
||||
userValue.Value,
|
||||
relativeCursor,
|
||||
), nil
|
||||
case ast.AliasValueError:
|
||||
errorValue := (*value).(ast.AliasValueError)
|
||||
|
||||
isAtErrorCode := errorValue.Code == nil &&
|
||||
relativeCursor >= errorValue.Location.Start.Character &&
|
||||
(errorValue.Message == nil ||
|
||||
relativeCursor <= errorValue.Message.Location.Start.Character)
|
||||
|
||||
if isAtErrorCode {
|
||||
kind := protocol.CompletionItemKindValue
|
||||
|
||||
detail_4 := "4XX (TempFail)"
|
||||
insertText_4 := "400"
|
||||
|
||||
detail_5 := "5XX (PermFail)"
|
||||
insertText_5 := "500"
|
||||
|
||||
return []protocol.CompletionItem{
|
||||
{
|
||||
Label: "4XX",
|
||||
InsertText: &insertText_4,
|
||||
Kind: &kind,
|
||||
Detail: &detail_4,
|
||||
},
|
||||
{
|
||||
Label: "5XX",
|
||||
InsertText: &insertText_5,
|
||||
Kind: &kind,
|
||||
Detail: &detail_5,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return completions, nil
|
||||
}
|
||||
|
||||
func getCommandCompletion() protocol.CompletionItem {
|
||||
kind := protocol.CompletionItemKindKeyword
|
||||
textFormat := protocol.InsertTextFormatSnippet
|
||||
insertText := "|"
|
||||
|
||||
return protocol.CompletionItem{
|
||||
Label: "|<command>",
|
||||
Documentation: "Pipe the message to command on its standard input. The command is run under the privileges of the daemon's unprivileged account.",
|
||||
Kind: &kind,
|
||||
InsertTextFormat: &textFormat,
|
||||
InsertText: &insertText,
|
||||
}
|
||||
}
|
||||
|
||||
func getIncludeCompletion() protocol.CompletionItem {
|
||||
kind := protocol.CompletionItemKindKeyword
|
||||
textFormat := protocol.InsertTextFormatSnippet
|
||||
insertText := ":include:"
|
||||
|
||||
return protocol.CompletionItem{
|
||||
Label: ":include:<path>",
|
||||
Documentation: " Include any definitions in file as alias entries. The format of the file is identical to this one.",
|
||||
Kind: &kind,
|
||||
InsertTextFormat: &textFormat,
|
||||
InsertText: &insertText,
|
||||
}
|
||||
}
|
||||
|
||||
func getErrorCompletion() protocol.CompletionItem {
|
||||
kind := protocol.CompletionItemKindKeyword
|
||||
textFormat := protocol.InsertTextFormatSnippet
|
||||
insertText := "error:"
|
||||
|
||||
return protocol.CompletionItem{
|
||||
Label: "error:<message>",
|
||||
Documentation: "A status code and message to return. The code must be 3 digits, starting 4XX (TempFail) or 5XX (PermFail). The message must be present and can be freely chosen.",
|
||||
Kind: &kind,
|
||||
InsertTextFormat: &textFormat,
|
||||
InsertText: &insertText,
|
||||
}
|
||||
}
|
||||
|
||||
func getUserCompletions(
|
||||
i *indexes.AliasesIndexes,
|
||||
excluded map[string]struct{},
|
||||
line string,
|
||||
cursor uint32,
|
||||
) []protocol.CompletionItem {
|
||||
users := fetchers.GetAvailableUserValues(i)
|
||||
|
||||
kind := protocol.CompletionItemKindValue
|
||||
|
||||
completions := make([]protocol.CompletionItem, 0)
|
||||
|
||||
for name, user := range users {
|
||||
if _, found := excluded[name]; found {
|
||||
continue
|
||||
}
|
||||
|
||||
completions = append(completions, protocol.CompletionItem{
|
||||
Label: name,
|
||||
Kind: &kind,
|
||||
Documentation: user.Documentation(),
|
||||
})
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
func getUsersFromEntry(
|
||||
entry *ast.AliasEntry,
|
||||
) map[string]struct{} {
|
||||
users := map[string]struct{}{
|
||||
indexes.NormalizeKey(entry.Key.Value): {},
|
||||
}
|
||||
|
||||
if entry.Values != nil {
|
||||
for _, value := range entry.Values.Values {
|
||||
switch (value).(type) {
|
||||
case ast.AliasValueUser:
|
||||
userValue := value.(ast.AliasValueUser)
|
||||
|
||||
users[indexes.NormalizeKey(userValue.Value)] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return users
|
||||
}
|
39
handlers/aliases/handlers/get-value.go
Normal file
39
handlers/aliases/handlers/get-value.go
Normal file
@ -0,0 +1,39 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"slices"
|
||||
)
|
||||
|
||||
func GetValueAtCursor(
|
||||
cursor uint32,
|
||||
entry *ast.AliasEntry,
|
||||
) *ast.AliasValueInterface {
|
||||
if entry.Values == nil || len(entry.Values.Values) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
index, found := slices.BinarySearchFunc(
|
||||
entry.Values.Values,
|
||||
cursor,
|
||||
func(entry ast.AliasValueInterface, pos uint32) int {
|
||||
value := entry.GetAliasValue()
|
||||
|
||||
if pos > value.Location.End.Character {
|
||||
return -1
|
||||
}
|
||||
|
||||
if pos < value.Location.Start.Character {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
},
|
||||
)
|
||||
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &entry.Values.Values[index]
|
||||
}
|
94
handlers/aliases/handlers/go_to_definition.go
Normal file
94
handlers/aliases/handlers/go_to_definition.go
Normal file
@ -0,0 +1,94 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/indexes"
|
||||
"config-lsp/utils"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func GetDefinitionLocationForValue(
|
||||
i indexes.AliasesIndexes,
|
||||
value ast.AliasValueInterface,
|
||||
documentURI string,
|
||||
) []protocol.Location {
|
||||
switch value.(type) {
|
||||
case ast.AliasValueUser:
|
||||
userValue := value.(ast.AliasValueUser)
|
||||
|
||||
// Own defined alias
|
||||
if entry, found := i.Keys[indexes.NormalizeKey(userValue.Value)]; found {
|
||||
return []protocol.Location{
|
||||
{
|
||||
URI: documentURI,
|
||||
Range: entry.Key.Location.ToLSPRange(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// System user
|
||||
systemUsers, _ := getSystemUserMap()
|
||||
if user, found := systemUsers[userValue.Value]; found {
|
||||
return []protocol.Location{
|
||||
{
|
||||
URI: "file:///etc/passwd",
|
||||
Range: protocol.Range{
|
||||
Start: protocol.Position{
|
||||
Line: user.Line,
|
||||
Character: 0,
|
||||
},
|
||||
End: protocol.Position{
|
||||
Line: user.Line,
|
||||
Character: uint32(len(user.Name)),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
case ast.AliasValueFile:
|
||||
fileValue := value.(ast.AliasValueFile)
|
||||
path := string(fileValue.Path)
|
||||
|
||||
if utils.DoesPathExist(path) {
|
||||
return []protocol.Location{
|
||||
{
|
||||
URI: "file://" + path,
|
||||
},
|
||||
}
|
||||
}
|
||||
case ast.AliasValueInclude:
|
||||
includeValue := value.(ast.AliasValueInclude)
|
||||
|
||||
if includeValue.Path != nil {
|
||||
path := string(includeValue.Path.Path)
|
||||
|
||||
if utils.DoesPathExist(path) {
|
||||
return []protocol.Location{
|
||||
{
|
||||
URI: "file://" + path,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSystemUserMap() (map[string]common.PasswdInfo, error) {
|
||||
users, err := common.FetchPasswdInfo()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userMap := make(map[string]common.PasswdInfo)
|
||||
|
||||
for _, user := range users {
|
||||
userMap[user.Name] = user
|
||||
}
|
||||
|
||||
return userMap, nil
|
||||
}
|
54
handlers/aliases/handlers/go_to_definition_test.go
Normal file
54
handlers/aliases/handlers/go_to_definition_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/indexes"
|
||||
"config-lsp/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGoToDefinitionSimpleExample(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
alice: root
|
||||
bob: root
|
||||
steve: alice@example.com, bob
|
||||
david: alice
|
||||
`)
|
||||
parser := ast.NewAliasesParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) > 0 {
|
||||
t.Fatalf("Unexpected errors: %v", errors)
|
||||
}
|
||||
|
||||
i, errors := indexes.CreateIndexes(parser)
|
||||
|
||||
if len(errors) > 0 {
|
||||
t.Fatalf("Expected no errors, but got: %v", errors)
|
||||
}
|
||||
|
||||
rawEntry, _ := parser.Aliases.Get(uint32(3))
|
||||
entry := rawEntry.(*ast.AliasEntry)
|
||||
rawValue := entry.Values.Values[0]
|
||||
value := rawValue.(ast.AliasValueUser)
|
||||
|
||||
locations := GetDefinitionLocationForValue(
|
||||
i,
|
||||
value,
|
||||
"file:///etc/aliases",
|
||||
)
|
||||
|
||||
if !(len(locations) == 1) {
|
||||
t.Errorf("Expected 1 location, but got %v", len(locations))
|
||||
}
|
||||
|
||||
if !(locations[0].URI == "file:///etc/aliases") {
|
||||
t.Errorf("Unexpected location: %v", locations[0])
|
||||
}
|
||||
|
||||
if !(locations[0].Range.Start.Line == 0 && locations[0].Range.Start.Character == 0 && locations[0].Range.End.Line == 0 && locations[0].Range.End.Character == 5) {
|
||||
t.Errorf("Unexpected location: %v", locations[0])
|
||||
}
|
||||
}
|
160
handlers/aliases/handlers/hover.go
Normal file
160
handlers/aliases/handlers/hover.go
Normal file
@ -0,0 +1,160 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/fields"
|
||||
"config-lsp/handlers/aliases/indexes"
|
||||
"config-lsp/utils"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Get hover information for an alias entry
|
||||
// Expects `entry` to contain at least a key
|
||||
func GetAliasHoverInfo(
|
||||
i indexes.AliasesIndexes,
|
||||
entry ast.AliasEntry,
|
||||
) string {
|
||||
header := []string{
|
||||
fmt.Sprintf("Emails targeted for `%s` will be passed to:", entry.Key.Value),
|
||||
"",
|
||||
}
|
||||
|
||||
var forwards []string
|
||||
|
||||
if entry.Values == nil {
|
||||
forwards = []string{
|
||||
"No forwards configured",
|
||||
}
|
||||
} else {
|
||||
if len(entry.Values.Values) == 1 {
|
||||
forwards = []string{
|
||||
GetAliasValueHoverInfo(
|
||||
i,
|
||||
entry.Values.Values[0],
|
||||
),
|
||||
}
|
||||
} else {
|
||||
forwards = utils.Map(
|
||||
entry.Values.Values,
|
||||
func(value ast.AliasValueInterface) string {
|
||||
return fmt.Sprintf(
|
||||
"* %s",
|
||||
GetAliasValueHoverInfo(
|
||||
i,
|
||||
value,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
content := append(header, forwards...)
|
||||
return strings.Join(
|
||||
content,
|
||||
"\n",
|
||||
)
|
||||
}
|
||||
|
||||
func GetAliasValueHoverInfo(
|
||||
i indexes.AliasesIndexes,
|
||||
value ast.AliasValueInterface,
|
||||
) string {
|
||||
switch value.(type) {
|
||||
case ast.AliasValueUser:
|
||||
return fmt.Sprintf("User: **%s**", value.GetAliasValue().Value)
|
||||
case ast.AliasValueEmail:
|
||||
return fmt.Sprintf("Email: **%s**", value.GetAliasValue().Value)
|
||||
case ast.AliasValueInclude:
|
||||
includeValue := value.(ast.AliasValueInclude)
|
||||
return fmt.Sprintf("Included file: `%s`", string(includeValue.Path.Path))
|
||||
case ast.AliasValueFile:
|
||||
fileValue := value.(ast.AliasValueFile)
|
||||
return fmt.Sprintf("File: Email will be written to `%s`", string(fileValue.Path))
|
||||
case ast.AliasValueCommand:
|
||||
commandValue := value.(ast.AliasValueCommand)
|
||||
return fmt.Sprintf("Command: Will be passed as stdin to `%s`", commandValue.Command)
|
||||
case ast.AliasValueError:
|
||||
errorValue := value.(ast.AliasValueError)
|
||||
|
||||
if errorValue.Code == nil || errorValue.Message == nil {
|
||||
return "Error: An error will show up"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"Error: An error will show up; code: **%s** (%s), message: '%s'",
|
||||
errorValue.Code.Value,
|
||||
getErrorCodeInfo(errorValue.Code.ErrorCodeAsInt()),
|
||||
errorValue.Message.Value,
|
||||
)
|
||||
}
|
||||
|
||||
panic("Unknown value type")
|
||||
}
|
||||
|
||||
func GetAliasValueTypeInfo(
|
||||
value ast.AliasValueInterface,
|
||||
) []string {
|
||||
println(fmt.Sprintf("value: %v, value type: %T", value, value))
|
||||
switch value.(type) {
|
||||
case ast.AliasValueUser:
|
||||
return []string{
|
||||
"### User",
|
||||
fields.UserDeclaration,
|
||||
"",
|
||||
fields.UserField.Documentation,
|
||||
}
|
||||
case ast.AliasValueEmail:
|
||||
return []string{
|
||||
"### Email",
|
||||
fields.EmailDeclaration,
|
||||
"",
|
||||
fields.EmailField.Documentation,
|
||||
}
|
||||
case ast.AliasValueInclude:
|
||||
return []string{
|
||||
"### Include",
|
||||
fields.IncludeDeclaration,
|
||||
"",
|
||||
fields.IncludeField.Documentation,
|
||||
}
|
||||
case ast.AliasValueFile:
|
||||
return []string{
|
||||
"### File",
|
||||
fields.PathDeclaration,
|
||||
"",
|
||||
fields.PathField.Documentation,
|
||||
}
|
||||
case ast.AliasValueCommand:
|
||||
return []string{
|
||||
"### Command",
|
||||
fields.CommandDeclaration,
|
||||
"",
|
||||
fields.CommandField.Documentation,
|
||||
}
|
||||
case ast.AliasValueError:
|
||||
return []string{
|
||||
"### Error",
|
||||
fields.ErrorDeclaration,
|
||||
"",
|
||||
fields.ErrorMessageField.Documentation,
|
||||
}
|
||||
}
|
||||
|
||||
panic("Unknown value type")
|
||||
}
|
||||
|
||||
func getErrorCodeInfo(
|
||||
code uint16,
|
||||
) string {
|
||||
if code >= 400 && code <= 499 {
|
||||
return "4XX: TempFail"
|
||||
}
|
||||
|
||||
if code >= 500 && code <= 599 {
|
||||
return "5XX: PermFail"
|
||||
}
|
||||
|
||||
return "Unknown code"
|
||||
}
|
33
handlers/aliases/handlers/rename.go
Normal file
33
handlers/aliases/handlers/rename.go
Normal file
@ -0,0 +1,33 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/indexes"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func RenameAlias(
|
||||
i indexes.AliasesIndexes,
|
||||
oldEntry *ast.AliasEntry,
|
||||
newName string,
|
||||
) []protocol.TextEdit {
|
||||
occurrences := i.UserOccurrences[indexes.NormalizeKey(oldEntry.Key.Value)]
|
||||
changes := make([]protocol.TextEdit, 0, len(occurrences))
|
||||
|
||||
// Own rename
|
||||
changes = append(changes, protocol.TextEdit{
|
||||
Range: oldEntry.Key.Location.ToLSPRange(),
|
||||
NewText: newName,
|
||||
})
|
||||
|
||||
// Other AliasValueUser occurrences
|
||||
for _, value := range occurrences {
|
||||
changes = append(changes, protocol.TextEdit{
|
||||
Range: value.Location.ToLSPRange(),
|
||||
NewText: newName,
|
||||
})
|
||||
}
|
||||
|
||||
return changes
|
||||
}
|
48
handlers/aliases/handlers/rename_test.go
Normal file
48
handlers/aliases/handlers/rename_test.go
Normal file
@ -0,0 +1,48 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/indexes"
|
||||
"config-lsp/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRenameSimpleExample(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
alice: alice
|
||||
bob: root
|
||||
support: alice, bob
|
||||
`)
|
||||
parser := ast.NewAliasesParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) > 0 {
|
||||
t.Fatalf("Unexpected errors: %v", errors)
|
||||
}
|
||||
|
||||
i, errors := indexes.CreateIndexes(parser)
|
||||
|
||||
if len(errors) > 0 {
|
||||
t.Fatalf("Expected no errors, but got: %v", errors)
|
||||
}
|
||||
|
||||
edits := RenameAlias(i, i.Keys["alice"], "amelie")
|
||||
|
||||
if !(len(edits) == 3) {
|
||||
t.Errorf("Expected 2 edits, but got %v", len(edits))
|
||||
}
|
||||
|
||||
if !(edits[0].Range.Start.Line == 0 && edits[0].Range.Start.Character == 0 && edits[0].Range.End.Line == 0 && edits[0].Range.End.Character == 5) {
|
||||
t.Errorf("Unexpected edit: %v", edits[0])
|
||||
}
|
||||
|
||||
if !(edits[1].Range.Start.Line == 0 && edits[1].Range.Start.Character == 7 && edits[1].Range.End.Line == 0 && edits[1].Range.End.Character == 12) {
|
||||
t.Errorf("Unexpected edit: %v", edits[1])
|
||||
}
|
||||
|
||||
if !(edits[2].Range.Start.Line == 2 && edits[2].Range.Start.Character == 9 && edits[2].Range.End.Line == 2 && edits[2].Range.End.Character == 14) {
|
||||
t.Errorf("Unexpected edit: %v", edits[2])
|
||||
}
|
||||
}
|
309
handlers/aliases/handlers/signature_help.go
Normal file
309
handlers/aliases/handlers/signature_help.go
Normal file
@ -0,0 +1,309 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"strings"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func GetRootSignatureHelp(
|
||||
activeParameter uint32,
|
||||
) *protocol.SignatureHelp {
|
||||
index := uint32(0)
|
||||
return &protocol.SignatureHelp{
|
||||
ActiveSignature: &index,
|
||||
Signatures: []protocol.SignatureInformation{
|
||||
{
|
||||
Label: "<alias>: <value1>, <value2>, ...",
|
||||
ActiveParameter: &activeParameter,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
0,
|
||||
uint32(len("<alias>") + 1),
|
||||
},
|
||||
Documentation: "The alias to define",
|
||||
},
|
||||
{
|
||||
Label: []uint32{
|
||||
uint32(len("<alias>:")),
|
||||
uint32(len("<alias>:") + len("<value1>")),
|
||||
},
|
||||
Documentation: "A value to associate with the alias",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func GetAllValuesSignatureHelp() *protocol.SignatureHelp {
|
||||
index := uint32(0)
|
||||
return &protocol.SignatureHelp{
|
||||
Signatures: []protocol.SignatureInformation{
|
||||
{
|
||||
Label: "<user>",
|
||||
ActiveParameter: &index,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
0,
|
||||
uint32(len("<user>")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "<user>@<host>",
|
||||
ActiveParameter: &index,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
0,
|
||||
uint32(len("<user>")),
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: []uint32{
|
||||
uint32(len("<user>@")),
|
||||
uint32(len("<user>@<host>")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "<file>",
|
||||
ActiveParameter: &index,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
0,
|
||||
uint32(len("<file>")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: ":include:<file>",
|
||||
ActiveParameter: &index,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
0,
|
||||
uint32(len(":include:")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "|<command>",
|
||||
ActiveParameter: &index,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
0,
|
||||
1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "error:<code> <message>",
|
||||
ActiveParameter: &index,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
0,
|
||||
uint32(len("error")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func GetValueSignatureHelp(
|
||||
value ast.AliasValueInterface,
|
||||
cursor uint32,
|
||||
) *protocol.SignatureHelp {
|
||||
switch value.(type) {
|
||||
case ast.AliasValueUser:
|
||||
index := uint32(0)
|
||||
return &protocol.SignatureHelp{
|
||||
Signatures: []protocol.SignatureInformation{
|
||||
{
|
||||
Label: "<user>",
|
||||
ActiveParameter: &index,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
0,
|
||||
uint32(len("<user>")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "<user>@<host>",
|
||||
ActiveParameter: &index,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
0,
|
||||
uint32(len("<user>")),
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: []uint32{
|
||||
uint32(len("<user>@")),
|
||||
uint32(len("<user>@") + len("<host>")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
case ast.AliasValueEmail:
|
||||
isBeforeAtSymbol := cursor <= uint32(strings.Index(value.GetAliasValue().Value, "@"))
|
||||
|
||||
var index uint32
|
||||
|
||||
if isBeforeAtSymbol {
|
||||
index = 0
|
||||
} else {
|
||||
index = 1
|
||||
}
|
||||
|
||||
return &protocol.SignatureHelp{
|
||||
Signatures: []protocol.SignatureInformation{
|
||||
{
|
||||
Label: "<user>@<host>",
|
||||
ActiveParameter: &index,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
0,
|
||||
uint32(len("<user>")),
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: []uint32{
|
||||
uint32(len("<user>@")),
|
||||
uint32(len("<user>@") + len("<host>")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
case ast.AliasValueFile:
|
||||
index := uint32(0)
|
||||
return &protocol.SignatureHelp{
|
||||
Signatures: []protocol.SignatureInformation{
|
||||
{
|
||||
Label: "<file>",
|
||||
ActiveParameter: &index,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
0,
|
||||
uint32(len("<file>")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
case ast.AliasValueInclude:
|
||||
index := uint32(0)
|
||||
return &protocol.SignatureHelp{
|
||||
Signatures: []protocol.SignatureInformation{
|
||||
{
|
||||
Label: "include:<file>",
|
||||
ActiveParameter: &index,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
uint32(len("include:")),
|
||||
uint32(len("include:<file>")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
case ast.AliasValueCommand:
|
||||
var index uint32
|
||||
|
||||
if cursor == 0 {
|
||||
index = 0
|
||||
} else {
|
||||
index = 1
|
||||
}
|
||||
|
||||
return &protocol.SignatureHelp{
|
||||
Signatures: []protocol.SignatureInformation{
|
||||
{
|
||||
Label: "|<command>",
|
||||
ActiveParameter: &index,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
0,
|
||||
1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: []uint32{
|
||||
1,
|
||||
uint32(1 + len("<command>")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
case ast.AliasValueError:
|
||||
errorValue := value.(ast.AliasValueError)
|
||||
var index uint32
|
||||
|
||||
if errorValue.Code == nil || cursor <= errorValue.Code.Location.End.Character {
|
||||
index = 1
|
||||
} else {
|
||||
index = 2
|
||||
}
|
||||
|
||||
return &protocol.SignatureHelp{
|
||||
Signatures: []protocol.SignatureInformation{
|
||||
{
|
||||
Label: "error:<code> <message>",
|
||||
ActiveParameter: &index,
|
||||
Parameters: []protocol.ParameterInformation{
|
||||
{
|
||||
Label: []uint32{
|
||||
0,
|
||||
uint32(len("error:")),
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: []uint32{
|
||||
uint32(len("error:")),
|
||||
uint32(len("error:<code>")),
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: []uint32{
|
||||
uint32(len("error:<code> ")),
|
||||
uint32(len("error:<code> <message>")),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
67
handlers/aliases/indexes/indexes.go
Normal file
67
handlers/aliases/indexes/indexes.go
Normal file
@ -0,0 +1,67 @@
|
||||
package indexes
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/shared"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AliasesIndexes struct {
|
||||
Keys map[string]*ast.AliasEntry
|
||||
UserOccurrences map[string][]*ast.AliasValueUser
|
||||
}
|
||||
|
||||
func NormalizeKey(key string) string {
|
||||
return strings.ToLower(key)
|
||||
}
|
||||
|
||||
func CreateIndexes(parser ast.AliasesParser) (AliasesIndexes, []common.LSPError) {
|
||||
errors := make([]common.LSPError, 0)
|
||||
indexes := &AliasesIndexes{
|
||||
Keys: make(map[string]*ast.AliasEntry),
|
||||
UserOccurrences: make(map[string][]*ast.AliasValueUser),
|
||||
}
|
||||
|
||||
it := parser.Aliases.Iterator()
|
||||
|
||||
for it.Next() {
|
||||
entry := it.Value().(*ast.AliasEntry)
|
||||
|
||||
if entry.Values != nil {
|
||||
for _, value := range entry.Values.Values {
|
||||
switch value.(type) {
|
||||
case ast.AliasValueUser:
|
||||
userValue := value.(ast.AliasValueUser)
|
||||
|
||||
indexes.UserOccurrences[userValue.Value] = append(
|
||||
indexes.UserOccurrences[userValue.Value],
|
||||
&userValue,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if entry.Key == nil || entry.Key.Value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
normalizedAlias := NormalizeKey(entry.Key.Value)
|
||||
|
||||
if existingEntry, found := indexes.Keys[normalizedAlias]; found {
|
||||
errors = append(errors, common.LSPError{
|
||||
Range: entry.Key.Location,
|
||||
Err: shared.DuplicateKeyEntry{
|
||||
AlreadyFoundAt: existingEntry.Location.Start.Line,
|
||||
Key: entry.Key.Value,
|
||||
},
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
indexes.Keys[normalizedAlias] = entry
|
||||
}
|
||||
|
||||
return *indexes, errors
|
||||
}
|
49
handlers/aliases/indexes/indexes_test.go
Normal file
49
handlers/aliases/indexes/indexes_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
package indexes
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestComplexExample(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
postmaster: alice, bob
|
||||
alice: root
|
||||
bob: root
|
||||
`)
|
||||
parser := ast.NewAliasesParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) > 0 {
|
||||
t.Fatalf("Unexpected errors: %v", errors)
|
||||
}
|
||||
|
||||
indexes, errors := CreateIndexes(parser)
|
||||
|
||||
if len(errors) > 0 {
|
||||
t.Fatalf("Expected no errors, but got: %v", errors)
|
||||
}
|
||||
|
||||
if !(len(indexes.Keys) == 3) {
|
||||
t.Errorf("Expected 3 keys, but got %v", len(indexes.Keys))
|
||||
}
|
||||
|
||||
if !(len(indexes.UserOccurrences) == 3) {
|
||||
t.Errorf("Expected 3 user occurrences, but got %v", len(indexes.UserOccurrences))
|
||||
}
|
||||
|
||||
if !(len(indexes.UserOccurrences["root"]) == 2) {
|
||||
t.Errorf("Expected 2 occurrences of root, but got %v", len(indexes.UserOccurrences["root"]))
|
||||
}
|
||||
|
||||
if !(len(indexes.UserOccurrences["alice"]) == 1) {
|
||||
t.Errorf("Expected 1 occurrence of alice, but got %v", len(indexes.UserOccurrences["alice"]))
|
||||
}
|
||||
|
||||
if !(len(indexes.UserOccurrences["bob"]) == 1) {
|
||||
t.Errorf("Expected 1 occurrence of bob, but got %v", len(indexes.UserOccurrences["bob"]))
|
||||
}
|
||||
}
|
14
handlers/aliases/lsp/text-document-code-action.go
Normal file
14
handlers/aliases/lsp/text-document-code-action.go
Normal file
@ -0,0 +1,14 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
|
||||
// document := hosts.DocumentParserMap[params.TextDocument.URI]
|
||||
//
|
||||
// actions := make([]protocol.CodeAction, 0, 1)
|
||||
|
||||
return nil, nil
|
||||
}
|
51
handlers/aliases/lsp/text-document-completion.go
Normal file
51
handlers/aliases/lsp/text-document-completion.go
Normal file
@ -0,0 +1,51 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/handlers"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) {
|
||||
d := aliases.DocumentParserMap[params.TextDocument.URI]
|
||||
cursor := params.Position.Character
|
||||
line := params.Position.Line
|
||||
|
||||
if _, found := d.Parser.CommentLines[line]; found {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rawEntry, found := d.Parser.Aliases.Get(line)
|
||||
|
||||
if !found {
|
||||
// For the key there are no completions available
|
||||
return handlers.GetAliasesCompletions(d.Indexes), nil
|
||||
}
|
||||
|
||||
entry := rawEntry.(*ast.AliasEntry)
|
||||
|
||||
if entry.Key == nil {
|
||||
return handlers.GetAliasesCompletions(d.Indexes), nil
|
||||
}
|
||||
|
||||
if cursor >= entry.Key.Location.Start.Character && cursor <= entry.Key.Location.End.Character {
|
||||
return handlers.GetAliasesCompletions(d.Indexes), nil
|
||||
}
|
||||
|
||||
if entry.Separator == nil && cursor > entry.Key.Location.End.Character {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if cursor > entry.Separator.End.Character {
|
||||
return handlers.GetCompletionsForEntry(
|
||||
cursor,
|
||||
entry,
|
||||
d.Indexes,
|
||||
)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
40
handlers/aliases/lsp/text-document-definition.go
Normal file
40
handlers/aliases/lsp/text-document-definition.go
Normal file
@ -0,0 +1,40 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/handlers"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentDefinition(context *glsp.Context, params *protocol.DefinitionParams) ([]protocol.Location, error) {
|
||||
d := aliases.DocumentParserMap[params.TextDocument.URI]
|
||||
character := params.Position.Character
|
||||
line := params.Position.Line
|
||||
|
||||
rawEntry, found := d.Parser.Aliases.Get(line)
|
||||
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
entry := rawEntry.(*ast.AliasEntry)
|
||||
|
||||
if entry.Values != nil && character >= entry.Values.Location.Start.Character && character <= entry.Values.Location.End.Character {
|
||||
rawValue := handlers.GetValueAtCursor(character, entry)
|
||||
|
||||
if rawValue == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return handlers.GetDefinitionLocationForValue(
|
||||
*d.Indexes,
|
||||
*rawValue,
|
||||
params.TextDocument.URI,
|
||||
), nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
41
handlers/aliases/lsp/text-document-did-change.go
Normal file
41
handlers/aliases/lsp/text-document-did-change.go
Normal file
@ -0,0 +1,41 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/handlers/aliases/analyzer"
|
||||
"config-lsp/utils"
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentDidChange(
|
||||
context *glsp.Context,
|
||||
params *protocol.DidChangeTextDocumentParams,
|
||||
) error {
|
||||
content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text
|
||||
common.ClearDiagnostics(context, params.TextDocument.URI)
|
||||
|
||||
document := aliases.DocumentParserMap[params.TextDocument.URI]
|
||||
document.Parser.Clear()
|
||||
|
||||
diagnostics := make([]protocol.Diagnostic, 0)
|
||||
errors := document.Parser.Parse(content)
|
||||
|
||||
if len(errors) > 0 {
|
||||
diagnostics = append(diagnostics, utils.Map(
|
||||
errors,
|
||||
func(err common.LSPError) protocol.Diagnostic {
|
||||
return err.ToDiagnostic()
|
||||
},
|
||||
)...)
|
||||
}
|
||||
|
||||
diagnostics = append(diagnostics, analyzer.Analyze(document)...)
|
||||
|
||||
if len(diagnostics) > 0 {
|
||||
common.SendDiagnostics(context, params.TextDocument.URI, diagnostics)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
13
handlers/aliases/lsp/text-document-did-close.go
Normal file
13
handlers/aliases/lsp/text-document-did-close.go
Normal file
@ -0,0 +1,13 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts"
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentDidClose(context *glsp.Context, params *protocol.DidCloseTextDocumentParams) error {
|
||||
delete(hosts.DocumentParserMap, params.TextDocument.URI)
|
||||
|
||||
return nil
|
||||
}
|
44
handlers/aliases/lsp/text-document-did-open.go
Normal file
44
handlers/aliases/lsp/text-document-did-open.go
Normal file
@ -0,0 +1,44 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/handlers/aliases/analyzer"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/utils"
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentDidOpen(
|
||||
context *glsp.Context,
|
||||
params *protocol.DidOpenTextDocumentParams,
|
||||
) error {
|
||||
common.ClearDiagnostics(context, params.TextDocument.URI)
|
||||
|
||||
parser := ast.NewAliasesParser()
|
||||
document := aliases.AliasesDocument{
|
||||
Parser: &parser,
|
||||
}
|
||||
aliases.DocumentParserMap[params.TextDocument.URI] = &document
|
||||
|
||||
errors := parser.Parse(params.TextDocument.Text)
|
||||
|
||||
diagnostics := utils.Map(
|
||||
errors,
|
||||
func(err common.LSPError) protocol.Diagnostic {
|
||||
return err.ToDiagnostic()
|
||||
},
|
||||
)
|
||||
|
||||
diagnostics = append(
|
||||
diagnostics,
|
||||
analyzer.Analyze(&document)...,
|
||||
)
|
||||
|
||||
if len(diagnostics) > 0 {
|
||||
common.SendDiagnostics(context, params.TextDocument.URI, diagnostics)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
64
handlers/aliases/lsp/text-document-hover.go
Normal file
64
handlers/aliases/lsp/text-document-hover.go
Normal file
@ -0,0 +1,64 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/handlers"
|
||||
"strings"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentHover(
|
||||
context *glsp.Context,
|
||||
params *protocol.HoverParams,
|
||||
) (*protocol.Hover, error) {
|
||||
document := aliases.DocumentParserMap[params.TextDocument.URI]
|
||||
|
||||
line := params.Position.Line
|
||||
character := params.Position.Character
|
||||
|
||||
if _, found := document.Parser.CommentLines[line]; found {
|
||||
// Comment
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rawEntry, found := document.Parser.Aliases.Get(line)
|
||||
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
entry := rawEntry.(*ast.AliasEntry)
|
||||
|
||||
if entry.Key != nil && character >= entry.Key.Location.Start.Character && character <= entry.Key.Location.End.Character {
|
||||
text := handlers.GetAliasHoverInfo(*document.Indexes, *entry)
|
||||
|
||||
return &protocol.Hover{
|
||||
Contents: text,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if entry.Values != nil && character >= entry.Values.Location.Start.Character && character <= entry.Values.Location.End.Character {
|
||||
value := handlers.GetValueAtCursor(character, entry)
|
||||
|
||||
if value == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
contents := []string{}
|
||||
contents = append(contents, handlers.GetAliasValueTypeInfo(*value)...)
|
||||
contents = append(contents, "")
|
||||
contents = append(contents, "#### Value")
|
||||
contents = append(contents, handlers.GetAliasValueHoverInfo(*document.Indexes, *value))
|
||||
|
||||
text := strings.Join(contents, "\n")
|
||||
|
||||
return &protocol.Hover{
|
||||
Contents: text,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
45
handlers/aliases/lsp/text-document-prepare-rename.go
Normal file
45
handlers/aliases/lsp/text-document-prepare-rename.go
Normal file
@ -0,0 +1,45 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/handlers"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentPrepareRename(context *glsp.Context, params *protocol.PrepareRenameParams) (any, error) {
|
||||
d := aliases.DocumentParserMap[params.TextDocument.URI]
|
||||
character := params.Position.Character
|
||||
line := params.Position.Line
|
||||
|
||||
rawEntry, found := d.Parser.Aliases.Get(line)
|
||||
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
entry := rawEntry.(*ast.AliasEntry)
|
||||
|
||||
if character >= entry.Key.Location.Start.Character && character <= entry.Key.Location.End.Character {
|
||||
return entry.Key.Location.ToLSPRange(), nil
|
||||
}
|
||||
|
||||
if entry.Values != nil && character >= entry.Values.Location.Start.Character && character <= entry.Values.Location.End.Character {
|
||||
rawValue := handlers.GetValueAtCursor(character, entry)
|
||||
|
||||
if rawValue == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch (*rawValue).(type) {
|
||||
case ast.AliasValueUser:
|
||||
userValue := (*rawValue).(ast.AliasValueUser)
|
||||
|
||||
return userValue.Location.ToLSPRange(), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
60
handlers/aliases/lsp/text-document-rename.go
Normal file
60
handlers/aliases/lsp/text-document-rename.go
Normal file
@ -0,0 +1,60 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/handlers"
|
||||
"config-lsp/handlers/aliases/indexes"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentRename(context *glsp.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) {
|
||||
d := aliases.DocumentParserMap[params.TextDocument.URI]
|
||||
character := params.Position.Character
|
||||
line := params.Position.Line
|
||||
|
||||
rawEntry, found := d.Parser.Aliases.Get(line)
|
||||
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
entry := rawEntry.(*ast.AliasEntry)
|
||||
|
||||
if character >= entry.Key.Location.Start.Character && character <= entry.Key.Location.End.Character {
|
||||
changes := handlers.RenameAlias(*d.Indexes, entry, params.NewName)
|
||||
|
||||
return &protocol.WorkspaceEdit{
|
||||
Changes: map[protocol.DocumentUri][]protocol.TextEdit{
|
||||
params.TextDocument.URI: changes,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if entry.Values != nil && character >= entry.Values.Location.Start.Character && character <= entry.Values.Location.End.Character {
|
||||
rawValue := handlers.GetValueAtCursor(character, entry)
|
||||
|
||||
if rawValue == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch (*rawValue).(type) {
|
||||
case ast.AliasValueUser:
|
||||
userValue := (*rawValue).(ast.AliasValueUser)
|
||||
|
||||
definitionEntry := d.Indexes.Keys[indexes.NormalizeKey(userValue.Value)]
|
||||
|
||||
changes := handlers.RenameAlias(*d.Indexes, definitionEntry, params.NewName)
|
||||
|
||||
return &protocol.WorkspaceEdit{
|
||||
Changes: map[protocol.DocumentUri][]protocol.TextEdit{
|
||||
params.TextDocument.URI: changes,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
52
handlers/aliases/lsp/text-document-signature-help.go
Normal file
52
handlers/aliases/lsp/text-document-signature-help.go
Normal file
@ -0,0 +1,52 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases"
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/handlers"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentSignatureHelp(context *glsp.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) {
|
||||
document := aliases.DocumentParserMap[params.TextDocument.URI]
|
||||
|
||||
line := params.Position.Line
|
||||
character := params.Position.Character
|
||||
|
||||
if _, found := document.Parser.CommentLines[line]; found {
|
||||
// Comment
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rawEntry, found := document.Parser.Aliases.Get(line)
|
||||
|
||||
if !found {
|
||||
return handlers.GetRootSignatureHelp(0), nil
|
||||
}
|
||||
|
||||
entry := rawEntry.(*ast.AliasEntry)
|
||||
|
||||
if entry.Key != nil && character >= entry.Key.Location.Start.Character && character <= entry.Key.Location.End.Character {
|
||||
return handlers.GetRootSignatureHelp(0), nil
|
||||
}
|
||||
|
||||
if entry.Values != nil && character >= entry.Values.Location.Start.Character && character <= entry.Values.Location.End.Character {
|
||||
value := handlers.GetValueAtCursor(character, entry)
|
||||
|
||||
if value == nil {
|
||||
// For some reason, this does not really work,
|
||||
// When we return all, and then a user value is entered
|
||||
// and the `GetValueSignatureHelp` is called, still the old
|
||||
// signatures with all signature are shown
|
||||
// return handlers.GetAllValuesSignatureHelp(), nil
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return handlers.GetValueSignatureHelp(*value, character), nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
21
handlers/aliases/lsp/workspace-execute-command.go
Normal file
21
handlers/aliases/lsp/workspace-execute-command.go
Normal file
@ -0,0 +1,21 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteCommandParams) (*protocol.ApplyWorkspaceEditParams, error) {
|
||||
// _, command, _ := strings.Cut(params.Command, ".")
|
||||
//
|
||||
// switch command {
|
||||
// case string(handlers.CodeActionInlineAliases):
|
||||
// args := handlers.CodeActionInlineAliasesArgsFromArguments(params.Arguments[0].(map[string]any))
|
||||
//
|
||||
// document := hosts.DocumentParserMap[args.URI]
|
||||
//
|
||||
// return args.RunCommand(*document.Parser)
|
||||
// }
|
||||
|
||||
return nil, nil
|
||||
}
|
48
handlers/aliases/parser/Aliases.interp
Normal file
48
handlers/aliases/parser/Aliases.interp
Normal file
@ -0,0 +1,48 @@
|
||||
token literal names:
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
'@'
|
||||
null
|
||||
'|'
|
||||
':'
|
||||
','
|
||||
'#'
|
||||
'/'
|
||||
null
|
||||
|
||||
token symbolic names:
|
||||
null
|
||||
DIGITS
|
||||
ERROR
|
||||
SEPARATOR
|
||||
AT
|
||||
INCLUDE
|
||||
VERTLINE
|
||||
COLON
|
||||
COMMA
|
||||
NUMBER_SIGN
|
||||
SLASH
|
||||
STRING
|
||||
|
||||
rule names:
|
||||
lineStatement
|
||||
entry
|
||||
separator
|
||||
key
|
||||
values
|
||||
value
|
||||
user
|
||||
file
|
||||
command
|
||||
include
|
||||
comment
|
||||
email
|
||||
error
|
||||
errorCode
|
||||
errorMessage
|
||||
|
||||
|
||||
atn:
|
||||
[4, 1, 11, 131, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 1, 0, 1, 0, 1, 0, 1, 1, 3, 1, 35, 8, 1, 1, 1, 1, 1, 3, 1, 39, 8, 1, 1, 1, 1, 1, 3, 1, 43, 8, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 5, 4, 55, 8, 4, 10, 4, 12, 4, 58, 9, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 68, 8, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 5, 7, 75, 8, 7, 10, 7, 12, 7, 78, 9, 7, 1, 7, 3, 7, 81, 8, 7, 1, 8, 1, 8, 3, 8, 85, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 91, 8, 9, 1, 10, 1, 10, 3, 10, 95, 8, 10, 1, 10, 4, 10, 98, 8, 10, 11, 10, 12, 10, 99, 1, 10, 3, 10, 103, 8, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 3, 12, 112, 8, 12, 1, 12, 3, 12, 115, 8, 12, 1, 12, 3, 12, 118, 8, 12, 1, 13, 1, 13, 1, 14, 1, 14, 5, 14, 124, 8, 14, 10, 14, 12, 14, 127, 9, 14, 1, 14, 1, 14, 1, 14, 0, 0, 15, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 0, 0, 135, 0, 30, 1, 0, 0, 0, 2, 34, 1, 0, 0, 0, 4, 46, 1, 0, 0, 0, 6, 48, 1, 0, 0, 0, 8, 56, 1, 0, 0, 0, 10, 67, 1, 0, 0, 0, 12, 69, 1, 0, 0, 0, 14, 71, 1, 0, 0, 0, 16, 82, 1, 0, 0, 0, 18, 86, 1, 0, 0, 0, 20, 92, 1, 0, 0, 0, 22, 104, 1, 0, 0, 0, 24, 108, 1, 0, 0, 0, 26, 119, 1, 0, 0, 0, 28, 125, 1, 0, 0, 0, 30, 31, 3, 2, 1, 0, 31, 32, 5, 0, 0, 1, 32, 1, 1, 0, 0, 0, 33, 35, 5, 3, 0, 0, 34, 33, 1, 0, 0, 0, 34, 35, 1, 0, 0, 0, 35, 36, 1, 0, 0, 0, 36, 38, 3, 6, 3, 0, 37, 39, 5, 3, 0, 0, 38, 37, 1, 0, 0, 0, 38, 39, 1, 0, 0, 0, 39, 40, 1, 0, 0, 0, 40, 42, 3, 4, 2, 0, 41, 43, 5, 3, 0, 0, 42, 41, 1, 0, 0, 0, 42, 43, 1, 0, 0, 0, 43, 44, 1, 0, 0, 0, 44, 45, 3, 8, 4, 0, 45, 3, 1, 0, 0, 0, 46, 47, 5, 7, 0, 0, 47, 5, 1, 0, 0, 0, 48, 49, 5, 11, 0, 0, 49, 7, 1, 0, 0, 0, 50, 51, 3, 10, 5, 0, 51, 52, 5, 8, 0, 0, 52, 53, 5, 3, 0, 0, 53, 55, 1, 0, 0, 0, 54, 50, 1, 0, 0, 0, 55, 58, 1, 0, 0, 0, 56, 54, 1, 0, 0, 0, 56, 57, 1, 0, 0, 0, 57, 59, 1, 0, 0, 0, 58, 56, 1, 0, 0, 0, 59, 60, 3, 10, 5, 0, 60, 9, 1, 0, 0, 0, 61, 68, 3, 12, 6, 0, 62, 68, 3, 14, 7, 0, 63, 68, 3, 16, 8, 0, 64, 68, 3, 18, 9, 0, 65, 68, 3, 22, 11, 0, 66, 68, 3, 24, 12, 0, 67, 61, 1, 0, 0, 0, 67, 62, 1, 0, 0, 0, 67, 63, 1, 0, 0, 0, 67, 64, 1, 0, 0, 0, 67, 65, 1, 0, 0, 0, 67, 66, 1, 0, 0, 0, 68, 11, 1, 0, 0, 0, 69, 70, 5, 11, 0, 0, 70, 13, 1, 0, 0, 0, 71, 76, 5, 10, 0, 0, 72, 73, 5, 11, 0, 0, 73, 75, 5, 10, 0, 0, 74, 72, 1, 0, 0, 0, 75, 78, 1, 0, 0, 0, 76, 74, 1, 0, 0, 0, 76, 77, 1, 0, 0, 0, 77, 80, 1, 0, 0, 0, 78, 76, 1, 0, 0, 0, 79, 81, 5, 11, 0, 0, 80, 79, 1, 0, 0, 0, 80, 81, 1, 0, 0, 0, 81, 15, 1, 0, 0, 0, 82, 84, 5, 6, 0, 0, 83, 85, 5, 11, 0, 0, 84, 83, 1, 0, 0, 0, 84, 85, 1, 0, 0, 0, 85, 17, 1, 0, 0, 0, 86, 87, 5, 7, 0, 0, 87, 88, 5, 5, 0, 0, 88, 90, 5, 7, 0, 0, 89, 91, 3, 14, 7, 0, 90, 89, 1, 0, 0, 0, 90, 91, 1, 0, 0, 0, 91, 19, 1, 0, 0, 0, 92, 97, 5, 9, 0, 0, 93, 95, 5, 3, 0, 0, 94, 93, 1, 0, 0, 0, 94, 95, 1, 0, 0, 0, 95, 96, 1, 0, 0, 0, 96, 98, 5, 11, 0, 0, 97, 94, 1, 0, 0, 0, 98, 99, 1, 0, 0, 0, 99, 97, 1, 0, 0, 0, 99, 100, 1, 0, 0, 0, 100, 102, 1, 0, 0, 0, 101, 103, 5, 3, 0, 0, 102, 101, 1, 0, 0, 0, 102, 103, 1, 0, 0, 0, 103, 21, 1, 0, 0, 0, 104, 105, 5, 11, 0, 0, 105, 106, 5, 4, 0, 0, 106, 107, 5, 11, 0, 0, 107, 23, 1, 0, 0, 0, 108, 109, 5, 2, 0, 0, 109, 111, 5, 7, 0, 0, 110, 112, 3, 26, 13, 0, 111, 110, 1, 0, 0, 0, 111, 112, 1, 0, 0, 0, 112, 114, 1, 0, 0, 0, 113, 115, 5, 3, 0, 0, 114, 113, 1, 0, 0, 0, 114, 115, 1, 0, 0, 0, 115, 117, 1, 0, 0, 0, 116, 118, 3, 28, 14, 0, 117, 116, 1, 0, 0, 0, 117, 118, 1, 0, 0, 0, 118, 25, 1, 0, 0, 0, 119, 120, 5, 1, 0, 0, 120, 27, 1, 0, 0, 0, 121, 122, 5, 11, 0, 0, 122, 124, 5, 3, 0, 0, 123, 121, 1, 0, 0, 0, 124, 127, 1, 0, 0, 0, 125, 123, 1, 0, 0, 0, 125, 126, 1, 0, 0, 0, 126, 128, 1, 0, 0, 0, 127, 125, 1, 0, 0, 0, 128, 129, 5, 11, 0, 0, 129, 29, 1, 0, 0, 0, 16, 34, 38, 42, 56, 67, 76, 80, 84, 90, 94, 99, 102, 111, 114, 117, 125]
|
17
handlers/aliases/parser/Aliases.tokens
Normal file
17
handlers/aliases/parser/Aliases.tokens
Normal file
@ -0,0 +1,17 @@
|
||||
DIGITS=1
|
||||
ERROR=2
|
||||
SEPARATOR=3
|
||||
AT=4
|
||||
INCLUDE=5
|
||||
VERTLINE=6
|
||||
COLON=7
|
||||
COMMA=8
|
||||
NUMBER_SIGN=9
|
||||
SLASH=10
|
||||
STRING=11
|
||||
'@'=4
|
||||
'|'=6
|
||||
':'=7
|
||||
','=8
|
||||
'#'=9
|
||||
'/'=10
|
50
handlers/aliases/parser/AliasesLexer.interp
Normal file
50
handlers/aliases/parser/AliasesLexer.interp
Normal file
@ -0,0 +1,50 @@
|
||||
token literal names:
|
||||
null
|
||||
null
|
||||
null
|
||||
null
|
||||
'@'
|
||||
null
|
||||
'|'
|
||||
':'
|
||||
','
|
||||
'#'
|
||||
'/'
|
||||
null
|
||||
|
||||
token symbolic names:
|
||||
null
|
||||
DIGITS
|
||||
ERROR
|
||||
SEPARATOR
|
||||
AT
|
||||
INCLUDE
|
||||
VERTLINE
|
||||
COLON
|
||||
COMMA
|
||||
NUMBER_SIGN
|
||||
SLASH
|
||||
STRING
|
||||
|
||||
rule names:
|
||||
DIGITS
|
||||
ERROR
|
||||
SEPARATOR
|
||||
AT
|
||||
INCLUDE
|
||||
VERTLINE
|
||||
COLON
|
||||
COMMA
|
||||
NUMBER_SIGN
|
||||
SLASH
|
||||
STRING
|
||||
|
||||
channel names:
|
||||
DEFAULT_TOKEN_CHANNEL
|
||||
HIDDEN
|
||||
|
||||
mode names:
|
||||
DEFAULT_MODE
|
||||
|
||||
atn:
|
||||
[4, 0, 11, 64, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 1, 0, 4, 0, 25, 8, 0, 11, 0, 12, 0, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 2, 36, 8, 2, 11, 2, 12, 2, 37, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 4, 10, 61, 8, 10, 11, 10, 12, 10, 62, 0, 0, 11, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 1, 0, 3, 1, 0, 48, 57, 2, 0, 9, 9, 32, 32, 9, 0, 9, 10, 13, 13, 32, 32, 35, 35, 44, 44, 47, 47, 58, 58, 64, 64, 124, 124, 66, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 1, 24, 1, 0, 0, 0, 3, 28, 1, 0, 0, 0, 5, 35, 1, 0, 0, 0, 7, 39, 1, 0, 0, 0, 9, 41, 1, 0, 0, 0, 11, 49, 1, 0, 0, 0, 13, 51, 1, 0, 0, 0, 15, 53, 1, 0, 0, 0, 17, 55, 1, 0, 0, 0, 19, 57, 1, 0, 0, 0, 21, 60, 1, 0, 0, 0, 23, 25, 7, 0, 0, 0, 24, 23, 1, 0, 0, 0, 25, 26, 1, 0, 0, 0, 26, 24, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 2, 1, 0, 0, 0, 28, 29, 5, 101, 0, 0, 29, 30, 5, 114, 0, 0, 30, 31, 5, 114, 0, 0, 31, 32, 5, 111, 0, 0, 32, 33, 5, 114, 0, 0, 33, 4, 1, 0, 0, 0, 34, 36, 7, 1, 0, 0, 35, 34, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0, 37, 35, 1, 0, 0, 0, 37, 38, 1, 0, 0, 0, 38, 6, 1, 0, 0, 0, 39, 40, 5, 64, 0, 0, 40, 8, 1, 0, 0, 0, 41, 42, 5, 105, 0, 0, 42, 43, 5, 110, 0, 0, 43, 44, 5, 99, 0, 0, 44, 45, 5, 108, 0, 0, 45, 46, 5, 117, 0, 0, 46, 47, 5, 100, 0, 0, 47, 48, 5, 101, 0, 0, 48, 10, 1, 0, 0, 0, 49, 50, 5, 124, 0, 0, 50, 12, 1, 0, 0, 0, 51, 52, 5, 58, 0, 0, 52, 14, 1, 0, 0, 0, 53, 54, 5, 44, 0, 0, 54, 16, 1, 0, 0, 0, 55, 56, 5, 35, 0, 0, 56, 18, 1, 0, 0, 0, 57, 58, 5, 47, 0, 0, 58, 20, 1, 0, 0, 0, 59, 61, 8, 2, 0, 0, 60, 59, 1, 0, 0, 0, 61, 62, 1, 0, 0, 0, 62, 60, 1, 0, 0, 0, 62, 63, 1, 0, 0, 0, 63, 22, 1, 0, 0, 0, 4, 0, 26, 37, 62, 0]
|
17
handlers/aliases/parser/AliasesLexer.tokens
Normal file
17
handlers/aliases/parser/AliasesLexer.tokens
Normal file
@ -0,0 +1,17 @@
|
||||
DIGITS=1
|
||||
ERROR=2
|
||||
SEPARATOR=3
|
||||
AT=4
|
||||
INCLUDE=5
|
||||
VERTLINE=6
|
||||
COLON=7
|
||||
COMMA=8
|
||||
NUMBER_SIGN=9
|
||||
SLASH=10
|
||||
STRING=11
|
||||
'@'=4
|
||||
'|'=6
|
||||
':'=7
|
||||
','=8
|
||||
'#'=9
|
||||
'/'=10
|
112
handlers/aliases/parser/aliases_base_listener.go
Normal file
112
handlers/aliases/parser/aliases_base_listener.go
Normal file
@ -0,0 +1,112 @@
|
||||
// Code generated from Aliases.g4 by ANTLR 4.13.0. DO NOT EDIT.
|
||||
|
||||
package parser // Aliases
|
||||
|
||||
import "github.com/antlr4-go/antlr/v4"
|
||||
|
||||
// BaseAliasesListener is a complete listener for a parse tree produced by AliasesParser.
|
||||
type BaseAliasesListener struct{}
|
||||
|
||||
var _ AliasesListener = &BaseAliasesListener{}
|
||||
|
||||
// VisitTerminal is called when a terminal node is visited.
|
||||
func (s *BaseAliasesListener) VisitTerminal(node antlr.TerminalNode) {}
|
||||
|
||||
// VisitErrorNode is called when an error node is visited.
|
||||
func (s *BaseAliasesListener) VisitErrorNode(node antlr.ErrorNode) {}
|
||||
|
||||
// EnterEveryRule is called when any rule is entered.
|
||||
func (s *BaseAliasesListener) EnterEveryRule(ctx antlr.ParserRuleContext) {}
|
||||
|
||||
// ExitEveryRule is called when any rule is exited.
|
||||
func (s *BaseAliasesListener) ExitEveryRule(ctx antlr.ParserRuleContext) {}
|
||||
|
||||
// EnterLineStatement is called when production lineStatement is entered.
|
||||
func (s *BaseAliasesListener) EnterLineStatement(ctx *LineStatementContext) {}
|
||||
|
||||
// ExitLineStatement is called when production lineStatement is exited.
|
||||
func (s *BaseAliasesListener) ExitLineStatement(ctx *LineStatementContext) {}
|
||||
|
||||
// EnterEntry is called when production entry is entered.
|
||||
func (s *BaseAliasesListener) EnterEntry(ctx *EntryContext) {}
|
||||
|
||||
// ExitEntry is called when production entry is exited.
|
||||
func (s *BaseAliasesListener) ExitEntry(ctx *EntryContext) {}
|
||||
|
||||
// EnterSeparator is called when production separator is entered.
|
||||
func (s *BaseAliasesListener) EnterSeparator(ctx *SeparatorContext) {}
|
||||
|
||||
// ExitSeparator is called when production separator is exited.
|
||||
func (s *BaseAliasesListener) ExitSeparator(ctx *SeparatorContext) {}
|
||||
|
||||
// EnterKey is called when production key is entered.
|
||||
func (s *BaseAliasesListener) EnterKey(ctx *KeyContext) {}
|
||||
|
||||
// ExitKey is called when production key is exited.
|
||||
func (s *BaseAliasesListener) ExitKey(ctx *KeyContext) {}
|
||||
|
||||
// EnterValues is called when production values is entered.
|
||||
func (s *BaseAliasesListener) EnterValues(ctx *ValuesContext) {}
|
||||
|
||||
// ExitValues is called when production values is exited.
|
||||
func (s *BaseAliasesListener) ExitValues(ctx *ValuesContext) {}
|
||||
|
||||
// EnterValue is called when production value is entered.
|
||||
func (s *BaseAliasesListener) EnterValue(ctx *ValueContext) {}
|
||||
|
||||
// ExitValue is called when production value is exited.
|
||||
func (s *BaseAliasesListener) ExitValue(ctx *ValueContext) {}
|
||||
|
||||
// EnterUser is called when production user is entered.
|
||||
func (s *BaseAliasesListener) EnterUser(ctx *UserContext) {}
|
||||
|
||||
// ExitUser is called when production user is exited.
|
||||
func (s *BaseAliasesListener) ExitUser(ctx *UserContext) {}
|
||||
|
||||
// EnterFile is called when production file is entered.
|
||||
func (s *BaseAliasesListener) EnterFile(ctx *FileContext) {}
|
||||
|
||||
// ExitFile is called when production file is exited.
|
||||
func (s *BaseAliasesListener) ExitFile(ctx *FileContext) {}
|
||||
|
||||
// EnterCommand is called when production command is entered.
|
||||
func (s *BaseAliasesListener) EnterCommand(ctx *CommandContext) {}
|
||||
|
||||
// ExitCommand is called when production command is exited.
|
||||
func (s *BaseAliasesListener) ExitCommand(ctx *CommandContext) {}
|
||||
|
||||
// EnterInclude is called when production include is entered.
|
||||
func (s *BaseAliasesListener) EnterInclude(ctx *IncludeContext) {}
|
||||
|
||||
// ExitInclude is called when production include is exited.
|
||||
func (s *BaseAliasesListener) ExitInclude(ctx *IncludeContext) {}
|
||||
|
||||
// EnterComment is called when production comment is entered.
|
||||
func (s *BaseAliasesListener) EnterComment(ctx *CommentContext) {}
|
||||
|
||||
// ExitComment is called when production comment is exited.
|
||||
func (s *BaseAliasesListener) ExitComment(ctx *CommentContext) {}
|
||||
|
||||
// EnterEmail is called when production email is entered.
|
||||
func (s *BaseAliasesListener) EnterEmail(ctx *EmailContext) {}
|
||||
|
||||
// ExitEmail is called when production email is exited.
|
||||
func (s *BaseAliasesListener) ExitEmail(ctx *EmailContext) {}
|
||||
|
||||
// EnterError is called when production error is entered.
|
||||
func (s *BaseAliasesListener) EnterError(ctx *ErrorContext) {}
|
||||
|
||||
// ExitError is called when production error is exited.
|
||||
func (s *BaseAliasesListener) ExitError(ctx *ErrorContext) {}
|
||||
|
||||
// EnterErrorCode is called when production errorCode is entered.
|
||||
func (s *BaseAliasesListener) EnterErrorCode(ctx *ErrorCodeContext) {}
|
||||
|
||||
// ExitErrorCode is called when production errorCode is exited.
|
||||
func (s *BaseAliasesListener) ExitErrorCode(ctx *ErrorCodeContext) {}
|
||||
|
||||
// EnterErrorMessage is called when production errorMessage is entered.
|
||||
func (s *BaseAliasesListener) EnterErrorMessage(ctx *ErrorMessageContext) {}
|
||||
|
||||
// ExitErrorMessage is called when production errorMessage is exited.
|
||||
func (s *BaseAliasesListener) ExitErrorMessage(ctx *ErrorMessageContext) {}
|
137
handlers/aliases/parser/aliases_lexer.go
Normal file
137
handlers/aliases/parser/aliases_lexer.go
Normal file
@ -0,0 +1,137 @@
|
||||
// Code generated from Aliases.g4 by ANTLR 4.13.0. DO NOT EDIT.
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
"sync"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Suppress unused import error
|
||||
var _ = fmt.Printf
|
||||
var _ = sync.Once{}
|
||||
var _ = unicode.IsLetter
|
||||
|
||||
type AliasesLexer struct {
|
||||
*antlr.BaseLexer
|
||||
channelNames []string
|
||||
modeNames []string
|
||||
// TODO: EOF string
|
||||
}
|
||||
|
||||
var AliasesLexerLexerStaticData struct {
|
||||
once sync.Once
|
||||
serializedATN []int32
|
||||
ChannelNames []string
|
||||
ModeNames []string
|
||||
LiteralNames []string
|
||||
SymbolicNames []string
|
||||
RuleNames []string
|
||||
PredictionContextCache *antlr.PredictionContextCache
|
||||
atn *antlr.ATN
|
||||
decisionToDFA []*antlr.DFA
|
||||
}
|
||||
|
||||
func aliaseslexerLexerInit() {
|
||||
staticData := &AliasesLexerLexerStaticData
|
||||
staticData.ChannelNames = []string{
|
||||
"DEFAULT_TOKEN_CHANNEL", "HIDDEN",
|
||||
}
|
||||
staticData.ModeNames = []string{
|
||||
"DEFAULT_MODE",
|
||||
}
|
||||
staticData.LiteralNames = []string{
|
||||
"", "", "", "", "'@'", "", "'|'", "':'", "','", "'#'", "'/'",
|
||||
}
|
||||
staticData.SymbolicNames = []string{
|
||||
"", "DIGITS", "ERROR", "SEPARATOR", "AT", "INCLUDE", "VERTLINE", "COLON",
|
||||
"COMMA", "NUMBER_SIGN", "SLASH", "STRING",
|
||||
}
|
||||
staticData.RuleNames = []string{
|
||||
"DIGITS", "ERROR", "SEPARATOR", "AT", "INCLUDE", "VERTLINE", "COLON",
|
||||
"COMMA", "NUMBER_SIGN", "SLASH", "STRING",
|
||||
}
|
||||
staticData.PredictionContextCache = antlr.NewPredictionContextCache()
|
||||
staticData.serializedATN = []int32{
|
||||
4, 0, 11, 64, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2,
|
||||
4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2,
|
||||
10, 7, 10, 1, 0, 4, 0, 25, 8, 0, 11, 0, 12, 0, 26, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 2, 4, 2, 36, 8, 2, 11, 2, 12, 2, 37, 1, 3, 1, 3, 1, 4,
|
||||
1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7,
|
||||
1, 7, 1, 8, 1, 8, 1, 9, 1, 9, 1, 10, 4, 10, 61, 8, 10, 11, 10, 12, 10,
|
||||
62, 0, 0, 11, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9,
|
||||
19, 10, 21, 11, 1, 0, 3, 1, 0, 48, 57, 2, 0, 9, 9, 32, 32, 9, 0, 9, 10,
|
||||
13, 13, 32, 32, 35, 35, 44, 44, 47, 47, 58, 58, 64, 64, 124, 124, 66, 0,
|
||||
1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0,
|
||||
9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0,
|
||||
0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 1, 24, 1, 0, 0,
|
||||
0, 3, 28, 1, 0, 0, 0, 5, 35, 1, 0, 0, 0, 7, 39, 1, 0, 0, 0, 9, 41, 1, 0,
|
||||
0, 0, 11, 49, 1, 0, 0, 0, 13, 51, 1, 0, 0, 0, 15, 53, 1, 0, 0, 0, 17, 55,
|
||||
1, 0, 0, 0, 19, 57, 1, 0, 0, 0, 21, 60, 1, 0, 0, 0, 23, 25, 7, 0, 0, 0,
|
||||
24, 23, 1, 0, 0, 0, 25, 26, 1, 0, 0, 0, 26, 24, 1, 0, 0, 0, 26, 27, 1,
|
||||
0, 0, 0, 27, 2, 1, 0, 0, 0, 28, 29, 5, 101, 0, 0, 29, 30, 5, 114, 0, 0,
|
||||
30, 31, 5, 114, 0, 0, 31, 32, 5, 111, 0, 0, 32, 33, 5, 114, 0, 0, 33, 4,
|
||||
1, 0, 0, 0, 34, 36, 7, 1, 0, 0, 35, 34, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0,
|
||||
37, 35, 1, 0, 0, 0, 37, 38, 1, 0, 0, 0, 38, 6, 1, 0, 0, 0, 39, 40, 5, 64,
|
||||
0, 0, 40, 8, 1, 0, 0, 0, 41, 42, 5, 105, 0, 0, 42, 43, 5, 110, 0, 0, 43,
|
||||
44, 5, 99, 0, 0, 44, 45, 5, 108, 0, 0, 45, 46, 5, 117, 0, 0, 46, 47, 5,
|
||||
100, 0, 0, 47, 48, 5, 101, 0, 0, 48, 10, 1, 0, 0, 0, 49, 50, 5, 124, 0,
|
||||
0, 50, 12, 1, 0, 0, 0, 51, 52, 5, 58, 0, 0, 52, 14, 1, 0, 0, 0, 53, 54,
|
||||
5, 44, 0, 0, 54, 16, 1, 0, 0, 0, 55, 56, 5, 35, 0, 0, 56, 18, 1, 0, 0,
|
||||
0, 57, 58, 5, 47, 0, 0, 58, 20, 1, 0, 0, 0, 59, 61, 8, 2, 0, 0, 60, 59,
|
||||
1, 0, 0, 0, 61, 62, 1, 0, 0, 0, 62, 60, 1, 0, 0, 0, 62, 63, 1, 0, 0, 0,
|
||||
63, 22, 1, 0, 0, 0, 4, 0, 26, 37, 62, 0,
|
||||
}
|
||||
deserializer := antlr.NewATNDeserializer(nil)
|
||||
staticData.atn = deserializer.Deserialize(staticData.serializedATN)
|
||||
atn := staticData.atn
|
||||
staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState))
|
||||
decisionToDFA := staticData.decisionToDFA
|
||||
for index, state := range atn.DecisionToState {
|
||||
decisionToDFA[index] = antlr.NewDFA(state, index)
|
||||
}
|
||||
}
|
||||
|
||||
// AliasesLexerInit initializes any static state used to implement AliasesLexer. By default the
|
||||
// static state used to implement the lexer is lazily initialized during the first call to
|
||||
// NewAliasesLexer(). You can call this function if you wish to initialize the static state ahead
|
||||
// of time.
|
||||
func AliasesLexerInit() {
|
||||
staticData := &AliasesLexerLexerStaticData
|
||||
staticData.once.Do(aliaseslexerLexerInit)
|
||||
}
|
||||
|
||||
// NewAliasesLexer produces a new lexer instance for the optional input antlr.CharStream.
|
||||
func NewAliasesLexer(input antlr.CharStream) *AliasesLexer {
|
||||
AliasesLexerInit()
|
||||
l := new(AliasesLexer)
|
||||
l.BaseLexer = antlr.NewBaseLexer(input)
|
||||
staticData := &AliasesLexerLexerStaticData
|
||||
l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache)
|
||||
l.channelNames = staticData.ChannelNames
|
||||
l.modeNames = staticData.ModeNames
|
||||
l.RuleNames = staticData.RuleNames
|
||||
l.LiteralNames = staticData.LiteralNames
|
||||
l.SymbolicNames = staticData.SymbolicNames
|
||||
l.GrammarFileName = "Aliases.g4"
|
||||
// TODO: l.EOF = antlr.TokenEOF
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// AliasesLexer tokens.
|
||||
const (
|
||||
AliasesLexerDIGITS = 1
|
||||
AliasesLexerERROR = 2
|
||||
AliasesLexerSEPARATOR = 3
|
||||
AliasesLexerAT = 4
|
||||
AliasesLexerINCLUDE = 5
|
||||
AliasesLexerVERTLINE = 6
|
||||
AliasesLexerCOLON = 7
|
||||
AliasesLexerCOMMA = 8
|
||||
AliasesLexerNUMBER_SIGN = 9
|
||||
AliasesLexerSLASH = 10
|
||||
AliasesLexerSTRING = 11
|
||||
)
|
100
handlers/aliases/parser/aliases_listener.go
Normal file
100
handlers/aliases/parser/aliases_listener.go
Normal file
@ -0,0 +1,100 @@
|
||||
// Code generated from Aliases.g4 by ANTLR 4.13.0. DO NOT EDIT.
|
||||
|
||||
package parser // Aliases
|
||||
|
||||
import "github.com/antlr4-go/antlr/v4"
|
||||
|
||||
// AliasesListener is a complete listener for a parse tree produced by AliasesParser.
|
||||
type AliasesListener interface {
|
||||
antlr.ParseTreeListener
|
||||
|
||||
// EnterLineStatement is called when entering the lineStatement production.
|
||||
EnterLineStatement(c *LineStatementContext)
|
||||
|
||||
// EnterEntry is called when entering the entry production.
|
||||
EnterEntry(c *EntryContext)
|
||||
|
||||
// EnterSeparator is called when entering the separator production.
|
||||
EnterSeparator(c *SeparatorContext)
|
||||
|
||||
// EnterKey is called when entering the key production.
|
||||
EnterKey(c *KeyContext)
|
||||
|
||||
// EnterValues is called when entering the values production.
|
||||
EnterValues(c *ValuesContext)
|
||||
|
||||
// EnterValue is called when entering the value production.
|
||||
EnterValue(c *ValueContext)
|
||||
|
||||
// EnterUser is called when entering the user production.
|
||||
EnterUser(c *UserContext)
|
||||
|
||||
// EnterFile is called when entering the file production.
|
||||
EnterFile(c *FileContext)
|
||||
|
||||
// EnterCommand is called when entering the command production.
|
||||
EnterCommand(c *CommandContext)
|
||||
|
||||
// EnterInclude is called when entering the include production.
|
||||
EnterInclude(c *IncludeContext)
|
||||
|
||||
// EnterComment is called when entering the comment production.
|
||||
EnterComment(c *CommentContext)
|
||||
|
||||
// EnterEmail is called when entering the email production.
|
||||
EnterEmail(c *EmailContext)
|
||||
|
||||
// EnterError is called when entering the error production.
|
||||
EnterError(c *ErrorContext)
|
||||
|
||||
// EnterErrorCode is called when entering the errorCode production.
|
||||
EnterErrorCode(c *ErrorCodeContext)
|
||||
|
||||
// EnterErrorMessage is called when entering the errorMessage production.
|
||||
EnterErrorMessage(c *ErrorMessageContext)
|
||||
|
||||
// ExitLineStatement is called when exiting the lineStatement production.
|
||||
ExitLineStatement(c *LineStatementContext)
|
||||
|
||||
// ExitEntry is called when exiting the entry production.
|
||||
ExitEntry(c *EntryContext)
|
||||
|
||||
// ExitSeparator is called when exiting the separator production.
|
||||
ExitSeparator(c *SeparatorContext)
|
||||
|
||||
// ExitKey is called when exiting the key production.
|
||||
ExitKey(c *KeyContext)
|
||||
|
||||
// ExitValues is called when exiting the values production.
|
||||
ExitValues(c *ValuesContext)
|
||||
|
||||
// ExitValue is called when exiting the value production.
|
||||
ExitValue(c *ValueContext)
|
||||
|
||||
// ExitUser is called when exiting the user production.
|
||||
ExitUser(c *UserContext)
|
||||
|
||||
// ExitFile is called when exiting the file production.
|
||||
ExitFile(c *FileContext)
|
||||
|
||||
// ExitCommand is called when exiting the command production.
|
||||
ExitCommand(c *CommandContext)
|
||||
|
||||
// ExitInclude is called when exiting the include production.
|
||||
ExitInclude(c *IncludeContext)
|
||||
|
||||
// ExitComment is called when exiting the comment production.
|
||||
ExitComment(c *CommentContext)
|
||||
|
||||
// ExitEmail is called when exiting the email production.
|
||||
ExitEmail(c *EmailContext)
|
||||
|
||||
// ExitError is called when exiting the error production.
|
||||
ExitError(c *ErrorContext)
|
||||
|
||||
// ExitErrorCode is called when exiting the errorCode production.
|
||||
ExitErrorCode(c *ErrorCodeContext)
|
||||
|
||||
// ExitErrorMessage is called when exiting the errorMessage production.
|
||||
ExitErrorMessage(c *ErrorMessageContext)
|
||||
}
|
2414
handlers/aliases/parser/aliases_parser.go
Normal file
2414
handlers/aliases/parser/aliases_parser.go
Normal file
File diff suppressed because it is too large
Load Diff
15
handlers/aliases/shared.go
Normal file
15
handlers/aliases/shared.go
Normal file
@ -0,0 +1,15 @@
|
||||
package aliases
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/aliases/ast"
|
||||
"config-lsp/handlers/aliases/indexes"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
type AliasesDocument struct {
|
||||
Parser *ast.AliasesParser
|
||||
Indexes *indexes.AliasesIndexes
|
||||
}
|
||||
|
||||
var DocumentParserMap = map[protocol.DocumentUri]*AliasesDocument{}
|
12
handlers/aliases/shared/errors.go
Normal file
12
handlers/aliases/shared/errors.go
Normal file
@ -0,0 +1,12 @@
|
||||
package shared
|
||||
|
||||
import "fmt"
|
||||
|
||||
type DuplicateKeyEntry struct {
|
||||
AlreadyFoundAt uint32
|
||||
Key string
|
||||
}
|
||||
|
||||
func (d DuplicateKeyEntry) Error() string {
|
||||
return fmt.Sprintf("Alias '%s' already defined on line %d", d.Key, d.AlreadyFoundAt+1)
|
||||
}
|
@ -13,6 +13,8 @@ var sampleInvalidOptionsExample = `
|
||||
LABEL=test /mnt/test btrfs subvol=backup,fat=32 0 0
|
||||
`
|
||||
|
||||
// TODO: Improve `entries`, sometimes the indexes seem
|
||||
// to be wrong. Use a treemap instead of a map.
|
||||
func TestValidBasicExample(t *testing.T) {
|
||||
// Arrange
|
||||
parser := FstabParser{}
|
||||
|
@ -3,7 +3,6 @@ package fstab
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/utils"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
@ -3,7 +3,6 @@ package fstab
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/utils"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
@ -2,13 +2,13 @@ package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/hosts"
|
||||
"config-lsp/utils"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func Analyze(parser *HostsParser) []protocol.Diagnostic {
|
||||
errors := analyzeEntriesSetCorrectly(*parser)
|
||||
func Analyze(document *hosts.HostsDocument) []protocol.Diagnostic {
|
||||
errors := analyzeEntriesSetCorrectly(*document.Parser)
|
||||
|
||||
if len(errors) > 0 {
|
||||
return utils.Map(
|
||||
@ -19,7 +19,7 @@ func Analyze(parser *HostsParser) []protocol.Diagnostic {
|
||||
)
|
||||
}
|
||||
|
||||
errors = analyzeEntriesAreValid(*parser)
|
||||
errors = analyzeEntriesAreValid(*document.Parser)
|
||||
|
||||
if len(errors) > 0 {
|
||||
return utils.Map(
|
||||
@ -30,8 +30,8 @@ func Analyze(parser *HostsParser) []protocol.Diagnostic {
|
||||
)
|
||||
}
|
||||
|
||||
errors = append(errors, analyzeDoubleIPs(parser)...)
|
||||
errors = append(errors, analyzeDoubleHostNames(parser)...)
|
||||
errors = append(errors, analyzeDoubleIPs(document)...)
|
||||
errors = append(errors, analyzeDoubleHostNames(document)...)
|
||||
|
||||
return utils.Map(
|
||||
errors,
|
51
handlers/hosts/analyzer/double_ips.go
Normal file
51
handlers/hosts/analyzer/double_ips.go
Normal file
@ -0,0 +1,51 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/hosts"
|
||||
"config-lsp/handlers/hosts/shared"
|
||||
"config-lsp/utils"
|
||||
"net"
|
||||
"slices"
|
||||
)
|
||||
|
||||
func ipToString(ip net.IPAddr) string {
|
||||
return ip.IP.String()
|
||||
}
|
||||
|
||||
func analyzeDoubleIPs(d *hosts.HostsDocument) []common.LSPError {
|
||||
errors := make([]common.LSPError, 0)
|
||||
ips := make(map[string]uint32)
|
||||
|
||||
d.Indexes.DoubleIPs = make(map[uint32]shared.DuplicateIPDeclaration)
|
||||
|
||||
// TODO: `range` does not seem to properly
|
||||
// iterate in a sorted way.
|
||||
// Instead, use a treemap
|
||||
lines := utils.KeysOfMap(d.Parser.Tree.Entries)
|
||||
slices.Sort(lines)
|
||||
|
||||
for _, lineNumber := range lines {
|
||||
entry := d.Parser.Tree.Entries[lineNumber]
|
||||
|
||||
if entry.IPAddress != nil {
|
||||
key := ipToString(entry.IPAddress.Value)
|
||||
|
||||
if foundLine, found := ips[key]; found {
|
||||
err := shared.DuplicateIPDeclaration{
|
||||
AlreadyFoundAt: foundLine,
|
||||
}
|
||||
|
||||
d.Indexes.DoubleIPs[lineNumber] = err
|
||||
errors = append(errors, common.LSPError{
|
||||
Range: entry.IPAddress.Location,
|
||||
Err: err,
|
||||
})
|
||||
} else {
|
||||
ips[key] = lineNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts"
|
||||
"config-lsp/handlers/hosts/ast"
|
||||
"config-lsp/handlers/hosts/indexes"
|
||||
"config-lsp/handlers/hosts/shared"
|
||||
"config-lsp/utils"
|
||||
"testing"
|
||||
)
|
||||
@ -15,14 +19,19 @@ func TestWorksWithNonDoubleIPs(
|
||||
1.2.3.6 bar.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
parser := ast.NewHostsParser()
|
||||
i := indexes.NewHostsIndexes()
|
||||
document := hosts.HostsDocument{
|
||||
Parser: &parser,
|
||||
Indexes: &i,
|
||||
}
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors)
|
||||
}
|
||||
|
||||
errors = analyzeDoubleIPs(&parser)
|
||||
errors = analyzeDoubleIPs(&document)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Errorf("Expected no errors, but got %v", errors)
|
||||
@ -38,14 +47,19 @@ func TestWorksWithDoubleIPs(
|
||||
1.2.3.4 foo.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
parser := ast.NewHostsParser()
|
||||
i := indexes.NewHostsIndexes()
|
||||
document := hosts.HostsDocument{
|
||||
Parser: &parser,
|
||||
Indexes: &i,
|
||||
}
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("PARER FAILED! Expected no errors, but got %v", errors)
|
||||
}
|
||||
|
||||
errors = analyzeDoubleIPs(&parser)
|
||||
errors = analyzeDoubleIPs(&document)
|
||||
|
||||
if !(len(errors) == 1) {
|
||||
t.Errorf("Expected 1 error, but got %v", len(errors))
|
||||
@ -55,7 +69,7 @@ func TestWorksWithDoubleIPs(
|
||||
t.Errorf("Expected error on line 2, but got %v", errors[0].Range.Start.Line)
|
||||
}
|
||||
|
||||
if !(errors[0].Err.(DuplicateIPDeclaration).AlreadyFoundAt == 0) {
|
||||
t.Errorf("Expected error on line 0, but got %v", errors[0].Err.(DuplicateIPDeclaration).AlreadyFoundAt)
|
||||
if !(errors[0].Err.(shared.DuplicateIPDeclaration).AlreadyFoundAt == 0) {
|
||||
t.Errorf("Expected error on line 0, but got %v", errors[0].Err.(shared.DuplicateIPDeclaration).AlreadyFoundAt)
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/ast"
|
||||
"config-lsp/utils"
|
||||
"net"
|
||||
"testing"
|
||||
@ -13,7 +14,7 @@ func TestValidSimpleExampleWorks(
|
||||
1.2.3.4 hello.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
parser := ast.NewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
@ -80,7 +81,7 @@ func TestValidComplexExampleWorks(
|
||||
1.2.3.4 example.com check.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
parser := ast.NewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
@ -115,7 +116,7 @@ func TestInvalidExampleWorks(
|
||||
1.2.3.4
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
parser := ast.NewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) == 0 {
|
@ -2,33 +2,19 @@ package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/hosts"
|
||||
"config-lsp/handlers/hosts/ast"
|
||||
"config-lsp/handlers/hosts/indexes"
|
||||
"config-lsp/handlers/hosts/shared"
|
||||
"config-lsp/utils"
|
||||
"net"
|
||||
)
|
||||
|
||||
type ResolverEntry struct {
|
||||
IPv4Address net.IP
|
||||
IPv6Address net.IP
|
||||
Line uint32
|
||||
}
|
||||
|
||||
func (e ResolverEntry) GetInfo() string {
|
||||
if e.IPv4Address != nil {
|
||||
return e.IPv4Address.String()
|
||||
}
|
||||
|
||||
return e.IPv6Address.String()
|
||||
}
|
||||
|
||||
type Resolver struct {
|
||||
Entries map[string]ResolverEntry
|
||||
}
|
||||
|
||||
func createEntry(
|
||||
line uint32,
|
||||
ip net.IP,
|
||||
) ResolverEntry {
|
||||
entry := ResolverEntry{
|
||||
) indexes.ResolverEntry {
|
||||
entry := indexes.ResolverEntry{
|
||||
Line: line,
|
||||
}
|
||||
|
||||
@ -46,10 +32,10 @@ type hostnameEntry struct {
|
||||
HostName string
|
||||
}
|
||||
|
||||
func createResolverFromParser(p HostsParser) (Resolver, []common.LSPError) {
|
||||
func createResolverFromParser(p ast.HostsParser) (indexes.Resolver, []common.LSPError) {
|
||||
errors := make([]common.LSPError, 0)
|
||||
resolver := Resolver{
|
||||
Entries: make(map[string]ResolverEntry),
|
||||
resolver := indexes.Resolver{
|
||||
Entries: make(map[string]indexes.ResolverEntry),
|
||||
}
|
||||
|
||||
for lineNumber, entry := range p.Tree.Entries {
|
||||
@ -63,7 +49,7 @@ func createResolverFromParser(p HostsParser) (Resolver, []common.LSPError) {
|
||||
},
|
||||
utils.Map(
|
||||
entry.Aliases,
|
||||
func(alias *HostsHostname) hostnameEntry {
|
||||
func(alias *ast.HostsHostname) hostnameEntry {
|
||||
return hostnameEntry{
|
||||
Location: alias.Location,
|
||||
HostName: alias.Value,
|
||||
@ -83,7 +69,7 @@ func createResolverFromParser(p HostsParser) (Resolver, []common.LSPError) {
|
||||
errors,
|
||||
common.LSPError{
|
||||
Range: hostName.Location,
|
||||
Err: DuplicateHostEntry{
|
||||
Err: shared.DuplicateHostEntry{
|
||||
AlreadyFoundAt: resolv.Line,
|
||||
Hostname: hostName.HostName,
|
||||
},
|
||||
@ -99,10 +85,10 @@ func createResolverFromParser(p HostsParser) (Resolver, []common.LSPError) {
|
||||
return resolver, errors
|
||||
}
|
||||
|
||||
func analyzeDoubleHostNames(p *HostsParser) []common.LSPError {
|
||||
resolver, errors := createResolverFromParser(*p)
|
||||
func analyzeDoubleHostNames(d *hosts.HostsDocument) []common.LSPError {
|
||||
resolver, errors := createResolverFromParser(*d.Parser)
|
||||
|
||||
p.Resolver = &resolver
|
||||
d.Indexes.Resolver = &resolver
|
||||
|
||||
return errors
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/ast"
|
||||
"config-lsp/utils"
|
||||
"testing"
|
||||
)
|
||||
@ -13,7 +14,7 @@ func TestResolverEntriesWorksWithNonOverlapping(
|
||||
5.5.5.5 world.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
parser := ast.NewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
@ -55,7 +56,7 @@ func TestResolverEntriesWithSimpleOverlapping(
|
||||
5.5.5.5 hello.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
parser := ast.NewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
@ -85,7 +86,7 @@ func TestResolverEntriesWithComplexOverlapping(
|
||||
5.5.5.5 check.com test.com
|
||||
`)
|
||||
|
||||
parser := CreateNewHostsParser()
|
||||
parser := ast.NewHostsParser()
|
||||
errors := parser.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
@ -3,13 +3,14 @@ package analyzer
|
||||
import (
|
||||
"config-lsp/common"
|
||||
docvalues "config-lsp/doc-values"
|
||||
"config-lsp/handlers/hosts/ast"
|
||||
"config-lsp/handlers/hosts/fields"
|
||||
"config-lsp/utils"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func analyzeEntriesSetCorrectly(
|
||||
parser HostsParser,
|
||||
parser ast.HostsParser,
|
||||
) []common.LSPError {
|
||||
err := make([]common.LSPError, 0)
|
||||
|
||||
@ -35,7 +36,7 @@ func analyzeEntriesSetCorrectly(
|
||||
}
|
||||
|
||||
func analyzeEntriesAreValid(
|
||||
parser HostsParser,
|
||||
parser ast.HostsParser,
|
||||
) []common.LSPError {
|
||||
err := make([]common.LSPError, 0)
|
||||
|
@ -1,38 +1,14 @@
|
||||
package analyzer
|
||||
package ast
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
)
|
||||
|
||||
func characterRangeFromCtx(
|
||||
ctx antlr.BaseParserRuleContext,
|
||||
) common.LocationRange {
|
||||
line := uint32(ctx.GetStart().GetLine())
|
||||
start := uint32(ctx.GetStart().GetStart())
|
||||
end := uint32(ctx.GetStop().GetStop())
|
||||
|
||||
return common.LocationRange{
|
||||
Start: common.Location{
|
||||
Line: line,
|
||||
Character: start,
|
||||
},
|
||||
End: common.Location{
|
||||
Line: line,
|
||||
Character: end + 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type HostsParser struct {
|
||||
Tree HostsTree
|
||||
CommentLines map[uint32]struct{}
|
||||
Resolver *Resolver
|
||||
// [line]error
|
||||
DoubleIPs map[uint32]DuplicateIPDeclaration
|
||||
}
|
||||
|
||||
type HostsTree struct {
|
@ -1,9 +1,9 @@
|
||||
package analyzer
|
||||
package ast
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
docvalues "config-lsp/doc-values"
|
||||
"config-lsp/handlers/hosts/parser"
|
||||
parser2 "config-lsp/handlers/hosts/ast/parser"
|
||||
"net"
|
||||
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
@ -14,19 +14,19 @@ type hostsListenerContext struct {
|
||||
}
|
||||
|
||||
type hostsParserListener struct {
|
||||
*parser.BaseHostsListener
|
||||
*parser2.BaseHostsListener
|
||||
Parser *HostsParser
|
||||
Errors []common.LSPError
|
||||
hostsContext hostsListenerContext
|
||||
}
|
||||
|
||||
func (s *hostsParserListener) EnterComment(ctx *parser.CommentContext) {
|
||||
func (s *hostsParserListener) EnterComment(ctx *parser2.CommentContext) {
|
||||
line := uint32(s.hostsContext.line)
|
||||
s.Parser.CommentLines[line] = struct{}{}
|
||||
}
|
||||
|
||||
func (s *hostsParserListener) EnterEntry(ctx *parser.EntryContext) {
|
||||
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
func (s *hostsParserListener) EnterEntry(ctx *parser2.EntryContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.hostsContext.line)
|
||||
|
||||
s.Parser.Tree.Entries[location.Start.Line] = &HostsEntry{
|
||||
@ -34,8 +34,8 @@ func (s *hostsParserListener) EnterEntry(ctx *parser.EntryContext) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *hostsParserListener) EnterIpAddress(ctx *parser.IpAddressContext) {
|
||||
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
func (s *hostsParserListener) EnterIpAddress(ctx *parser2.IpAddressContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.hostsContext.line)
|
||||
|
||||
ip := net.ParseIP(ctx.GetText())
|
||||
@ -65,8 +65,8 @@ func (s *hostsParserListener) EnterIpAddress(ctx *parser.IpAddressContext) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *hostsParserListener) EnterHostname(ctx *parser.HostnameContext) {
|
||||
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
func (s *hostsParserListener) EnterHostname(ctx *parser2.HostnameContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.hostsContext.line)
|
||||
|
||||
entry := s.Parser.Tree.Entries[location.Start.Line]
|
||||
@ -79,8 +79,8 @@ func (s *hostsParserListener) EnterHostname(ctx *parser.HostnameContext) {
|
||||
s.Parser.Tree.Entries[location.Start.Line] = entry
|
||||
}
|
||||
|
||||
func (s *hostsParserListener) EnterAliases(ctx *parser.AliasesContext) {
|
||||
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
func (s *hostsParserListener) EnterAliases(ctx *parser2.AliasesContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.hostsContext.line)
|
||||
|
||||
entry := s.Parser.Tree.Entries[location.Start.Line]
|
||||
@ -90,8 +90,8 @@ func (s *hostsParserListener) EnterAliases(ctx *parser.AliasesContext) {
|
||||
entry.Aliases = aliases
|
||||
}
|
||||
|
||||
func (s *hostsParserListener) EnterAlias(ctx *parser.AliasContext) {
|
||||
location := characterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
func (s *hostsParserListener) EnterAlias(ctx *parser2.AliasContext) {
|
||||
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
|
||||
location.ChangeBothLines(s.hostsContext.line)
|
||||
|
||||
entry := s.Parser.Tree.Entries[location.Start.Line]
|
||||
@ -123,17 +123,6 @@ type errorListener struct {
|
||||
hostsContext hostsListenerContext
|
||||
}
|
||||
|
||||
func createErrorListener(
|
||||
line uint32,
|
||||
) errorListener {
|
||||
return errorListener{
|
||||
Errors: make([]common.LSPError, 0),
|
||||
hostsContext: hostsListenerContext{
|
||||
line: line,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *errorListener) SyntaxError(
|
||||
recognizer antlr.Recognizer,
|
||||
offendingSymbol interface{},
|
||||
@ -150,3 +139,14 @@ func (d *errorListener) SyntaxError(
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func createErrorListener(
|
||||
line uint32,
|
||||
) errorListener {
|
||||
return errorListener{
|
||||
Errors: make([]common.LSPError, 0),
|
||||
hostsContext: hostsListenerContext{
|
||||
line: line,
|
||||
},
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package analyzer
|
||||
package ast
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/hosts/parser"
|
||||
parser2 "config-lsp/handlers/hosts/ast/parser"
|
||||
"config-lsp/utils"
|
||||
"regexp"
|
||||
|
||||
@ -26,7 +26,7 @@ func (p *HostsParser) parseStatement(
|
||||
stream := antlr.NewInputStream(input)
|
||||
|
||||
errorListener := createErrorListener(line)
|
||||
lexer := parser.NewHostsLexer(stream)
|
||||
lexer := parser2.NewHostsLexer(stream)
|
||||
lexer.RemoveErrorListeners()
|
||||
lexer.AddErrorListener(&errorListener)
|
||||
|
||||
@ -34,7 +34,7 @@ func (p *HostsParser) parseStatement(
|
||||
|
||||
errorListener = createErrorListener(line)
|
||||
tokenStream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
|
||||
antlrParser := parser.NewHostsParser(tokenStream)
|
||||
antlrParser := parser2.NewHostsParser(tokenStream)
|
||||
antlrParser.RemoveErrorListeners()
|
||||
antlrParser.AddErrorListener(&errorListener)
|
||||
|
||||
@ -74,7 +74,7 @@ func (p *HostsParser) Parse(input string) []common.LSPError {
|
||||
return errors
|
||||
}
|
||||
|
||||
func CreateNewHostsParser() HostsParser {
|
||||
func NewHostsParser() HostsParser {
|
||||
p := HostsParser{}
|
||||
p.Clear()
|
||||
|
@ -1,39 +0,0 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"net"
|
||||
)
|
||||
|
||||
func ipToString(ip net.IPAddr) string {
|
||||
return ip.IP.String()
|
||||
}
|
||||
|
||||
func analyzeDoubleIPs(p *HostsParser) []common.LSPError {
|
||||
errors := make([]common.LSPError, 0)
|
||||
ips := make(map[string]uint32)
|
||||
|
||||
p.DoubleIPs = make(map[uint32]DuplicateIPDeclaration)
|
||||
|
||||
for lineNumber, entry := range p.Tree.Entries {
|
||||
if entry.IPAddress != nil {
|
||||
key := ipToString(entry.IPAddress.Value)
|
||||
|
||||
if foundLine, found := ips[key]; found {
|
||||
err := DuplicateIPDeclaration{
|
||||
AlreadyFoundAt: foundLine,
|
||||
}
|
||||
|
||||
p.DoubleIPs[lineNumber] = err
|
||||
errors = append(errors, common.LSPError{
|
||||
Range: entry.IPAddress.Location,
|
||||
Err: err,
|
||||
})
|
||||
} else {
|
||||
ips[key] = lineNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
"config-lsp/handlers/hosts/ast"
|
||||
"config-lsp/utils"
|
||||
"fmt"
|
||||
"strings"
|
||||
@ -16,7 +16,7 @@ const (
|
||||
)
|
||||
|
||||
type CodeAction interface {
|
||||
RunCommand(analyzer.HostsParser) (*protocol.ApplyWorkspaceEditParams, error)
|
||||
RunCommand(ast.HostsParser) (*protocol.ApplyWorkspaceEditParams, error)
|
||||
}
|
||||
|
||||
type CodeActionArgs interface{}
|
||||
@ -35,7 +35,7 @@ func CodeActionInlineAliasesArgsFromArguments(arguments map[string]any) CodeActi
|
||||
}
|
||||
}
|
||||
|
||||
func (args CodeActionInlineAliasesArgs) RunCommand(hostsParser analyzer.HostsParser) (*protocol.ApplyWorkspaceEditParams, error) {
|
||||
func (args CodeActionInlineAliasesArgs) RunCommand(hostsParser ast.HostsParser) (*protocol.ApplyWorkspaceEditParams, error) {
|
||||
fromEntry := hostsParser.Tree.Entries[args.FromLine]
|
||||
toEntry := hostsParser.Tree.Entries[args.ToLine]
|
||||
|
||||
@ -58,7 +58,7 @@ func (args CodeActionInlineAliasesArgs) RunCommand(hostsParser analyzer.HostsPar
|
||||
},
|
||||
utils.Map(
|
||||
fromEntry.Aliases,
|
||||
func(alias *analyzer.HostsHostname) string {
|
||||
func(alias *ast.HostsHostname) string {
|
||||
return alias.Value
|
||||
},
|
||||
)...,
|
||||
|
@ -1,17 +1,17 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
"config-lsp/handlers/hosts"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func GetInlineAliasesCodeAction(
|
||||
p analyzer.HostsParser,
|
||||
d hosts.HostsDocument,
|
||||
params *protocol.CodeActionParams,
|
||||
) []protocol.CodeAction {
|
||||
line := params.Range.Start.Line
|
||||
|
||||
if duplicateInfo, found := p.DoubleIPs[line]; found {
|
||||
if duplicateInfo, found := d.Indexes.DoubleIPs[line]; found {
|
||||
commandID := "hosts." + CodeActionInlineAliases
|
||||
command := protocol.Command{
|
||||
Title: "Inline Aliases",
|
||||
|
@ -1,7 +1,8 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
"config-lsp/handlers/hosts"
|
||||
"config-lsp/handlers/hosts/ast"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
@ -14,7 +15,7 @@ const (
|
||||
)
|
||||
|
||||
func GetHoverTargetInEntry(
|
||||
e analyzer.HostsEntry,
|
||||
e ast.HostsEntry,
|
||||
cursor uint32,
|
||||
) *HoverTarget {
|
||||
if e.IPAddress != nil && e.IPAddress.Location.ContainsCursorByCharacter(cursor) {
|
||||
@ -38,11 +39,11 @@ func GetHoverTargetInEntry(
|
||||
}
|
||||
|
||||
func GetHoverInfoForHostname(
|
||||
parser analyzer.HostsParser,
|
||||
hostname analyzer.HostsHostname,
|
||||
d hosts.HostsDocument,
|
||||
hostname ast.HostsHostname,
|
||||
cursor uint32,
|
||||
) []string {
|
||||
ipAddress := parser.Resolver.Entries[hostname.Value]
|
||||
ipAddress := d.Indexes.Resolver.Entries[hostname.Value]
|
||||
|
||||
return []string{
|
||||
fmt.Sprintf("**%s** maps to _%s_", hostname.Value, ipAddress.GetInfo()),
|
||||
|
17
handlers/hosts/indexes/indexes.go
Normal file
17
handlers/hosts/indexes/indexes.go
Normal file
@ -0,0 +1,17 @@
|
||||
package indexes
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/shared"
|
||||
)
|
||||
|
||||
type HostsIndexes struct {
|
||||
Resolver *Resolver
|
||||
// [line]error
|
||||
DoubleIPs map[uint32]shared.DuplicateIPDeclaration
|
||||
}
|
||||
|
||||
func NewHostsIndexes() HostsIndexes {
|
||||
return HostsIndexes{
|
||||
DoubleIPs: make(map[uint32]shared.DuplicateIPDeclaration),
|
||||
}
|
||||
}
|
23
handlers/hosts/indexes/resolver.go
Normal file
23
handlers/hosts/indexes/resolver.go
Normal file
@ -0,0 +1,23 @@
|
||||
package indexes
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type ResolverEntry struct {
|
||||
IPv4Address net.IP
|
||||
IPv6Address net.IP
|
||||
Line uint32
|
||||
}
|
||||
|
||||
func (e ResolverEntry) GetInfo() string {
|
||||
if e.IPv4Address != nil {
|
||||
return e.IPv4Address.String()
|
||||
}
|
||||
|
||||
return e.IPv6Address.String()
|
||||
}
|
||||
|
||||
type Resolver struct {
|
||||
Entries map[string]ResolverEntry
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
var documentParserMap = map[protocol.DocumentUri]*analyzer.HostsParser{}
|
@ -1,18 +1,18 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts"
|
||||
"config-lsp/handlers/hosts/handlers"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
|
||||
parser := documentParserMap[params.TextDocument.URI]
|
||||
document := hosts.DocumentParserMap[params.TextDocument.URI]
|
||||
|
||||
actions := make([]protocol.CodeAction, 0, 1)
|
||||
|
||||
actions = append(actions, handlers.GetInlineAliasesCodeAction(*parser, params)...)
|
||||
actions = append(actions, handlers.GetInlineAliasesCodeAction(*document, params)...)
|
||||
|
||||
if len(actions) > 0 {
|
||||
return actions, nil
|
||||
|
@ -2,9 +2,9 @@ package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
"config-lsp/handlers/hosts"
|
||||
"config-lsp/handlers/hosts/analyzer"
|
||||
"config-lsp/utils"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
@ -16,11 +16,11 @@ func TextDocumentDidChange(
|
||||
content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text
|
||||
common.ClearDiagnostics(context, params.TextDocument.URI)
|
||||
|
||||
parser := documentParserMap[params.TextDocument.URI]
|
||||
parser.Clear()
|
||||
document := hosts.DocumentParserMap[params.TextDocument.URI]
|
||||
document.Parser.Clear()
|
||||
|
||||
diagnostics := make([]protocol.Diagnostic, 0)
|
||||
errors := parser.Parse(content)
|
||||
errors := document.Parser.Parse(content)
|
||||
|
||||
if len(errors) > 0 {
|
||||
diagnostics = append(diagnostics, utils.Map(
|
||||
@ -31,7 +31,7 @@ func TextDocumentDidChange(
|
||||
)...)
|
||||
}
|
||||
|
||||
diagnostics = append(diagnostics, analyzer.Analyze(parser)...)
|
||||
diagnostics = append(diagnostics, analyzer.Analyze(document)...)
|
||||
|
||||
if len(diagnostics) > 0 {
|
||||
common.SendDiagnostics(context, params.TextDocument.URI, diagnostics)
|
||||
|
@ -1,12 +1,13 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts"
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func TextDocumentDidClose(context *glsp.Context, params *protocol.DidCloseTextDocumentParams) error {
|
||||
delete(documentParserMap, params.TextDocument.URI)
|
||||
delete(hosts.DocumentParserMap, params.TextDocument.URI)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -2,9 +2,11 @@ package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
"config-lsp/handlers/hosts"
|
||||
"config-lsp/handlers/hosts/analyzer"
|
||||
"config-lsp/handlers/hosts/ast"
|
||||
"config-lsp/handlers/hosts/indexes"
|
||||
"config-lsp/utils"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
@ -15,8 +17,13 @@ func TextDocumentDidOpen(
|
||||
) error {
|
||||
common.ClearDiagnostics(context, params.TextDocument.URI)
|
||||
|
||||
parser := analyzer.CreateNewHostsParser()
|
||||
documentParserMap[params.TextDocument.URI] = &parser
|
||||
parser := ast.NewHostsParser()
|
||||
i := indexes.NewHostsIndexes()
|
||||
document := hosts.HostsDocument{
|
||||
Parser: &parser,
|
||||
Indexes: &i,
|
||||
}
|
||||
hosts.DocumentParserMap[params.TextDocument.URI] = &document
|
||||
|
||||
errors := parser.Parse(params.TextDocument.Text)
|
||||
|
||||
@ -29,7 +36,7 @@ func TextDocumentDidOpen(
|
||||
|
||||
diagnostics = append(
|
||||
diagnostics,
|
||||
analyzer.Analyze(&parser)...,
|
||||
analyzer.Analyze(&document)...,
|
||||
)
|
||||
|
||||
if len(diagnostics) > 0 {
|
||||
|
@ -1,9 +1,10 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts"
|
||||
"config-lsp/handlers/hosts/ast"
|
||||
"config-lsp/handlers/hosts/fields"
|
||||
"config-lsp/handlers/hosts/handlers"
|
||||
"config-lsp/handlers/hosts/handlers/analyzer"
|
||||
"strings"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
@ -14,17 +15,17 @@ func TextDocumentHover(
|
||||
context *glsp.Context,
|
||||
params *protocol.HoverParams,
|
||||
) (*protocol.Hover, error) {
|
||||
parser := documentParserMap[params.TextDocument.URI]
|
||||
document := hosts.DocumentParserMap[params.TextDocument.URI]
|
||||
|
||||
line := params.Position.Line
|
||||
character := params.Position.Character
|
||||
|
||||
if _, found := parser.CommentLines[line]; found {
|
||||
if _, found := document.Parser.CommentLines[line]; found {
|
||||
// Comment
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
entry, found := parser.Tree.Entries[line]
|
||||
entry, found := document.Parser.Tree.Entries[line]
|
||||
|
||||
if !found {
|
||||
// Empty line
|
||||
@ -33,7 +34,7 @@ func TextDocumentHover(
|
||||
|
||||
target := handlers.GetHoverTargetInEntry(*entry, character)
|
||||
|
||||
var hostname *analyzer.HostsHostname
|
||||
var hostname *ast.HostsHostname
|
||||
|
||||
switch *target {
|
||||
case handlers.HoverTargetIPAddress:
|
||||
@ -74,7 +75,7 @@ func TextDocumentHover(
|
||||
)
|
||||
contents = append(
|
||||
contents,
|
||||
handlers.GetHoverInfoForHostname(*parser, *hostname, character)...,
|
||||
handlers.GetHoverInfoForHostname(*document, *hostname, character)...,
|
||||
)
|
||||
|
||||
return &protocol.Hover{
|
||||
|
@ -1,6 +1,7 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts"
|
||||
"config-lsp/handlers/hosts/handlers"
|
||||
"strings"
|
||||
|
||||
@ -15,9 +16,9 @@ func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteComm
|
||||
case string(handlers.CodeActionInlineAliases):
|
||||
args := handlers.CodeActionInlineAliasesArgsFromArguments(params.Arguments[0].(map[string]any))
|
||||
|
||||
parser := documentParserMap[args.URI]
|
||||
document := hosts.DocumentParserMap[args.URI]
|
||||
|
||||
return args.RunCommand(*parser)
|
||||
return args.RunCommand(*document.Parser)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
14
handlers/hosts/shared.go
Normal file
14
handlers/hosts/shared.go
Normal file
@ -0,0 +1,14 @@
|
||||
package hosts
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/hosts/ast"
|
||||
"config-lsp/handlers/hosts/indexes"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
type HostsDocument struct {
|
||||
Parser *ast.HostsParser
|
||||
Indexes *indexes.HostsIndexes
|
||||
}
|
||||
|
||||
var DocumentParserMap = map[protocol.DocumentUri]*HostsDocument{}
|
@ -1,4 +1,4 @@
|
||||
package analyzer
|
||||
package shared
|
||||
|
||||
import "fmt"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package common
|
||||
package openssh
|
||||
|
||||
import (
|
||||
docvalues "config-lsp/doc-values"
|
@ -1,8 +1,6 @@
|
||||
package openssh
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
@ -10,18 +8,18 @@ import (
|
||||
func DiagnoseSSHOptions(
|
||||
context *glsp.Context,
|
||||
documentURI protocol.DocumentUri,
|
||||
parser *common.SimpleConfigParser,
|
||||
parser *SimpleConfigParser,
|
||||
) []protocol.Diagnostic {
|
||||
diagnostics := make([]protocol.Diagnostic, 0)
|
||||
|
||||
diagnostics = append(
|
||||
diagnostics,
|
||||
common.DiagnoseOption(
|
||||
DiagnoseOption(
|
||||
context,
|
||||
documentURI,
|
||||
parser,
|
||||
"Port",
|
||||
func(value string, position common.SimpleConfigPosition) []protocol.Diagnostic {
|
||||
func(value string, position SimpleConfigPosition) []protocol.Diagnostic {
|
||||
if value == "22" {
|
||||
severity := protocol.DiagnosticSeverityWarning
|
||||
|
||||
@ -50,3 +48,20 @@ func DiagnoseSSHOptions(
|
||||
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
func DiagnoseOption(
|
||||
context *glsp.Context,
|
||||
uri protocol.DocumentUri,
|
||||
parser *SimpleConfigParser,
|
||||
optionName string,
|
||||
checkerFunc func(string, SimpleConfigPosition) []protocol.Diagnostic,
|
||||
) []protocol.Diagnostic {
|
||||
option, err := parser.GetOption(optionName)
|
||||
|
||||
if err != nil {
|
||||
// Nothing to diagnose
|
||||
return nil
|
||||
}
|
||||
|
||||
return checkerFunc(option.Value, option.Position)
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user