Merge pull request #17 from Myzel394/improve-fstab

Improve fstab
This commit is contained in:
Myzel394 2024-10-14 10:50:18 +02:00 committed by GitHub
commit 418c549a15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 3142 additions and 691 deletions

View File

@ -1,13 +1,17 @@
package parser
import "strings"
type ParseFeatures struct {
ParseDoubleQuotes bool
ParseEscapedCharacters bool
Replacements *map[string]string
}
var FullFeatures = ParseFeatures{
ParseDoubleQuotes: true,
ParseEscapedCharacters: true,
Replacements: &map[string]string{},
}
type ParsedString struct {
@ -21,6 +25,10 @@ func ParseRawString(
) ParsedString {
value := raw
if len(*features.Replacements) > 0 {
value = ParseReplacements(value, *features.Replacements)
}
// Parse double quotes
if features.ParseDoubleQuotes {
value = ParseDoubleQuotes(value)
@ -85,6 +93,19 @@ func ParseEscapedCharacters(
return value
}
func ParseReplacements(
raw string,
replacements map[string]string,
) string {
value := raw
for key, replacement := range replacements {
value = strings.ReplaceAll(value, key, replacement)
}
return value
}
func modifyString(
input string,
start int,

View File

@ -165,3 +165,25 @@ func TestStringsIncompleteQuotes3FullFeatures(
t.Errorf("Expected %v, got %v", expected, actual)
}
}
func TestStringsReplacements(
t *testing.T,
) {
input := `Hello\\040World`
expected := ParsedString{
Raw: input,
Value: `Hello World`,
}
actual := ParseRawString(input, ParseFeatures{
ParseDoubleQuotes: true,
ParseEscapedCharacters: true,
Replacements: &map[string]string{
`\\040`: " ",
},
})
if !(cmp.Equal(expected, actual)) {
t.Errorf("Expected %v, got %v", expected, actual)
}
}

View File

@ -132,7 +132,6 @@ func (v ArrayValue) getCurrentValue(line string, cursor uint32) (string, uint32)
// hello,worl[d]
// hello,world[,]
// hello[,]world,how,are,you
relativePosition, found := utils.FindPreviousCharacter(
line,
v.Separator,

View File

@ -170,13 +170,14 @@ func (v KeyEnumAssignmentValue) DeprecatedFetchCompletions(line string, cursor u
return v.FetchEnumCompletions()
}
relativePosition, found := utils.FindPreviousCharacter(
foundPosition, found := utils.FindPreviousCharacter(
line,
v.Separator,
int(cursor),
)
if found {
relativePosition := max(1, foundPosition) - 1
selectedKey := line[:uint32(relativePosition)]
line = line[uint32(relativePosition+len(v.Separator)):]
cursor -= uint32(relativePosition)

View File

@ -108,13 +108,14 @@ func (v KeyValueAssignmentValue) DeprecatedFetchCompletions(line string, cursor
return v.Key.DeprecatedFetchCompletions(line, cursor)
}
relativePosition, found := utils.FindPreviousCharacter(
foundPosition, found := utils.FindPreviousCharacter(
line,
v.Separator,
int(cursor),
)
if found {
relativePosition := max(1, foundPosition) - 1
selectedKey := line[:uint32(relativePosition)]
line = line[uint32(relativePosition+len(v.Separator)):]
cursor -= uint32(relativePosition)

View File

@ -0,0 +1,76 @@
grammar Fstab;
entry
:
WHITESPACE? spec?
WHITESPACE? mountPoint?
WHITESPACE? fileSystem?
WHITESPACE? mountOptions?
WHITESPACE? freq?
WHITESPACE? pass? WHITESPACE?
EOF
;
spec
: QUOTED_STRING | STRING
;
mountPoint
: QUOTED_STRING | STRING
;
fileSystem
: ADFS | AFFS | BTRFS | EXFAT
// Still match unknown file systems
| STRING | QUOTED_STRING
;
mountOptions
: QUOTED_STRING | STRING
;
freq
: DIGITS
;
pass
: DIGITS
;
DIGITS
: [0-9]+
;
WHITESPACE
: [ \t]+
;
HASH
: '#'
;
STRING
: ~(' ' | '\t' | '#')+
;
QUOTED_STRING
: '"' WHITESPACE? (STRING WHITESPACE)* STRING? ('"')?
;
// ///// Supported file systems /////
ADFS
: ('A' | 'a') ('D' | 'd') ('F' | 'f') ('S' | 's')
;
AFFS
: ('A' | 'a') ('F' | 'f') ('F' | 'f') ('S' | 's')
;
BTRFS
: ('B' | 'b') ('T' | 't') ('R' | 'r') ('F' | 'f') ('S' | 's')
;
EXFAT
: ('E' | 'e') ('X' | 'x') ('F' | 'f') ('A' | 'a') ('T' | 't')
;

View File

@ -0,0 +1,30 @@
package analyzer
import (
"config-lsp/handlers/fstab/shared"
protocol "github.com/tliron/glsp/protocol_3_16"
)
type analyzerContext struct {
document *shared.FstabDocument
diagnostics []protocol.Diagnostic
}
func Analyze(
document *shared.FstabDocument,
) []protocol.Diagnostic {
ctx := analyzerContext{
document: document,
}
analyzeFieldAreFilled(&ctx)
if len(ctx.diagnostics) > 0 {
return ctx.diagnostics
}
analyzeValuesAreValid(&ctx)
return ctx.diagnostics
}

View File

@ -0,0 +1,93 @@
package analyzer
import (
"config-lsp/common"
"config-lsp/handlers/fstab/ast"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func analyzeFieldAreFilled(
ctx *analyzerContext,
) {
it := ctx.document.Config.Entries.Iterator()
for it.Next() {
entry := it.Value().(*ast.FstabEntry)
if entry.Fields.Spec == nil || entry.Fields.Spec.Value.Value == "" {
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: protocol.Range{
Start: protocol.Position{
Line: entry.Fields.Start.Line,
Character: 0,
},
End: protocol.Position{
Line: entry.Fields.Start.Line,
Character: 0,
},
},
Message: "The spec field is missing",
Severity: &common.SeverityError,
})
continue
}
if entry.Fields.MountPoint == nil || entry.Fields.MountPoint.Value.Value == "" {
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: protocol.Range{
Start: protocol.Position{
Line: entry.Fields.Start.Line,
Character: entry.Fields.Spec.End.Character,
},
End: protocol.Position{
Line: entry.Fields.Start.Line,
Character: entry.Fields.Spec.End.Character,
},
},
Message: "The mount point field is missing",
Severity: &common.SeverityError,
})
continue
}
if entry.Fields.FilesystemType == nil || entry.Fields.FilesystemType.Value.Value == "" {
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: protocol.Range{
Start: protocol.Position{
Line: entry.Fields.Start.Line,
Character: entry.Fields.MountPoint.End.Character,
},
End: protocol.Position{
Line: entry.Fields.Start.Line,
Character: entry.Fields.MountPoint.End.Character,
},
},
Message: "The file system type field is missing",
Severity: &common.SeverityError,
})
continue
}
if entry.Fields.Options == nil || entry.Fields.Options.Value.Value == "" {
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: protocol.Range{
Start: protocol.Position{
Line: entry.Fields.Start.Line,
Character: entry.Fields.FilesystemType.End.Character,
},
End: protocol.Position{
Line: entry.Fields.Start.Line,
Character: entry.Fields.FilesystemType.End.Character,
},
},
Message: `The options field is empty. The usual convention is to use at least "defaults" keyword there.`,
Severity: &common.SeverityWarning,
})
continue
}
}
}

View File

@ -0,0 +1,38 @@
package analyzer
import (
testutils_test "config-lsp/handlers/fstab/test_utils"
"testing"
)
func TestFieldsMissingMountPoint(t *testing.T) {
document := testutils_test.DocumentFromInput(t, `
LABEL=test
`)
ctx := &analyzerContext{
document: document,
}
analyzeFieldAreFilled(ctx)
if len(ctx.diagnostics) != 1 {
t.Fatalf("Expected 1 diagnostic, got %d", len(ctx.diagnostics))
}
}
func TestValidExample(t *testing.T) {
document := testutils_test.DocumentFromInput(t, `
LABEL=test /mnt/test ext4 defaults 0 0
`)
ctx := &analyzerContext{
document: document,
}
analyzeFieldAreFilled(ctx)
if len(ctx.diagnostics) != 0 {
t.Fatalf("Expected 0 diagnostics, got %d", len(ctx.diagnostics))
}
}

View File

@ -0,0 +1,58 @@
package analyzer
import (
"config-lsp/common"
docvalues "config-lsp/doc-values"
"config-lsp/handlers/fstab/ast"
"config-lsp/handlers/fstab/fields"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func analyzeValuesAreValid(
ctx *analyzerContext,
) {
it := ctx.document.Config.Entries.Iterator()
for it.Next() {
entry := it.Value().(*ast.FstabEntry)
checkField(ctx, entry.Fields.Spec, fields.SpecField)
checkField(ctx, entry.Fields.MountPoint, fields.MountPointField)
checkField(ctx, entry.Fields.FilesystemType, fields.FileSystemTypeField)
if entry.Fields.Options != nil {
mountOptions := entry.FetchMountOptionsField(true)
if mountOptions != nil {
checkField(ctx, entry.Fields.Options, mountOptions)
}
}
if entry.Fields.Freq != nil {
checkField(ctx, entry.Fields.Freq, fields.FreqField)
}
if entry.Fields.Pass != nil {
checkField(ctx, entry.Fields.Pass, fields.PassField)
}
}
}
func checkField(
ctx *analyzerContext,
field *ast.FstabField,
docOption docvalues.DeprecatedValue,
) {
invalidValues := docOption.DeprecatedCheckIsValid(field.Value.Value)
for _, invalidValue := range invalidValues {
err := docvalues.LSPErrorFromInvalidValue(field.Start.Line, *invalidValue).ShiftCharacter(field.Start.Character)
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: err.Range.ToLSPRange(),
Message: err.Err.Error(),
Severity: &common.SeverityError,
})
}
}

View File

@ -0,0 +1,61 @@
package analyzer
import (
testutils_test "config-lsp/handlers/fstab/test_utils"
"testing"
)
func TestInvalidMountOptionsExample(
t *testing.T,
) {
document := testutils_test.DocumentFromInput(t, `
LABEL=test /mnt/test ext4 invalid 0 0
`)
ctx := &analyzerContext{
document: document,
}
analyzeValuesAreValid(ctx)
if len(ctx.diagnostics) == 0 {
t.Fatalf("Expected diagnostic, got %d", len(ctx.diagnostics))
}
}
func TestExt4IsUsingBtrfsMountOption(
t *testing.T,
) {
document := testutils_test.DocumentFromInput(t, `
# Valid, but only for btrfs
LABEL=test /mnt/test ext4 subvolid=1 0 0
`)
ctx := &analyzerContext{
document: document,
}
analyzeValuesAreValid(ctx)
if len(ctx.diagnostics) == 0 {
t.Fatalf("Expected diagnostic, got %d", len(ctx.diagnostics))
}
}
func TestValidBtrfsIsUsingBtrfsMountOption(
t *testing.T,
) {
document := testutils_test.DocumentFromInput(t, `
LABEL=test /mnt/test btrfs subvolid=1 0 0
`)
ctx := &analyzerContext{
document: document,
}
analyzeValuesAreValid(ctx)
if len(ctx.diagnostics) != 0 {
t.Fatalf("Expected diagnostic, got %d", len(ctx.diagnostics))
}
}

View File

@ -0,0 +1,45 @@
package ast
import (
"config-lsp/common"
"github.com/antlr4-go/antlr/v4"
)
type errorListenerContext struct {
line uint32
}
type errorListener struct {
*antlr.DefaultErrorListener
Errors []common.LSPError
context errorListenerContext
}
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: errorListenerContext{
line: line,
},
}
}

View File

@ -0,0 +1,45 @@
package ast
import (
"config-lsp/common"
commonparser "config-lsp/common/parser"
"github.com/emirpasic/gods/maps/treemap"
)
type FstabFieldName string
const (
FstabFieldSpec FstabFieldName = "spec"
FstabFieldMountPoint FstabFieldName = "mountpoint"
FstabFieldFileSystemType FstabFieldName = "filesystemtype"
FstabFieldOptions FstabFieldName = "options"
FstabFieldFreq FstabFieldName = "freq"
FstabFieldPass FstabFieldName = "pass"
)
type FstabField struct {
common.LocationRange
Value commonparser.ParsedString
}
type FstabFields struct {
common.LocationRange
Spec *FstabField
MountPoint *FstabField
FilesystemType *FstabField
Options *FstabField
Freq *FstabField
Pass *FstabField
}
type FstabEntry struct {
Fields *FstabFields
}
type FstabConfig struct {
// [uint32]FstabEntry - line number to line mapping
Entries *treemap.Map
// [uint32]{} - line number to empty struct for comments
CommentLines map[uint32]struct{}
}

View File

@ -0,0 +1,172 @@
package ast
import (
"config-lsp/common"
docvalues "config-lsp/doc-values"
"config-lsp/handlers/fstab/fields"
"config-lsp/utils"
)
// func (c FstabConfig) GetEntry(line uint32) *FstabEntry {
// entry, found := c.Entries.Get(line)
//
// if !found {
// return nil
// }
//
// return entry.(*FstabEntry)
// }
// LABEL=test ext4 defaults 0 0
func (e FstabEntry) GetFieldAtPosition(position common.Position) FstabFieldName {
// No fields defined, empty line
if e.Fields.Spec == nil && e.Fields.MountPoint == nil && e.Fields.FilesystemType == nil && e.Fields.Options == nil && e.Fields.Freq == nil && e.Fields.Pass == nil {
return FstabFieldSpec
}
// First, try if out of the existing fields the user wants to edit one of them
if e.Fields.Spec != nil && e.Fields.Spec.ContainsPosition(position) {
return FstabFieldSpec
}
if e.Fields.MountPoint != nil && e.Fields.MountPoint.ContainsPosition(position) {
return FstabFieldMountPoint
}
if e.Fields.FilesystemType != nil && e.Fields.FilesystemType.ContainsPosition(position) {
return FstabFieldFileSystemType
}
if e.Fields.Options != nil && e.Fields.Options.ContainsPosition(position) {
return FstabFieldOptions
}
if e.Fields.Freq != nil && e.Fields.Freq.ContainsPosition(position) {
return FstabFieldFreq
}
if e.Fields.Pass != nil && e.Fields.Pass.ContainsPosition(position) {
return FstabFieldPass
}
// Okay let's try to fetch the field by assuming the user is typing from left to right normally
if e.Fields.Spec != nil && e.Fields.Spec.IsPositionAfterEnd(position) && (e.Fields.MountPoint == nil || e.Fields.MountPoint.IsPositionBeforeEnd(position)) {
return FstabFieldMountPoint
}
if e.Fields.MountPoint != nil && e.Fields.MountPoint.IsPositionAfterEnd(position) && (e.Fields.FilesystemType == nil || e.Fields.FilesystemType.IsPositionBeforeEnd(position)) {
return FstabFieldFileSystemType
}
if e.Fields.FilesystemType != nil && e.Fields.FilesystemType.IsPositionAfterEnd(position) && (e.Fields.Options == nil || e.Fields.Options.IsPositionBeforeEnd(position)) {
return FstabFieldOptions
}
if e.Fields.Options != nil && e.Fields.Options.IsPositionAfterEnd(position) && (e.Fields.Freq == nil || e.Fields.Freq.IsPositionBeforeEnd(position)) {
return FstabFieldFreq
}
if e.Fields.Freq != nil && e.Fields.Freq.IsPositionAfterEnd(position) && (e.Fields.Pass == nil || e.Fields.Pass.IsPositionBeforeEnd(position)) {
return FstabFieldPass
}
// Okay shit no idea, let's just give whatever is missing
if e.Fields.Spec == nil {
return FstabFieldSpec
}
if e.Fields.MountPoint == nil {
return FstabFieldMountPoint
}
if e.Fields.FilesystemType == nil {
return FstabFieldFileSystemType
}
if e.Fields.Options == nil {
return FstabFieldOptions
}
if e.Fields.Freq == nil {
return FstabFieldFreq
}
return FstabFieldPass
}
// LABEL=test /mnt/test btrfs subvol=backup,fat=32 [0] [0]
func (e FstabEntry) getCursorIndex() uint8 {
definedAmount := e.getDefinedFieldsAmount()
switch definedAmount {
case 5:
}
return 0
}
func (e FstabEntry) getDefinedFieldsAmount() uint8 {
var definedAmount uint8 = 0
if e.Fields.Spec != nil {
definedAmount++
}
if e.Fields.MountPoint != nil {
definedAmount++
}
if e.Fields.FilesystemType != nil {
definedAmount++
}
if e.Fields.Options != nil {
definedAmount++
}
if e.Fields.Freq != nil {
definedAmount++
}
if e.Fields.Pass != nil {
definedAmount++
}
return definedAmount
}
// Create a mount options field for the entry
func (e FstabEntry) FetchMountOptionsField(includeDefaults bool) docvalues.DeprecatedValue {
if e.Fields.FilesystemType == nil {
return nil
}
option, found := fields.MountOptionsMapField[e.Fields.FilesystemType.Value.Value]
if !found {
return nil
}
var enums []docvalues.EnumString
var assignable map[docvalues.EnumString]docvalues.DeprecatedValue
if includeDefaults {
enums = append(option.Enums, fields.DefaultOptions...)
assignable = utils.MergeMaps(option.Assignable, fields.DefaultAssignOptions)
} else {
enums = option.Enums
assignable = option.Assignable
}
return &docvalues.ArrayValue{
Separator: ",",
DuplicatesExtractor: &fields.MountOptionsExtractor,
SubValue: docvalues.OrValue{
Values: []docvalues.DeprecatedValue{
docvalues.KeyEnumAssignmentValue{
Values: assignable,
ValueIsOptional: false,
Separator: "=",
},
docvalues.EnumValue{
EnforceValues: true,
Values: enums,
},
},
},
}
}

View File

@ -0,0 +1,135 @@
package ast
import (
"config-lsp/common"
"config-lsp/handlers/fstab/ast/parser"
commonparser "config-lsp/common/parser"
)
type fstabListenerContext struct {
line uint32
currentEntry *FstabEntry
}
func createListenerContext() *fstabListenerContext {
context := new(fstabListenerContext)
return context
}
type fstabParserListener struct {
*parser.BaseFstabListener
Config *FstabConfig
Errors []common.LSPError
fstabContext *fstabListenerContext
}
func createListener(
config *FstabConfig,
context *fstabListenerContext,
) fstabParserListener {
return fstabParserListener{
Config: config,
Errors: make([]common.LSPError, 0),
fstabContext: context,
}
}
func (s *fstabParserListener) EnterEntry(ctx *parser.EntryContext) {
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
location.ChangeBothLines(s.fstabContext.line)
s.fstabContext.currentEntry = &FstabEntry{
Fields: &FstabFields{
LocationRange: location,
},
}
s.Config.Entries.Put(
s.fstabContext.line,
s.fstabContext.currentEntry,
)
}
func (s *fstabParserListener) ExitEntry(ctx *parser.EntryContext) {
s.fstabContext.currentEntry = nil
}
func (s *fstabParserListener) EnterSpec(ctx *parser.SpecContext) {
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
location.ChangeBothLines(s.fstabContext.line)
text := ctx.GetText()
value := commonparser.ParseRawString(text, commonparser.FullFeatures)
s.fstabContext.currentEntry.Fields.Spec = &FstabField{
Value: value,
LocationRange: location,
}
}
func (s *fstabParserListener) EnterMountPoint(ctx *parser.MountPointContext) {
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
location.ChangeBothLines(s.fstabContext.line)
text := ctx.GetText()
value := commonparser.ParseRawString(text, commonparser.FullFeatures)
s.fstabContext.currentEntry.Fields.MountPoint = &FstabField{
LocationRange: location,
Value: value,
}
}
func (s *fstabParserListener) EnterFileSystem(ctx *parser.FileSystemContext) {
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
location.ChangeBothLines(s.fstabContext.line)
text := ctx.GetText()
value := commonparser.ParseRawString(text, commonparser.FullFeatures)
s.fstabContext.currentEntry.Fields.FilesystemType = &FstabField{
LocationRange: location,
Value: value,
}
}
func (s *fstabParserListener) EnterMountOptions(ctx *parser.MountOptionsContext) {
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
location.ChangeBothLines(s.fstabContext.line)
text := ctx.GetText()
value := commonparser.ParseRawString(text, commonparser.FullFeatures)
s.fstabContext.currentEntry.Fields.Options = &FstabField{
LocationRange: location,
Value: value,
}
}
func (s *fstabParserListener) EnterFreq(ctx *parser.FreqContext) {
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
location.ChangeBothLines(s.fstabContext.line)
text := ctx.GetText()
value := commonparser.ParseRawString(text, commonparser.FullFeatures)
s.fstabContext.currentEntry.Fields.Freq = &FstabField{
LocationRange: location,
Value: value,
}
}
func (s *fstabParserListener) EnterPass(ctx *parser.PassContext) {
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
location.ChangeBothLines(s.fstabContext.line)
text := ctx.GetText()
value := commonparser.ParseRawString(text, commonparser.FullFeatures)
s.fstabContext.currentEntry.Fields.Pass = &FstabField{
LocationRange: location,
Value: value,
}
}

View File

@ -0,0 +1,201 @@
package ast
import (
"config-lsp/common"
commonparser "config-lsp/common/parser"
"config-lsp/handlers/fstab/ast/parser"
"config-lsp/utils"
"regexp"
"strings"
"github.com/antlr4-go/antlr/v4"
"github.com/emirpasic/gods/maps/treemap"
gods "github.com/emirpasic/gods/utils"
)
func NewFstabConfig() *FstabConfig {
config := &FstabConfig{}
config.Clear()
return config
}
func (c *FstabConfig) Clear() {
c.Entries = treemap.NewWith(gods.UInt32Comparator)
c.CommentLines = map[uint32]struct{}{}
}
var commentPattern = regexp.MustCompile(`^\s*#`)
var emptyPattern = regexp.MustCompile(`^\s*$`)
var leadingCommentPattern = regexp.MustCompile(`^(.+?)#`)
func (c *FstabConfig) Parse(input string) []common.LSPError {
errors := make([]common.LSPError, 0)
lines := utils.SplitIntoLines(input)
context := createListenerContext()
for rawLineNumber, rawLine := range lines {
line := rawLine
lineNumber := uint32(rawLineNumber)
context.line = lineNumber
if emptyPattern.MatchString(line) {
continue
}
if commentPattern.MatchString(line) {
c.CommentLines[lineNumber] = struct{}{}
continue
}
if strings.Contains(line, "#") {
matches := leadingCommentPattern.FindStringSubmatch(line)
line = matches[1]
}
errors = append(
errors,
c.parseStatement(context, line)...,
)
}
return errors
}
func (c *FstabConfig) parseStatement(
context *fstabListenerContext,
input string,
) []common.LSPError {
stream := antlr.NewInputStream(input)
lexerErrorListener := createErrorListener(context.line)
lexer := parser.NewFstabLexer(stream)
lexer.RemoveErrorListeners()
lexer.AddErrorListener(&lexerErrorListener)
tokenStream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
parserErrorListener := createErrorListener(context.line)
antlrParser := parser.NewFstabParser(tokenStream)
antlrParser.RemoveErrorListeners()
antlrParser.AddErrorListener(&parserErrorListener)
listener := createListener(c, context)
antlr.ParseTreeWalkerDefault.Walk(
&listener,
antlrParser.Entry(),
)
errors := lexerErrorListener.Errors
errors = append(errors, parserErrorListener.Errors...)
errors = append(errors, listener.Errors...)
return errors
}
// func (c *FstabConfig) parseStatement(
// line uint32,
// input string,
// ) []common.LSPError {
// fields := whitespacePattern.FindAllStringIndex(input, -1)
//
// if len(fields) == 0 {
// return []common.LSPError{
// {
// Range: common.LocationRange{
// Start: common.Location{
// Line: line,
// Character: 0,
// },
// End: common.Location{
// Line: line,
// Character: 0,
// },
// },
// },
// }
// }
//
// var spec *FstabField
// var mountPoint *FstabField
// var filesystemType *FstabField
// var options *FstabField
// var freq *FstabField
// var pass *FstabField
//
// switch len(fields) {
// case 6:
// pass = parseField(line, input, fields[5])
// fallthrough
// case 5:
// freq = parseField(line, input, fields[4])
// fallthrough
// case 4:
// options = parseField(line, input, fields[3])
// fallthrough
// case 3:
// filesystemType = parseField(line, input, fields[2])
// fallthrough
// case 2:
// mountPoint = parseField(line, input, fields[1])
// fallthrough
// case 1:
// spec = parseField(line, input, fields[0])
// }
//
// fstabLine := &FstabEntry{
// Fields: FstabFields{
// LocationRange: common.LocationRange{
// Start: common.Location{
// Line: line,
// Character: 0,
// },
// End: common.Location{
// Line: line,
// Character: uint32(len(input)),
// },
// },
// Spec: spec,
// MountPoint: mountPoint,
// FilesystemType: filesystemType,
// Options: options,
// Freq: freq,
// Pass: pass,
// },
// }
//
// c.Entries.Put(line, fstabLine)
//
// return nil
// }
func parseField(
line uint32,
input string,
field []int,
) *FstabField {
start := uint32(field[0])
end := uint32(field[1])
value := input[start:end]
return &FstabField{
LocationRange: common.LocationRange{
Start: common.Location{
Line: line,
Character: start,
},
End: common.Location{
Line: line,
Character: end,
},
},
Value: commonparser.ParseRawString(value, commonparser.ParseFeatures{
ParseEscapedCharacters: true,
ParseDoubleQuotes: true,
Replacements: &map[string]string{
`\\040`: " ",
},
}),
}
}

View File

@ -0,0 +1,36 @@
token literal names:
null
null
null
'#'
null
null
null
null
null
null
token symbolic names:
null
DIGITS
WHITESPACE
HASH
STRING
QUOTED_STRING
ADFS
AFFS
BTRFS
EXFAT
rule names:
entry
spec
mountPoint
fileSystem
mountOptions
freq
pass
atn:
[4, 1, 9, 68, 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, 1, 0, 3, 0, 16, 8, 0, 1, 0, 3, 0, 19, 8, 0, 1, 0, 3, 0, 22, 8, 0, 1, 0, 3, 0, 25, 8, 0, 1, 0, 3, 0, 28, 8, 0, 1, 0, 3, 0, 31, 8, 0, 1, 0, 3, 0, 34, 8, 0, 1, 0, 3, 0, 37, 8, 0, 1, 0, 3, 0, 40, 8, 0, 1, 0, 3, 0, 43, 8, 0, 1, 0, 3, 0, 46, 8, 0, 1, 0, 3, 0, 49, 8, 0, 1, 0, 3, 0, 52, 8, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 0, 0, 7, 0, 2, 4, 6, 8, 10, 12, 0, 2, 1, 0, 4, 5, 1, 0, 4, 9, 73, 0, 15, 1, 0, 0, 0, 2, 55, 1, 0, 0, 0, 4, 57, 1, 0, 0, 0, 6, 59, 1, 0, 0, 0, 8, 61, 1, 0, 0, 0, 10, 63, 1, 0, 0, 0, 12, 65, 1, 0, 0, 0, 14, 16, 5, 2, 0, 0, 15, 14, 1, 0, 0, 0, 15, 16, 1, 0, 0, 0, 16, 18, 1, 0, 0, 0, 17, 19, 3, 2, 1, 0, 18, 17, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, 21, 1, 0, 0, 0, 20, 22, 5, 2, 0, 0, 21, 20, 1, 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 24, 1, 0, 0, 0, 23, 25, 3, 4, 2, 0, 24, 23, 1, 0, 0, 0, 24, 25, 1, 0, 0, 0, 25, 27, 1, 0, 0, 0, 26, 28, 5, 2, 0, 0, 27, 26, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 30, 1, 0, 0, 0, 29, 31, 3, 6, 3, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 33, 1, 0, 0, 0, 32, 34, 5, 2, 0, 0, 33, 32, 1, 0, 0, 0, 33, 34, 1, 0, 0, 0, 34, 36, 1, 0, 0, 0, 35, 37, 3, 8, 4, 0, 36, 35, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0, 37, 39, 1, 0, 0, 0, 38, 40, 5, 2, 0, 0, 39, 38, 1, 0, 0, 0, 39, 40, 1, 0, 0, 0, 40, 42, 1, 0, 0, 0, 41, 43, 3, 10, 5, 0, 42, 41, 1, 0, 0, 0, 42, 43, 1, 0, 0, 0, 43, 45, 1, 0, 0, 0, 44, 46, 5, 2, 0, 0, 45, 44, 1, 0, 0, 0, 45, 46, 1, 0, 0, 0, 46, 48, 1, 0, 0, 0, 47, 49, 3, 12, 6, 0, 48, 47, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 51, 1, 0, 0, 0, 50, 52, 5, 2, 0, 0, 51, 50, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 53, 1, 0, 0, 0, 53, 54, 5, 0, 0, 1, 54, 1, 1, 0, 0, 0, 55, 56, 7, 0, 0, 0, 56, 3, 1, 0, 0, 0, 57, 58, 7, 0, 0, 0, 58, 5, 1, 0, 0, 0, 59, 60, 7, 1, 0, 0, 60, 7, 1, 0, 0, 0, 61, 62, 7, 0, 0, 0, 62, 9, 1, 0, 0, 0, 63, 64, 5, 1, 0, 0, 64, 11, 1, 0, 0, 0, 65, 66, 5, 1, 0, 0, 66, 13, 1, 0, 0, 0, 13, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51]

View File

@ -0,0 +1,10 @@
DIGITS=1
WHITESPACE=2
HASH=3
STRING=4
QUOTED_STRING=5
ADFS=6
AFFS=7
BTRFS=8
EXFAT=9
'#'=3

View File

@ -0,0 +1,44 @@
token literal names:
null
null
null
'#'
null
null
null
null
null
null
token symbolic names:
null
DIGITS
WHITESPACE
HASH
STRING
QUOTED_STRING
ADFS
AFFS
BTRFS
EXFAT
rule names:
DIGITS
WHITESPACE
HASH
STRING
QUOTED_STRING
ADFS
AFFS
BTRFS
EXFAT
channel names:
DEFAULT_TOKEN_CHANNEL
HIDDEN
mode names:
DEFAULT_MODE
atn:
[4, 0, 9, 76, 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, 1, 0, 4, 0, 21, 8, 0, 11, 0, 12, 0, 22, 1, 1, 4, 1, 26, 8, 1, 11, 1, 12, 1, 27, 1, 2, 1, 2, 1, 3, 4, 3, 33, 8, 3, 11, 3, 12, 3, 34, 1, 4, 1, 4, 3, 4, 39, 8, 4, 1, 4, 1, 4, 1, 4, 5, 4, 44, 8, 4, 10, 4, 12, 4, 47, 9, 4, 1, 4, 3, 4, 50, 8, 4, 1, 4, 3, 4, 53, 8, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 0, 0, 9, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 1, 0, 12, 1, 0, 48, 57, 2, 0, 9, 9, 32, 32, 3, 0, 9, 9, 32, 32, 35, 35, 2, 0, 65, 65, 97, 97, 2, 0, 68, 68, 100, 100, 2, 0, 70, 70, 102, 102, 2, 0, 83, 83, 115, 115, 2, 0, 66, 66, 98, 98, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 69, 69, 101, 101, 2, 0, 88, 88, 120, 120, 82, 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, 1, 20, 1, 0, 0, 0, 3, 25, 1, 0, 0, 0, 5, 29, 1, 0, 0, 0, 7, 32, 1, 0, 0, 0, 9, 36, 1, 0, 0, 0, 11, 54, 1, 0, 0, 0, 13, 59, 1, 0, 0, 0, 15, 64, 1, 0, 0, 0, 17, 70, 1, 0, 0, 0, 19, 21, 7, 0, 0, 0, 20, 19, 1, 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 20, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 2, 1, 0, 0, 0, 24, 26, 7, 1, 0, 0, 25, 24, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 25, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 4, 1, 0, 0, 0, 29, 30, 5, 35, 0, 0, 30, 6, 1, 0, 0, 0, 31, 33, 8, 2, 0, 0, 32, 31, 1, 0, 0, 0, 33, 34, 1, 0, 0, 0, 34, 32, 1, 0, 0, 0, 34, 35, 1, 0, 0, 0, 35, 8, 1, 0, 0, 0, 36, 38, 5, 34, 0, 0, 37, 39, 3, 3, 1, 0, 38, 37, 1, 0, 0, 0, 38, 39, 1, 0, 0, 0, 39, 45, 1, 0, 0, 0, 40, 41, 3, 7, 3, 0, 41, 42, 3, 3, 1, 0, 42, 44, 1, 0, 0, 0, 43, 40, 1, 0, 0, 0, 44, 47, 1, 0, 0, 0, 45, 43, 1, 0, 0, 0, 45, 46, 1, 0, 0, 0, 46, 49, 1, 0, 0, 0, 47, 45, 1, 0, 0, 0, 48, 50, 3, 7, 3, 0, 49, 48, 1, 0, 0, 0, 49, 50, 1, 0, 0, 0, 50, 52, 1, 0, 0, 0, 51, 53, 5, 34, 0, 0, 52, 51, 1, 0, 0, 0, 52, 53, 1, 0, 0, 0, 53, 10, 1, 0, 0, 0, 54, 55, 7, 3, 0, 0, 55, 56, 7, 4, 0, 0, 56, 57, 7, 5, 0, 0, 57, 58, 7, 6, 0, 0, 58, 12, 1, 0, 0, 0, 59, 60, 7, 3, 0, 0, 60, 61, 7, 5, 0, 0, 61, 62, 7, 5, 0, 0, 62, 63, 7, 6, 0, 0, 63, 14, 1, 0, 0, 0, 64, 65, 7, 7, 0, 0, 65, 66, 7, 8, 0, 0, 66, 67, 7, 9, 0, 0, 67, 68, 7, 5, 0, 0, 68, 69, 7, 6, 0, 0, 69, 16, 1, 0, 0, 0, 70, 71, 7, 10, 0, 0, 71, 72, 7, 11, 0, 0, 72, 73, 7, 5, 0, 0, 73, 74, 7, 3, 0, 0, 74, 75, 7, 8, 0, 0, 75, 18, 1, 0, 0, 0, 8, 0, 22, 27, 34, 38, 45, 49, 52, 0]

View File

@ -0,0 +1,10 @@
DIGITS=1
WHITESPACE=2
HASH=3
STRING=4
QUOTED_STRING=5
ADFS=6
AFFS=7
BTRFS=8
EXFAT=9
'#'=3

View File

@ -0,0 +1,64 @@
// Code generated from Fstab.g4 by ANTLR 4.13.0. DO NOT EDIT.
package parser // Fstab
import "github.com/antlr4-go/antlr/v4"
// BaseFstabListener is a complete listener for a parse tree produced by FstabParser.
type BaseFstabListener struct{}
var _ FstabListener = &BaseFstabListener{}
// VisitTerminal is called when a terminal node is visited.
func (s *BaseFstabListener) VisitTerminal(node antlr.TerminalNode) {}
// VisitErrorNode is called when an error node is visited.
func (s *BaseFstabListener) VisitErrorNode(node antlr.ErrorNode) {}
// EnterEveryRule is called when any rule is entered.
func (s *BaseFstabListener) EnterEveryRule(ctx antlr.ParserRuleContext) {}
// ExitEveryRule is called when any rule is exited.
func (s *BaseFstabListener) ExitEveryRule(ctx antlr.ParserRuleContext) {}
// EnterEntry is called when production entry is entered.
func (s *BaseFstabListener) EnterEntry(ctx *EntryContext) {}
// ExitEntry is called when production entry is exited.
func (s *BaseFstabListener) ExitEntry(ctx *EntryContext) {}
// EnterSpec is called when production spec is entered.
func (s *BaseFstabListener) EnterSpec(ctx *SpecContext) {}
// ExitSpec is called when production spec is exited.
func (s *BaseFstabListener) ExitSpec(ctx *SpecContext) {}
// EnterMountPoint is called when production mountPoint is entered.
func (s *BaseFstabListener) EnterMountPoint(ctx *MountPointContext) {}
// ExitMountPoint is called when production mountPoint is exited.
func (s *BaseFstabListener) ExitMountPoint(ctx *MountPointContext) {}
// EnterFileSystem is called when production fileSystem is entered.
func (s *BaseFstabListener) EnterFileSystem(ctx *FileSystemContext) {}
// ExitFileSystem is called when production fileSystem is exited.
func (s *BaseFstabListener) ExitFileSystem(ctx *FileSystemContext) {}
// EnterMountOptions is called when production mountOptions is entered.
func (s *BaseFstabListener) EnterMountOptions(ctx *MountOptionsContext) {}
// ExitMountOptions is called when production mountOptions is exited.
func (s *BaseFstabListener) ExitMountOptions(ctx *MountOptionsContext) {}
// EnterFreq is called when production freq is entered.
func (s *BaseFstabListener) EnterFreq(ctx *FreqContext) {}
// ExitFreq is called when production freq is exited.
func (s *BaseFstabListener) ExitFreq(ctx *FreqContext) {}
// EnterPass is called when production pass is entered.
func (s *BaseFstabListener) EnterPass(ctx *PassContext) {}
// ExitPass is called when production pass is exited.
func (s *BaseFstabListener) ExitPass(ctx *PassContext) {}

View File

@ -0,0 +1,143 @@
// Code generated from Fstab.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 FstabLexer struct {
*antlr.BaseLexer
channelNames []string
modeNames []string
// TODO: EOF string
}
var FstabLexerLexerStaticData 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 fstablexerLexerInit() {
staticData := &FstabLexerLexerStaticData
staticData.ChannelNames = []string{
"DEFAULT_TOKEN_CHANNEL", "HIDDEN",
}
staticData.ModeNames = []string{
"DEFAULT_MODE",
}
staticData.LiteralNames = []string{
"", "", "", "'#'",
}
staticData.SymbolicNames = []string{
"", "DIGITS", "WHITESPACE", "HASH", "STRING", "QUOTED_STRING", "ADFS",
"AFFS", "BTRFS", "EXFAT",
}
staticData.RuleNames = []string{
"DIGITS", "WHITESPACE", "HASH", "STRING", "QUOTED_STRING", "ADFS", "AFFS",
"BTRFS", "EXFAT",
}
staticData.PredictionContextCache = antlr.NewPredictionContextCache()
staticData.serializedATN = []int32{
4, 0, 9, 76, 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, 1, 0, 4, 0, 21,
8, 0, 11, 0, 12, 0, 22, 1, 1, 4, 1, 26, 8, 1, 11, 1, 12, 1, 27, 1, 2, 1,
2, 1, 3, 4, 3, 33, 8, 3, 11, 3, 12, 3, 34, 1, 4, 1, 4, 3, 4, 39, 8, 4,
1, 4, 1, 4, 1, 4, 5, 4, 44, 8, 4, 10, 4, 12, 4, 47, 9, 4, 1, 4, 3, 4, 50,
8, 4, 1, 4, 3, 4, 53, 8, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1,
6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1,
8, 1, 8, 1, 8, 0, 0, 9, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15,
8, 17, 9, 1, 0, 12, 1, 0, 48, 57, 2, 0, 9, 9, 32, 32, 3, 0, 9, 9, 32, 32,
35, 35, 2, 0, 65, 65, 97, 97, 2, 0, 68, 68, 100, 100, 2, 0, 70, 70, 102,
102, 2, 0, 83, 83, 115, 115, 2, 0, 66, 66, 98, 98, 2, 0, 84, 84, 116, 116,
2, 0, 82, 82, 114, 114, 2, 0, 69, 69, 101, 101, 2, 0, 88, 88, 120, 120,
82, 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, 1, 20, 1, 0, 0, 0, 3, 25, 1, 0, 0, 0, 5, 29, 1,
0, 0, 0, 7, 32, 1, 0, 0, 0, 9, 36, 1, 0, 0, 0, 11, 54, 1, 0, 0, 0, 13,
59, 1, 0, 0, 0, 15, 64, 1, 0, 0, 0, 17, 70, 1, 0, 0, 0, 19, 21, 7, 0, 0,
0, 20, 19, 1, 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 20, 1, 0, 0, 0, 22, 23,
1, 0, 0, 0, 23, 2, 1, 0, 0, 0, 24, 26, 7, 1, 0, 0, 25, 24, 1, 0, 0, 0,
26, 27, 1, 0, 0, 0, 27, 25, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 4, 1, 0,
0, 0, 29, 30, 5, 35, 0, 0, 30, 6, 1, 0, 0, 0, 31, 33, 8, 2, 0, 0, 32, 31,
1, 0, 0, 0, 33, 34, 1, 0, 0, 0, 34, 32, 1, 0, 0, 0, 34, 35, 1, 0, 0, 0,
35, 8, 1, 0, 0, 0, 36, 38, 5, 34, 0, 0, 37, 39, 3, 3, 1, 0, 38, 37, 1,
0, 0, 0, 38, 39, 1, 0, 0, 0, 39, 45, 1, 0, 0, 0, 40, 41, 3, 7, 3, 0, 41,
42, 3, 3, 1, 0, 42, 44, 1, 0, 0, 0, 43, 40, 1, 0, 0, 0, 44, 47, 1, 0, 0,
0, 45, 43, 1, 0, 0, 0, 45, 46, 1, 0, 0, 0, 46, 49, 1, 0, 0, 0, 47, 45,
1, 0, 0, 0, 48, 50, 3, 7, 3, 0, 49, 48, 1, 0, 0, 0, 49, 50, 1, 0, 0, 0,
50, 52, 1, 0, 0, 0, 51, 53, 5, 34, 0, 0, 52, 51, 1, 0, 0, 0, 52, 53, 1,
0, 0, 0, 53, 10, 1, 0, 0, 0, 54, 55, 7, 3, 0, 0, 55, 56, 7, 4, 0, 0, 56,
57, 7, 5, 0, 0, 57, 58, 7, 6, 0, 0, 58, 12, 1, 0, 0, 0, 59, 60, 7, 3, 0,
0, 60, 61, 7, 5, 0, 0, 61, 62, 7, 5, 0, 0, 62, 63, 7, 6, 0, 0, 63, 14,
1, 0, 0, 0, 64, 65, 7, 7, 0, 0, 65, 66, 7, 8, 0, 0, 66, 67, 7, 9, 0, 0,
67, 68, 7, 5, 0, 0, 68, 69, 7, 6, 0, 0, 69, 16, 1, 0, 0, 0, 70, 71, 7,
10, 0, 0, 71, 72, 7, 11, 0, 0, 72, 73, 7, 5, 0, 0, 73, 74, 7, 3, 0, 0,
74, 75, 7, 8, 0, 0, 75, 18, 1, 0, 0, 0, 8, 0, 22, 27, 34, 38, 45, 49, 52,
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)
}
}
// FstabLexerInit initializes any static state used to implement FstabLexer. By default the
// static state used to implement the lexer is lazily initialized during the first call to
// NewFstabLexer(). You can call this function if you wish to initialize the static state ahead
// of time.
func FstabLexerInit() {
staticData := &FstabLexerLexerStaticData
staticData.once.Do(fstablexerLexerInit)
}
// NewFstabLexer produces a new lexer instance for the optional input antlr.CharStream.
func NewFstabLexer(input antlr.CharStream) *FstabLexer {
FstabLexerInit()
l := new(FstabLexer)
l.BaseLexer = antlr.NewBaseLexer(input)
staticData := &FstabLexerLexerStaticData
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 = "Fstab.g4"
// TODO: l.EOF = antlr.TokenEOF
return l
}
// FstabLexer tokens.
const (
FstabLexerDIGITS = 1
FstabLexerWHITESPACE = 2
FstabLexerHASH = 3
FstabLexerSTRING = 4
FstabLexerQUOTED_STRING = 5
FstabLexerADFS = 6
FstabLexerAFFS = 7
FstabLexerBTRFS = 8
FstabLexerEXFAT = 9
)

View File

@ -0,0 +1,52 @@
// Code generated from Fstab.g4 by ANTLR 4.13.0. DO NOT EDIT.
package parser // Fstab
import "github.com/antlr4-go/antlr/v4"
// FstabListener is a complete listener for a parse tree produced by FstabParser.
type FstabListener interface {
antlr.ParseTreeListener
// EnterEntry is called when entering the entry production.
EnterEntry(c *EntryContext)
// EnterSpec is called when entering the spec production.
EnterSpec(c *SpecContext)
// EnterMountPoint is called when entering the mountPoint production.
EnterMountPoint(c *MountPointContext)
// EnterFileSystem is called when entering the fileSystem production.
EnterFileSystem(c *FileSystemContext)
// EnterMountOptions is called when entering the mountOptions production.
EnterMountOptions(c *MountOptionsContext)
// EnterFreq is called when entering the freq production.
EnterFreq(c *FreqContext)
// EnterPass is called when entering the pass production.
EnterPass(c *PassContext)
// ExitEntry is called when exiting the entry production.
ExitEntry(c *EntryContext)
// ExitSpec is called when exiting the spec production.
ExitSpec(c *SpecContext)
// ExitMountPoint is called when exiting the mountPoint production.
ExitMountPoint(c *MountPointContext)
// ExitFileSystem is called when exiting the fileSystem production.
ExitFileSystem(c *FileSystemContext)
// ExitMountOptions is called when exiting the mountOptions production.
ExitMountOptions(c *MountOptionsContext)
// ExitFreq is called when exiting the freq production.
ExitFreq(c *FreqContext)
// ExitPass is called when exiting the pass production.
ExitPass(c *PassContext)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,145 @@
package ast
import (
"config-lsp/common"
"config-lsp/utils"
"testing"
)
func TestExample1(
t *testing.T,
) {
input := utils.Dedent(`
LABEL=test /mnt/test ext4 defaults 0 0
LABEL=example /mnt/example fat32 defaults 0 2
`)
c := NewFstabConfig()
errors := c.Parse(input)
if len(errors) > 0 {
t.Fatalf("Expected no errors, got %v", errors)
}
if c.Entries.Size() != 2 {
t.Fatalf("Expected 2 entry, got %d", c.Entries.Size())
}
rawFirstEntry, _ := c.Entries.Get(uint32(0))
firstEntry := rawFirstEntry.(*FstabEntry)
if !(firstEntry.Fields.Spec.Value.Value == "LABEL=test" && firstEntry.Fields.MountPoint.Value.Value == "/mnt/test" && firstEntry.Fields.FilesystemType.Value.Value == "ext4" && firstEntry.Fields.Options.Value.Value == "defaults" && firstEntry.Fields.Freq.Value.Value == "0" && firstEntry.Fields.Pass.Value.Value == "0") {
t.Fatalf("Expected entry to be LABEL=test /mnt/test ext4 defaults 0 0, got %v", firstEntry)
}
if !(firstEntry.Fields.Spec.LocationRange.Start.Line == 0 && firstEntry.Fields.Spec.LocationRange.Start.Character == 0) {
t.Errorf("Expected spec start to be 0:0, got %v", firstEntry.Fields.Spec.LocationRange.Start)
}
if !(firstEntry.Fields.Spec.LocationRange.End.Line == 0 && firstEntry.Fields.Spec.LocationRange.End.Character == 10) {
t.Errorf("Expected spec end to be 0:10, got %v", firstEntry.Fields.Spec.LocationRange.End)
}
if !(firstEntry.Fields.MountPoint.LocationRange.Start.Line == 0 && firstEntry.Fields.MountPoint.LocationRange.Start.Character == 11) {
t.Errorf("Expected mountpoint start to be 0:11, got %v", firstEntry.Fields.MountPoint.LocationRange.Start)
}
if !(firstEntry.Fields.MountPoint.LocationRange.End.Line == 0 && firstEntry.Fields.MountPoint.LocationRange.End.Character == 20) {
t.Errorf("Expected mountpoint end to be 0:20, got %v", firstEntry.Fields.MountPoint.LocationRange.End)
}
if !(firstEntry.Fields.FilesystemType.LocationRange.Start.Line == 0 && firstEntry.Fields.FilesystemType.LocationRange.Start.Character == 21) {
t.Errorf("Expected filesystemtype start to be 0:21, got %v", firstEntry.Fields.FilesystemType.LocationRange.Start)
}
if !(firstEntry.Fields.FilesystemType.LocationRange.End.Line == 0 && firstEntry.Fields.FilesystemType.LocationRange.End.Character == 25) {
t.Errorf("Expected filesystemtype end to be 0:25, got %v", firstEntry.Fields.FilesystemType.LocationRange.End)
}
if !(firstEntry.Fields.Options.LocationRange.Start.Line == 0 && firstEntry.Fields.Options.LocationRange.Start.Character == 26) {
t.Errorf("Expected options start to be 0:26, got %v", firstEntry.Fields.Options.LocationRange.Start)
}
if !(firstEntry.Fields.Options.LocationRange.End.Line == 0 && firstEntry.Fields.Options.LocationRange.End.Character == 34) {
t.Errorf("Expected options end to be 0:34, got %v", firstEntry.Fields.Options.LocationRange.End)
}
if !(firstEntry.Fields.Freq.LocationRange.Start.Line == 0 && firstEntry.Fields.Freq.LocationRange.Start.Character == 35) {
t.Errorf("Expected freq start to be 0:35, got %v", firstEntry.Fields.Freq.LocationRange.Start)
}
if !(firstEntry.Fields.Freq.LocationRange.End.Line == 0 && firstEntry.Fields.Freq.LocationRange.End.Character == 36) {
t.Errorf("Expected freq end to be 0:36, got %v", firstEntry.Fields.Freq.LocationRange.End)
}
if !(firstEntry.Fields.Pass.LocationRange.Start.Line == 0 && firstEntry.Fields.Pass.LocationRange.Start.Character == 37) {
t.Errorf("Expected pass start to be 0:37, got %v", firstEntry.Fields.Pass.LocationRange.Start)
}
field := firstEntry.GetFieldAtPosition(common.IndexPosition(0))
if !(field == FstabFieldSpec) {
t.Errorf("Expected field to be spec, got %v", field)
}
field = firstEntry.GetFieldAtPosition(common.IndexPosition(11))
if !(field == FstabFieldMountPoint) {
t.Errorf("Expected field to be mountpoint, got %v", field)
}
field = firstEntry.GetFieldAtPosition(common.IndexPosition(33))
if !(field == FstabFieldOptions) {
t.Errorf("Expected field to be spec, got %v", field)
}
field = firstEntry.GetFieldAtPosition(common.IndexPosition(35))
if !(field == FstabFieldFreq) {
t.Errorf("Expected field to be freq, got %v", field)
}
rawSecondEntry, _ := c.Entries.Get(uint32(1))
secondEntry := rawSecondEntry.(*FstabEntry)
if !(secondEntry.Fields.Start.Line == 1) {
t.Errorf("Expected start line to be 1, got %d", secondEntry.Fields.Start.Line)
}
}
func TestIncompleteExample(
t *testing.T,
) {
input := utils.Dedent(`
LABEL=test /mnt/test ext4 defaults
`)
c := NewFstabConfig()
errors := c.Parse(input)
if len(errors) != 0 {
t.Fatalf("Expected no errors, got %v", errors)
}
rawFirstEntry, _ := c.Entries.Get(uint32(0))
firstEntry := rawFirstEntry.(*FstabEntry)
if !(firstEntry.Fields.Spec.Value.Raw == "LABEL=test" && firstEntry.Fields.MountPoint.Value.Raw == "/mnt/test" && firstEntry.Fields.FilesystemType.Value.Raw == "ext4" && firstEntry.Fields.Options.Value.Raw == "defaults") {
t.Fatalf("Expected entry to be LABEL=test /mnt/test ext4 defaults, got %v", firstEntry)
}
field := firstEntry.GetFieldAtPosition(common.IndexPosition(0))
if !(field == FstabFieldSpec) {
t.Errorf("Expected field to be spec, got %v", field)
}
field = firstEntry.GetFieldAtPosition(common.IndexPosition(11))
if !(field == FstabFieldMountPoint) {
t.Errorf("Expected field to be mountpoint, got %v", field)
}
field = firstEntry.GetFieldAtPosition(common.IndexPosition(33))
if !(field == FstabFieldOptions) {
t.Errorf("Expected field to be spec, got %v", field)
}
field = firstEntry.GetFieldAtPosition(common.IndexPosition(35))
if !(field == FstabFieldFreq) {
t.Errorf("Expected field to be freq, got %v", field)
}
}

View File

@ -1,4 +1,4 @@
package fstabdocumentation
package fields
import docvalues "config-lsp/doc-values"

View File

@ -1,4 +1,4 @@
package fstabdocumentation
package fields
import (
docvalues "config-lsp/doc-values"

View File

@ -1,4 +1,4 @@
package fstabdocumentation
package fields
import (
commondocumentation "config-lsp/common-documentation"
@ -6,7 +6,7 @@ import (
"strings"
)
var mountOptionsExtractor = func(value string) string {
var MountOptionsExtractor = func(value string) string {
separatorIndex := strings.Index(value, "=")
if separatorIndex == -1 {
@ -17,7 +17,7 @@ var mountOptionsExtractor = func(value string) string {
}
// From https://www.man7.org/linux/man-pages/man8/mount.8.html
var defaultOptions = []docvalues.EnumString{
var DefaultOptions = []docvalues.EnumString{
// Default options
docvalues.CreateEnumStringWithDoc(
"async",
@ -39,22 +39,6 @@ var defaultOptions = []docvalues.EnumString{
"noauto",
"Can only be mounted explicitly (i.e., the -a option will not cause the filesystem to be mounted).",
),
docvalues.CreateEnumStringWithDoc(
"context",
"The context= option is useful when mounting filesystems that do not support extended attributes, such as a floppy or hard disk formatted with VFAT, or systems that are not normally running under SELinux, such as an ext3 or ext4 formatted disk from a non-SELinux workstation. You can also use context= on filesystems you do not trust, such as a floppy. It also helps in compatibility with xattr-supporting filesystems on earlier 2.4.<x> kernel versions. Even where xattrs are supported, you can save time not having to label every file by assigning the entire disk one security context.",
),
docvalues.CreateEnumStringWithDoc(
"fscontext",
"The fscontext= option works for all filesystems, regardless of their xattr support. The fscontext option sets the overarching filesystem label to a specific security context. This filesystem label is separate from the individual labels on the files. It represents the entire filesystem for certain kinds of permission checks, such as during mount or file creation. Individual file labels are still obtained from the xattrs on the files themselves. The context option actually sets the aggregate context that fscontext provides, in addition to supplying the same label for individual files.",
),
docvalues.CreateEnumStringWithDoc(
"defcontext",
"You can set the default security context for unlabeled files using defcontext= option. This overrides the value set for unlabeled files in the policy and requires a filesystem that supports xattr labeling.",
),
docvalues.CreateEnumStringWithDoc(
"rootcontext",
"The rootcontext= option allows you to explicitly label the root inode of a FS being mounted before that FS or inode becomes visible to userspace. This was found to be useful for things like stateless Linux. The special value @target can be used to assign the current context of the target mountpoint location.",
),
docvalues.CreateEnumStringWithDoc(
"defaults",
"Use the default options: rw, suid, dev, exec, auto, nouser, and async. Note that the real set of all default mount options depends on the kernel and filesystem type. See the beginning of this section for more details.",
@ -244,7 +228,7 @@ type assignOption struct {
Handler func(context docvalues.KeyValueAssignmentContext) docvalues.DeprecatedValue
}
var defaultAssignOptions = map[docvalues.EnumString]docvalues.DeprecatedValue{
var DefaultAssignOptions = map[docvalues.EnumString]docvalues.DeprecatedValue{
docvalues.CreateEnumStringWithDoc(
"context",
"The context= option is useful when mounting filesystems that do not support extended attributes, such as a floppy or hard disk formatted with VFAT, or systems that are not normally running under SELinux, such as an ext3 or ext4 formatted disk from a non-SELinux workstation. You can also use context= on filesystems you do not trust, such as a floppy. It also helps in compatibility with xattr-supporting filesystems on earlier 2.4.<x> kernel versions. Even where xattrs are supported, you can save time not having to label every file by assigning the entire disk one security context. A commonly used option for removable media is context=\"system_u:object_r:removable_t\".",
@ -345,130 +329,149 @@ See TimeoutSec= below for details.
Added in version 233.`,
): docvalues.StringValue{},
docvalues.CreateEnumStringWithDoc(
"fscontext",
"The fscontext= option works for all filesystems, regardless of their xattr support. The fscontext option sets the overarching filesystem label to a specific security context. This filesystem label is separate from the individual labels on the files. It represents the entire filesystem for certain kinds of permission checks, such as during mount or file creation. Individual file labels are still obtained from the xattrs on the files themselves. The context option actually sets the aggregate context that fscontext provides, in addition to supplying the same label for individual files.",
): docvalues.StringValue{},
docvalues.CreateEnumStringWithDoc(
"defcontext",
"You can set the default security context for unlabeled files using defcontext= option. This overrides the value set for unlabeled files in the policy and requires a filesystem that supports xattr labeling.",
): docvalues.StringValue{},
}
func createMountOptionField(
options []docvalues.EnumString,
assignOption map[docvalues.EnumString]docvalues.DeprecatedValue,
) docvalues.DeprecatedValue {
dynamicOptions := docvalues.MergeKeyEnumAssignmentMaps(defaultAssignOptions, assignOption)
// dynamicOptions := docvalues.MergeKeyEnumAssignmentMaps(defaultAssignOptions, assignOption)
return docvalues.ArrayValue{
Separator: ",",
DuplicatesExtractor: &mountOptionsExtractor,
DuplicatesExtractor: &MountOptionsExtractor,
SubValue: docvalues.OrValue{
Values: []docvalues.DeprecatedValue{
docvalues.KeyEnumAssignmentValue{
Values: dynamicOptions,
Values: assignOption,
ValueIsOptional: false,
Separator: "=",
},
docvalues.EnumValue{
EnforceValues: true,
Values: append(defaultOptions, options...),
Values: options,
},
},
},
}
}
var DefaultMountOptionsField = createMountOptionField([]docvalues.EnumString{}, map[docvalues.EnumString]docvalues.DeprecatedValue{})
type optionField struct {
Assignable map[docvalues.EnumString]docvalues.DeprecatedValue
Enums []docvalues.EnumString
}
var MountOptionsMapField = map[string]docvalues.DeprecatedValue{
"adfs": createMountOptionField(
commondocumentation.AdfsDocumentationEnums,
commondocumentation.AdfsDocumentationAssignable,
),
"affs": createMountOptionField(
commondocumentation.AffsDocumentationEnums,
commondocumentation.AffsDocumentationAssignable,
),
"btrfs": createMountOptionField(
commondocumentation.BtrfsDocumentationEnums,
commondocumentation.BtrfsDocumentationAssignable,
),
"debugfs": createMountOptionField(
commondocumentation.DebugfsDocumentationEnums,
commondocumentation.DebugfsDocumentationAssignable,
),
"ext2": createMountOptionField(
var DefaultMountOptionsField = createMountOptionField(DefaultOptions, DefaultAssignOptions)
var MountOptionsMapField = map[string]optionField{
"adfs": {
Enums: commondocumentation.AdfsDocumentationEnums,
Assignable: commondocumentation.AdfsDocumentationAssignable,
},
"affs": {
Enums: commondocumentation.AffsDocumentationEnums,
Assignable: commondocumentation.AffsDocumentationAssignable,
},
"btrfs": {
Enums: commondocumentation.BtrfsDocumentationEnums,
Assignable: commondocumentation.BtrfsDocumentationAssignable,
},
"debugfs": {
Enums: commondocumentation.DebugfsDocumentationEnums,
Assignable: commondocumentation.DebugfsDocumentationAssignable,
},
"ext2": {
Enums: commondocumentation.Ext2DocumentationEnums,
Assignable: commondocumentation.Ext2DocumentationAssignable,
},
"ext3": {
Enums: append(commondocumentation.Ext2DocumentationEnums, commondocumentation.Ext3DocumentationEnums...),
Assignable: docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext2DocumentationAssignable, commondocumentation.Ext3DocumentationAssignable),
},
"ext4": {
Enums: append(
append(
commondocumentation.Ext2DocumentationEnums,
commondocumentation.Ext2DocumentationAssignable,
commondocumentation.Ext3DocumentationEnums...,
),
"ext3": createMountOptionField(
append(commondocumentation.Ext2DocumentationEnums, commondocumentation.Ext3DocumentationEnums...),
docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext2DocumentationAssignable, commondocumentation.Ext3DocumentationAssignable),
),
"ext4": createMountOptionField(
append(append(commondocumentation.Ext2DocumentationEnums, commondocumentation.Ext3DocumentationEnums...), commondocumentation.Ext4DocumentationEnums...),
docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext2DocumentationAssignable, docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext3DocumentationAssignable, commondocumentation.Ext4DocumentationAssignable)),
),
"devpts": createMountOptionField(
commondocumentation.DevptsDocumentationEnums,
commondocumentation.DevptsDocumentationAssignable,
),
"fat": createMountOptionField(
commondocumentation.FatDocumentationEnums,
commondocumentation.FatDocumentationAssignable,
),
"hfs": createMountOptionField(
commondocumentation.HfsDocumentationEnums,
commondocumentation.HfsDocumentationAssignable,
),
"hpfs": createMountOptionField(
commondocumentation.HpfsDocumentationEnums,
commondocumentation.HpfsDocumentationAssignable,
),
"iso9660": createMountOptionField(
commondocumentation.Iso9660DocumentationEnums,
commondocumentation.Iso9660DocumentationAssignable,
),
"jfs": createMountOptionField(
commondocumentation.JfsDocumentationEnums,
commondocumentation.JfsDocumentationAssignable,
),
"msdos": createMountOptionField(
commondocumentation.MsdosDocumentationEnums,
commondocumentation.MsdosDocumentationAssignable,
),
"ncpfs": createMountOptionField(
commondocumentation.NcpfsDocumentationEnums,
commondocumentation.NcpfsDocumentationAssignable,
),
"ntfs": createMountOptionField(
commondocumentation.NtfsDocumentationEnums,
commondocumentation.NtfsDocumentationAssignable,
),
"overlay": createMountOptionField(
commondocumentation.OverlayDocumentationEnums,
commondocumentation.OverlayDocumentationAssignable,
),
"reiserfs": createMountOptionField(
commondocumentation.ReiserfsDocumentationEnums,
commondocumentation.ReiserfsDocumentationAssignable,
),
"usbfs": createMountOptionField(
commondocumentation.UsbfsDocumentationEnums,
commondocumentation.UsbfsDocumentationAssignable,
),
"ubifs": createMountOptionField(
commondocumentation.UbifsDocumentationEnums,
commondocumentation.UbifsDocumentationAssignable,
),
"udf": createMountOptionField(
commondocumentation.UdfDocumentationEnums,
commondocumentation.UdfDocumentationAssignable,
),
"ufs": createMountOptionField(
commondocumentation.UfsDocumentationEnums,
commondocumentation.UfsDocumentationAssignable,
),
"umsdos": createMountOptionField(
commondocumentation.UmsdosDocumentationEnums,
commondocumentation.UmsdosDocumentationAssignable,
),
"vfat": createMountOptionField(
commondocumentation.VfatDocumentationEnums,
commondocumentation.VfatDocumentationAssignable,
commondocumentation.Ext4DocumentationEnums...,
),
Assignable: docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext2DocumentationAssignable, docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext3DocumentationAssignable, commondocumentation.Ext4DocumentationAssignable)),
},
"devpts": {
Enums: commondocumentation.DevptsDocumentationEnums,
Assignable: commondocumentation.DevptsDocumentationAssignable,
},
"fat": {
Enums: commondocumentation.FatDocumentationEnums,
Assignable: commondocumentation.FatDocumentationAssignable,
},
"hfs": {
Enums: commondocumentation.HfsDocumentationEnums,
Assignable: commondocumentation.HfsDocumentationAssignable,
},
"hpfs": {
Enums: commondocumentation.HpfsDocumentationEnums,
Assignable: commondocumentation.HpfsDocumentationAssignable,
},
"iso9660": {
Enums: commondocumentation.Iso9660DocumentationEnums,
Assignable: commondocumentation.Iso9660DocumentationAssignable,
},
"jfs": {
Enums: commondocumentation.JfsDocumentationEnums,
Assignable: commondocumentation.JfsDocumentationAssignable,
},
"msdos": {
Enums: commondocumentation.MsdosDocumentationEnums,
Assignable: commondocumentation.MsdosDocumentationAssignable,
},
"ncpfs": {
Enums: commondocumentation.NcpfsDocumentationEnums,
Assignable: commondocumentation.NcpfsDocumentationAssignable,
},
"ntfs": {
Enums: commondocumentation.NtfsDocumentationEnums,
Assignable: commondocumentation.NtfsDocumentationAssignable,
},
"overlay": {
Enums: commondocumentation.OverlayDocumentationEnums,
Assignable: commondocumentation.OverlayDocumentationAssignable,
},
"reiserfs": {
Enums: commondocumentation.ReiserfsDocumentationEnums,
Assignable: commondocumentation.ReiserfsDocumentationAssignable,
},
"usbfs": {
Enums: commondocumentation.UsbfsDocumentationEnums,
Assignable: commondocumentation.UsbfsDocumentationAssignable,
},
"ubifs": {
Enums: commondocumentation.UbifsDocumentationEnums,
Assignable: commondocumentation.UbifsDocumentationAssignable,
},
"udf": {
Enums: commondocumentation.UdfDocumentationEnums,
Assignable: commondocumentation.UdfDocumentationAssignable,
},
"ufs": {
Enums: commondocumentation.UfsDocumentationEnums,
Assignable: commondocumentation.UfsDocumentationAssignable,
},
"umsdos": {
Enums: commondocumentation.UmsdosDocumentationEnums,
Assignable: commondocumentation.UmsdosDocumentationAssignable,
},
"vfat": {
Enums: commondocumentation.VfatDocumentationEnums,
Assignable: commondocumentation.VfatDocumentationAssignable,
},
}

View File

@ -1,4 +1,4 @@
package fstabdocumentation
package fields
import docvalues "config-lsp/doc-values"

View File

@ -1,4 +1,4 @@
package fstabdocumentation
package fields
import (
docvalues "config-lsp/doc-values"
@ -15,7 +15,7 @@ var LabelField = docvalues.RegexValue{
var SpecField = docvalues.OrValue{
Values: []docvalues.DeprecatedValue{
docvalues.PathValue{
RequiredType: docvalues.PathTypeFile & docvalues.PathTypeExistenceOptional,
RequiredType: docvalues.PathTypeExistenceOptional,
},
docvalues.KeyEnumAssignmentValue{
Separator: "=",

View File

@ -1,4 +1,4 @@
package fstabdocumentation
package fields
import (
docvalues "config-lsp/doc-values"

View File

@ -1,9 +1,10 @@
package fstab
import (
fstabdocumentation "config-lsp/handlers/fstab/documentation"
"config-lsp/common"
"config-lsp/handlers/fstab/ast"
fields "config-lsp/handlers/fstab/fields"
handlers "config-lsp/handlers/fstab/handlers"
"config-lsp/handlers/fstab/parser"
"config-lsp/utils"
"testing"
)
@ -12,22 +13,21 @@ func TestValidBasicExample(t *testing.T) {
input := utils.Dedent(`
LABEL=test /mnt/test ext4 defaults 0 0
`)
p := parser.FstabParser{}
p.Clear()
p := ast.NewFstabConfig()
errors := p.ParseFromContent(input)
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatal("ParseFromContent failed with error", errors)
t.Fatal("Parse failed with error", errors)
}
// Get hover for first field
rawEntry, _ := p.Entries.Get(uint32(0))
entry := rawEntry.(parser.FstabEntry)
entry := rawEntry.(*ast.FstabEntry)
println("Getting hover info")
{
hover, err := handlers.GetHoverInfo(&entry, uint32(0))
hover, err := handlers.GetHoverInfo(uint32(0), common.IndexPosition(0), entry)
if err != nil {
t.Fatal("getHoverInfo failed with error", err)
@ -38,7 +38,7 @@ LABEL=test /mnt/test ext4 defaults 0 0
}
// Get hover for second field
hover, err = handlers.GetHoverInfo(&entry, uint32(11))
hover, err = handlers.GetHoverInfo(uint32(0), common.IndexPosition(11), entry)
if err != nil {
t.Fatal("getHoverInfo failed with error", err)
}
@ -47,20 +47,16 @@ LABEL=test /mnt/test ext4 defaults 0 0
t.Fatal("getHoverInfo failed to return correct hover content. Got:", hover.Contents, "but expected:", handlers.MountPointHoverField.Contents)
}
hover, err = handlers.GetHoverInfo(&entry, uint32(20))
hover, err = handlers.GetHoverInfo(uint32(0), common.IndexPosition(20), entry)
if err != nil {
t.Fatal("getHoverInfo failed with error", err)
}
if hover.Contents != handlers.MountPointHoverField.Contents {
t.Fatal("getHoverInfo failed to return correct hover content. Got:", hover.Contents, "but expected:", handlers.MountPointHoverField.Contents)
}
}
println("Getting completions")
{
completions, err := handlers.GetCompletion(entry.Line, uint32(0))
completions, err := handlers.GetCompletion(entry, common.CursorPosition(0))
if err != nil {
t.Fatal("getCompletion failed with error", err)
@ -79,49 +75,59 @@ LABEL=test /mnt/test ext4 defaults 0 0
}
{
completions, err := handlers.GetCompletion(entry.Line, uint32(21))
completions, err := handlers.GetCompletion(entry, common.CursorPosition(23))
if err != nil {
t.Fatal("getCompletion failed with error", err)
}
expectedLength := len(utils.KeysOfMap(fstabdocumentation.MountOptionsMapField))
expectedLength := len(utils.KeysOfMap(fields.MountOptionsMapField))
if len(completions) != expectedLength {
t.Fatal("getCompletion failed to return correct number of completions. Got:", len(completions), "but expected:", expectedLength)
}
}
println("Checking values")
{
diagnostics := p.AnalyzeValues()
if len(diagnostics) > 0 {
t.Fatal("AnalyzeValues failed with error", diagnostics)
}
}
}
func TestInvalidOptionsExample(t *testing.T) {
input := utils.Dedent(`
LABEL=test /mnt/test btrfs subvol=backup,fat=32 0 0
`)
p := parser.FstabParser{}
p.Clear()
p := ast.NewFstabConfig()
errors := p.ParseFromContent(input)
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatal("ParseFromContent returned error", errors)
}
}
func TestIncompleteExample(t *testing.T) {
input := utils.Dedent(`
LABEL=test /mnt/test defaults 0 0
`)
p := ast.NewFstabConfig()
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatal("ParseFromContent returned error", errors)
}
// Get hover for first field
println("Checking values")
{
diagnostics := p.AnalyzeValues()
if len(diagnostics) == 0 {
t.Fatal("AnalyzeValues should have returned error")
rawFirstEntry, _ := p.Entries.Get(uint32(0))
firstEntry := rawFirstEntry.(*ast.FstabEntry)
name := firstEntry.GetFieldAtPosition(common.CursorPosition(0))
if !(name == ast.FstabFieldSpec) {
t.Errorf("GetFieldAtPosition failed to return correct field name. Got: %v but expected: %v", name, ast.FstabFieldSpec)
}
name = firstEntry.GetFieldAtPosition(common.CursorPosition(9))
if !(name == ast.FstabFieldSpec) {
t.Errorf("GetFieldAtPosition failed to return correct field name. Got: %v but expected: %v", name, ast.FstabFieldSpec)
}
name = firstEntry.GetFieldAtPosition(common.CursorPosition(21))
if !(name == ast.FstabFieldFileSystemType) {
t.Errorf("GetFieldAtPosition failed to return correct field name. Got: %v but expected: %v", name, ast.FstabFieldFileSystemType)
}
}
@ -150,14 +156,32 @@ UUID=0a3407de-014b-458b-b5c1-848e92a327a3 / ext4 defaults 0 1
UUID=f9fe0b69-a280-415d-a03a-a32752370dee none swap defaults 0 0
UUID=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2
`)
p := parser.FstabParser{}
p.Clear()
p := ast.NewFstabConfig()
errors := p.ParseFromContent(input)
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors)
}
}
func TestArchExample1WithComments(t *testing.T) {
input := utils.Dedent(`
# Hello there!
UUID=0a3407de-014b-458b-b5c1-848e92a327a3 / ext4 defaults 0 1
# How are you?
UUID=f9fe0b69-a280-415d-a03a-a32752370dee none swap defaults 0 0 # And I am trailing!
UUID=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2 # I am tailing too!
`)
p := ast.NewFstabConfig()
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors)
}
}
func TestArchExample2(t *testing.T) {
@ -167,10 +191,9 @@ func TestArchExample2(t *testing.T) {
/dev/sda3 /home ext4 defaults 0 2
/dev/sda4 none swap defaults 0 0
`)
p := parser.FstabParser{}
p.Clear()
p := ast.NewFstabConfig()
errors := p.ParseFromContent(input)
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors)
@ -184,14 +207,14 @@ LABEL=System / ext4 defaults 0 1
LABEL=Data /home ext4 defaults 0 2
LABEL=Swap none swap defaults 0 0
`)
p := parser.FstabParser{}
p.Clear()
p := ast.NewFstabConfig()
errors := p.ParseFromContent(input)
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors)
}
}
func TestArchExample4(t *testing.T) {
@ -201,10 +224,9 @@ UUID=0a3407de-014b-458b-b5c1-848e92a327a3 / ext4 defaults 0 1
UUID=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2
UUID=f9fe0b69-a280-415d-a03a-a32752370dee none swap defaults 0 0
`)
p := parser.FstabParser{}
p.Clear()
p := ast.NewFstabConfig()
errors := p.ParseFromContent(input)
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors)
@ -218,10 +240,9 @@ PARTLABEL=GNU/Linux / ext4 defaults 0 1
PARTLABEL=Home /home ext4 defaults 0 2
PARTLABEL=Swap none swap defaults 0 0
`)
p := parser.FstabParser{}
p.Clear()
p := ast.NewFstabConfig()
errors := p.ParseFromContent(input)
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors)
@ -235,13 +256,74 @@ PARTUUID=98a81274-10f7-40db-872a-03df048df366 / ext4 defaults 0 1
PARTUUID=7280201c-fc5d-40f2-a9b2-466611d3d49e /home ext4 defaults 0 2
PARTUUID=039b6c1c-7553-4455-9537-1befbc9fbc5b none swap defaults 0 0
`)
p := parser.FstabParser{}
p.Clear()
p := ast.NewFstabConfig()
errors := p.ParseFromContent(input)
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors)
}
}
func TestLinuxConfigExample(t *testing.T) {
input := utils.Dedent(`
UUID=80b496fa-ce2d-4dcf-9afc-bcaa731a67f1 /mnt/example ext4 defaults 0 2
`)
p := ast.NewFstabConfig()
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors)
}
}
func Test1(t *testing.T) {
input := utils.Dedent(`
PARTLABEL="rootfs" / ext4 noatime,lazytime,rw 0 0
`)
p := ast.NewFstabConfig()
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors)
}
}
func Test2(t *testing.T) {
input := utils.Dedent(`
/dev/sda /home1 xfs defaults 1 2
/dev/sdb /homeB xfs noauto,nobarrier,rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0
/dev/sdc /homeC xfs noauto,defaults 0 0
/dev/sdd /homeD xfs noauto,rw,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0
/dev/sde /homeE xfs defaults 0 0
`)
p := ast.NewFstabConfig()
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors)
}
}
func Test3(t *testing.T) {
input := utils.Dedent(`
/dev/disk/by-label/dsp /dsp auto ro
/dev/disk/by-partlabel/modem_a /firmware auto ro
/dev/disk/by-partlabel/persist /persist auto ro,discard,nosuid,nodev,noexec
/dev/disk/by-partlabel/userdata /data auto discard,noatime,nodiratime,nosuid,nodev,nofail 0 0
/dev/disk/by-partlabel/cache /cache ext4 relatime,data=ordered,noauto_da_alloc,discard,noexec,nodev,nosuid,x-systemd.makefs 0 0
/dev/nvme0n1 /data/media auto discard,nosuid,nodev,nofail,x-systemd.device-timeout=5s 0 0
tmpfs /var tmpfs rw,nosuid,nodev,size=128M,mode=755 0 0
tmpfs /tmp tmpfs rw,nosuid,nodev,size=150M,mode=1777 0 0
`)
p := ast.NewFstabConfig()
errors := p.Parse(input)
if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors)
}
}

View File

@ -1,70 +1,93 @@
package handlers
import (
"config-lsp/doc-values"
"config-lsp/handlers/fstab/documentation"
"config-lsp/handlers/fstab/parser"
"config-lsp/common"
"config-lsp/handlers/fstab/ast"
"config-lsp/handlers/fstab/fields"
"fmt"
"github.com/tliron/glsp/protocol_3_16"
)
func GetCompletion(
line parser.FstabLine,
cursor uint32,
entry *ast.FstabEntry,
cursor common.CursorPosition,
) ([]protocol.CompletionItem, error) {
targetField := line.GetFieldAtPosition(cursor)
targetField := entry.GetFieldAtPosition(cursor)
switch targetField {
case parser.FstabFieldSpec:
value, cursor := GetFieldSafely(line.Fields.Spec, cursor)
case ast.FstabFieldSpec:
value, cursor := getFieldSafely(entry.Fields.Spec, cursor)
return fstabdocumentation.SpecField.DeprecatedFetchCompletions(
return fields.SpecField.DeprecatedFetchCompletions(
value,
cursor,
), nil
case parser.FstabFieldMountPoint:
value, cursor := GetFieldSafely(line.Fields.MountPoint, cursor)
case ast.FstabFieldMountPoint:
value, cursor := getFieldSafely(entry.Fields.MountPoint, cursor)
return fstabdocumentation.MountPointField.DeprecatedFetchCompletions(
return fields.MountPointField.DeprecatedFetchCompletions(
value,
cursor,
), nil
case parser.FstabFieldFileSystemType:
value, cursor := GetFieldSafely(line.Fields.FilesystemType, cursor)
case ast.FstabFieldFileSystemType:
value, cursor := getFieldSafely(entry.Fields.FilesystemType, cursor)
return fstabdocumentation.FileSystemTypeField.DeprecatedFetchCompletions(
return fields.FileSystemTypeField.DeprecatedFetchCompletions(
value,
cursor,
), nil
case parser.FstabFieldOptions:
fileSystemType := line.Fields.FilesystemType.Value
case ast.FstabFieldOptions:
line, cursor := getFieldSafely(entry.Fields.Options, cursor)
fileSystemType := entry.Fields.FilesystemType.Value.Value
completions := make([]protocol.CompletionItem, 0, 50)
var optionsField docvalues.DeprecatedValue
for _, completion := range fields.DefaultMountOptionsField.DeprecatedFetchCompletions(line, cursor) {
var documentation string
if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found {
optionsField = foundField
} else {
optionsField = fstabdocumentation.DefaultMountOptionsField
switch completion.Documentation.(type) {
case string:
documentation = completion.Documentation.(string)
case *string:
documentation = *completion.Documentation.(*string)
}
value, cursor := GetFieldSafely(line.Fields.Options, cursor)
completion.Documentation = protocol.MarkupContent{
Kind: protocol.MarkupKindMarkdown,
Value: documentation + "\n\n" + "From: _Default Mount Options_",
}
completions = append(completions, completion)
}
completions := optionsField.DeprecatedFetchCompletions(
value,
cursor,
)
for _, completion := range entry.FetchMountOptionsField(false).DeprecatedFetchCompletions(line, cursor) {
var documentation string
switch completion.Documentation.(type) {
case string:
documentation = completion.Documentation.(string)
case *string:
documentation = *completion.Documentation.(*string)
}
completion.Documentation = protocol.MarkupContent{
Kind: protocol.MarkupKindMarkdown,
Value: documentation + "\n\n" + fmt.Sprintf("From: _%s_", fileSystemType),
}
completions = append(completions, completion)
}
return completions, nil
case parser.FstabFieldFreq:
value, cursor := GetFieldSafely(line.Fields.Freq, cursor)
case ast.FstabFieldFreq:
value, cursor := getFieldSafely(entry.Fields.Freq, cursor)
return fstabdocumentation.FreqField.DeprecatedFetchCompletions(
return fields.FreqField.DeprecatedFetchCompletions(
value,
cursor,
), nil
case parser.FstabFieldPass:
value, cursor := GetFieldSafely(line.Fields.Pass, cursor)
case ast.FstabFieldPass:
value, cursor := getFieldSafely(entry.Fields.Pass, cursor)
return fstabdocumentation.PassField.DeprecatedFetchCompletions(
return fields.PassField.DeprecatedFetchCompletions(
value,
cursor,
), nil
@ -75,14 +98,18 @@ func GetCompletion(
// Safely get value and new cursor position
// If field is nil, return empty string and 0
func GetFieldSafely(field *parser.Field, character uint32) (string, uint32) {
func getFieldSafely(field *ast.FstabField, cursor common.CursorPosition) (string, uint32) {
if field == nil {
return "", 0
}
if field.Value == "" {
if field.Value.Value == "" {
return "", 0
}
return field.Value, character - field.Start
if uint32(cursor) < field.Start.Character {
return "", 0
}
return field.Value.Raw, common.CursorToCharacterIndex(uint32(cursor) - field.Start.Character)
}

View File

@ -1,36 +1,36 @@
package handlers
import (
"config-lsp/doc-values"
"config-lsp/handlers/fstab/documentation"
"config-lsp/handlers/fstab/parser"
"github.com/tliron/glsp/protocol_3_16"
"config-lsp/common"
"config-lsp/handlers/fstab/ast"
"strings"
"github.com/tliron/glsp/protocol_3_16"
)
func GetHoverInfo(entry *parser.FstabEntry, cursor uint32) (*protocol.Hover, error) {
line := entry.Line
targetField := line.GetFieldAtPosition(cursor)
func GetHoverInfo(
line uint32,
index common.IndexPosition,
entry *ast.FstabEntry,
) (*protocol.Hover, error) {
targetField := entry.GetFieldAtPosition(index)
switch targetField {
case parser.FstabFieldSpec:
case ast.FstabFieldSpec:
return &SpecHoverField, nil
case parser.FstabFieldMountPoint:
case ast.FstabFieldMountPoint:
return &MountPointHoverField, nil
case parser.FstabFieldFileSystemType:
case ast.FstabFieldFileSystemType:
return &FileSystemTypeField, nil
case parser.FstabFieldOptions:
fileSystemType := line.Fields.FilesystemType.Value
var optionsField docvalues.DeprecatedValue
case ast.FstabFieldOptions:
optionsField := entry.FetchMountOptionsField(true)
if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found {
optionsField = foundField
} else {
optionsField = fstabdocumentation.DefaultMountOptionsField
if optionsField == nil {
return nil, nil
}
relativeCursor := cursor - line.Fields.Options.Start
fieldInfo := optionsField.DeprecatedFetchHoverInfo(line.Fields.Options.Value, relativeCursor)
relativeCursor := uint32(index) - entry.Fields.Options.Start.Character
fieldInfo := optionsField.DeprecatedFetchHoverInfo(entry.Fields.Options.Value.Value, relativeCursor)
hover := protocol.Hover{
Contents: protocol.MarkupContent{
@ -40,9 +40,9 @@ func GetHoverInfo(entry *parser.FstabEntry, cursor uint32) (*protocol.Hover, err
}
return &hover, nil
case parser.FstabFieldFreq:
case ast.FstabFieldFreq:
return &FreqHoverField, nil
case parser.FstabFieldPass:
case ast.FstabFieldPass:
return &PassHoverField, nil
}

View File

@ -2,9 +2,9 @@ package lsp
import (
"config-lsp/common"
fstabdocumentation "config-lsp/handlers/fstab/documentation"
"config-lsp/handlers/fstab/ast"
fstabdocumentation "config-lsp/handlers/fstab/fields"
"config-lsp/handlers/fstab/handlers"
"config-lsp/handlers/fstab/parser"
"config-lsp/handlers/fstab/shared"
"github.com/tliron/glsp"
@ -12,9 +12,10 @@ import (
)
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) {
p := shared.DocumentParserMap[params.TextDocument.URI]
d := shared.DocumentParserMap[params.TextDocument.URI]
cursor := common.LSPCharacterAsCursorPosition(params.Position.Character)
entry, found := p.GetEntry(params.Position.Line)
rawEntry, found := d.Config.Entries.Get(params.Position.Line)
if !found {
// Empty line, return spec completions
@ -24,12 +25,7 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa
), nil
}
if entry.Type == parser.FstabEntryTypeComment {
return nil, nil
}
entry := rawEntry.(*ast.FstabEntry)
cursor := common.CursorToCharacterIndex(params.Position.Character)
line := entry.Line
return handlers.GetCompletion(line, cursor)
return handlers.GetCompletion(entry, cursor)
}

View File

@ -2,8 +2,10 @@ package lsp
import (
"config-lsp/common"
"config-lsp/handlers/fstab/analyzer"
"config-lsp/handlers/fstab/shared"
"config-lsp/utils"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
@ -15,21 +17,21 @@ func TextDocumentDidChange(
content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text
common.ClearDiagnostics(context, params.TextDocument.URI)
p := shared.DocumentParserMap[params.TextDocument.URI]
p.Clear()
d := shared.DocumentParserMap[params.TextDocument.URI]
d.Config.Clear()
diagnostics := make([]protocol.Diagnostic, 0)
errors := p.ParseFromContent(content)
errors := d.Config.Parse(content)
if len(errors) > 0 {
diagnostics = append(diagnostics, utils.Map(
errors,
func(err common.ParseError) protocol.Diagnostic {
func(err common.LSPError) protocol.Diagnostic {
return err.ToDiagnostic()
},
)...)
} else {
diagnostics = append(diagnostics, p.AnalyzeValues()...)
diagnostics = append(diagnostics, analyzer.Analyze(d)...)
}
if len(diagnostics) > 0 {

View File

@ -2,9 +2,11 @@ package lsp
import (
"config-lsp/common"
"config-lsp/handlers/fstab/parser"
"config-lsp/handlers/fstab/analyzer"
"config-lsp/handlers/fstab/ast"
"config-lsp/handlers/fstab/shared"
"config-lsp/utils"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
@ -15,24 +17,26 @@ func TextDocumentDidOpen(
) error {
common.ClearDiagnostics(context, params.TextDocument.URI)
p := parser.FstabParser{}
p.Clear()
shared.DocumentParserMap[params.TextDocument.URI] = &p
config := ast.NewFstabConfig()
d := &shared.FstabDocument{
Config: config,
}
shared.DocumentParserMap[params.TextDocument.URI] = d
content := params.TextDocument.Text
diagnostics := make([]protocol.Diagnostic, 0)
errors := p.ParseFromContent(content)
errors := d.Config.Parse(content)
if len(errors) > 0 {
diagnostics = append(diagnostics, utils.Map(
errors,
func(err common.ParseError) protocol.Diagnostic {
func(err common.LSPError) protocol.Diagnostic {
return err.ToDiagnostic()
},
)...)
} else {
diagnostics = append(diagnostics, p.AnalyzeValues()...)
diagnostics = append(diagnostics, analyzer.Analyze(d)...)
}
if len(diagnostics) > 0 {

View File

@ -1,29 +1,33 @@
package lsp
import (
"config-lsp/common"
"config-lsp/handlers/fstab/ast"
"config-lsp/handlers/fstab/handlers"
"config-lsp/handlers/fstab/parser"
"config-lsp/handlers/fstab/shared"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
cursor := params.Position.Character
line := params.Position.Line
index := common.LSPCharacterAsIndexPosition(params.Position.Character)
p := shared.DocumentParserMap[params.TextDocument.URI]
d := shared.DocumentParserMap[params.TextDocument.URI]
entry, found := p.GetEntry(params.Position.Line)
rawEntry, found := d.Config.Entries.Get(params.Position.Line)
// Empty line
if !found {
return nil, nil
}
// Comment line
if entry.Type == parser.FstabEntryTypeComment {
return nil, nil
}
entry := rawEntry.(*ast.FstabEntry)
return handlers.GetHoverInfo(entry, cursor)
return handlers.GetHoverInfo(
line,
index,
entry,
)
}

View File

@ -1,387 +0,0 @@
package parser
import (
"config-lsp/common"
docvalues "config-lsp/doc-values"
fstabdocumentation "config-lsp/handlers/fstab/documentation"
"fmt"
"regexp"
"strings"
"github.com/emirpasic/gods/maps/treemap"
protocol "github.com/tliron/glsp/protocol_3_16"
gods "github.com/emirpasic/gods/utils"
)
var commentPattern = regexp.MustCompile(`^\s*#`)
var ignoreLinePattern = regexp.MustCompile(`^\s*$`)
var whitespacePattern = regexp.MustCompile(`\S+`)
type MalformedLineError struct{}
func (e MalformedLineError) Error() string {
return "Malformed line"
}
type Field struct {
Value string
Start uint32
End uint32
}
func (f Field) String() string {
return fmt.Sprintf("%v [%v-%v]", f.Value, f.Start, f.End)
}
func (f *Field) CreateRange(fieldLine uint32) protocol.Range {
return protocol.Range{
Start: protocol.Position{
Line: fieldLine,
Character: f.Start,
},
End: protocol.Position{
Line: fieldLine,
Character: f.End,
},
}
}
type FstabField string
const (
FstabFieldSpec FstabField = "spec"
FstabFieldMountPoint FstabField = "mountpoint"
FstabFieldFileSystemType FstabField = "filesystemtype"
FstabFieldOptions FstabField = "options"
FstabFieldFreq FstabField = "freq"
FstabFieldPass FstabField = "pass"
)
type FstabFields struct {
Spec *Field
MountPoint *Field
FilesystemType *Field
Options *Field
Freq *Field
Pass *Field
}
func (f FstabFields) String() string {
return fmt.Sprintf(
"Spec: %s, MountPoint: %s, FilesystemType: %s, Options: %s, Freq: %s, Pass: %s",
f.Spec,
f.MountPoint,
f.FilesystemType,
f.Options,
f.Freq,
f.Pass,
)
}
type FstabLine struct {
Line uint32
Fields FstabFields
}
func (e *FstabLine) CheckIsValid() []protocol.Diagnostic {
diagnostics := make([]protocol.Diagnostic, 0)
if e.Fields.Spec != nil {
errors := fstabdocumentation.SpecField.DeprecatedCheckIsValid(e.Fields.Spec.Value)
if len(errors) > 0 {
diagnostics = append(
diagnostics,
docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.Spec.Start, errors)...,
)
}
}
if e.Fields.MountPoint != nil {
errors := fstabdocumentation.MountPointField.DeprecatedCheckIsValid(e.Fields.MountPoint.Value)
if len(errors) > 0 {
diagnostics = append(
diagnostics,
docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.MountPoint.Start, errors)...,
)
}
}
var fileSystemType string = ""
if e.Fields.FilesystemType != nil {
errors := fstabdocumentation.FileSystemTypeField.DeprecatedCheckIsValid(e.Fields.FilesystemType.Value)
if len(errors) > 0 {
diagnostics = append(
diagnostics,
docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.FilesystemType.Start, errors)...,
)
} else {
fileSystemType = e.Fields.FilesystemType.Value
}
}
if e.Fields.Options != nil && fileSystemType != "" {
var optionsField docvalues.DeprecatedValue
if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found {
optionsField = foundField
} else {
optionsField = fstabdocumentation.DefaultMountOptionsField
}
errors := optionsField.DeprecatedCheckIsValid(e.Fields.Options.Value)
if len(errors) > 0 {
diagnostics = append(
diagnostics,
docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.Options.Start, errors)...,
)
}
}
if e.Fields.Freq != nil {
errors := fstabdocumentation.FreqField.DeprecatedCheckIsValid(e.Fields.Freq.Value)
if len(errors) > 0 {
diagnostics = append(
diagnostics,
docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.Freq.Start, errors)...,
)
}
}
if e.Fields.Pass != nil {
errors := fstabdocumentation.PassField.DeprecatedCheckIsValid(e.Fields.Pass.Value)
if len(errors) > 0 {
diagnostics = append(
diagnostics,
docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.Pass.Start, errors)...,
)
}
}
return diagnostics
}
func (e FstabLine) GetFieldAtPosition(cursor uint32) FstabField {
if e.Fields.Spec == nil || (cursor >= e.Fields.Spec.Start && cursor <= e.Fields.Spec.End) {
return FstabFieldSpec
}
if e.Fields.MountPoint == nil || (cursor >= e.Fields.MountPoint.Start && cursor <= e.Fields.MountPoint.End) {
return FstabFieldMountPoint
}
if e.Fields.FilesystemType == nil || (cursor >= e.Fields.FilesystemType.Start && cursor <= e.Fields.FilesystemType.End) {
return FstabFieldFileSystemType
}
if e.Fields.Options == nil || (cursor >= e.Fields.Options.Start && cursor <= e.Fields.Options.End) {
return FstabFieldOptions
}
if e.Fields.Freq == nil || (cursor >= e.Fields.Freq.Start && cursor <= e.Fields.Freq.End) {
return FstabFieldFreq
}
return FstabFieldPass
}
type FstabEntryType string
const (
FstabEntryTypeLine FstabEntryType = "line"
FstabEntryTypeComment FstabEntryType = "comment"
)
type FstabEntry struct {
Type FstabEntryType
Line FstabLine
}
type FstabParser struct {
// [uint32]FstabEntry - line number to entry mapping
Entries *treemap.Map
}
func (p *FstabParser) AddLine(line string, lineNumber int) error {
fields := whitespacePattern.FindAllStringIndex(line, -1)
if len(fields) == 0 {
return MalformedLineError{}
}
var spec *Field
var mountPoint *Field
var filesystemType *Field
var options *Field
var freq *Field
var pass *Field
switch len(fields) {
case 6:
field := fields[5]
start := uint32(field[0])
end := uint32(field[1])
pass = &Field{
Value: line[start:end],
Start: start,
End: end,
}
fallthrough
case 5:
field := fields[4]
start := uint32(field[0])
end := uint32(field[1])
freq = &Field{
Value: line[start:end],
Start: start,
End: end,
}
fallthrough
case 4:
field := fields[3]
start := uint32(field[0])
end := uint32(field[1])
options = &Field{
Value: line[start:end],
Start: start,
End: end,
}
fallthrough
case 3:
field := fields[2]
start := uint32(field[0])
end := uint32(field[1])
filesystemType = &Field{
Value: line[start:end],
Start: start,
End: end,
}
fallthrough
case 2:
field := fields[1]
start := uint32(field[0])
end := uint32(field[1])
mountPoint = &Field{
Value: line[start:end],
Start: start,
End: end,
}
fallthrough
case 1:
field := fields[0]
start := uint32(field[0])
end := uint32(field[1])
spec = &Field{
Value: line[start:end],
Start: start,
End: end,
}
}
entry := FstabEntry{
Type: FstabEntryTypeLine,
Line: FstabLine{
Line: uint32(lineNumber),
Fields: FstabFields{
Spec: spec,
MountPoint: mountPoint,
FilesystemType: filesystemType,
Options: options,
Freq: freq,
Pass: pass,
},
},
}
p.Entries.Put(entry.Line.Line, entry)
return nil
}
func (p *FstabParser) AddCommentLine(line string, lineNumber int) {
entry := FstabLine{
Line: uint32(lineNumber),
}
p.Entries.Put(entry.Line, FstabEntry{
Type: FstabEntryTypeComment,
Line: entry,
})
}
func (p *FstabParser) ParseFromContent(content string) []common.ParseError {
errors := []common.ParseError{}
lines := strings.Split(content, "\n")
for index, line := range lines {
if ignoreLinePattern.MatchString(line) {
continue
}
if commentPattern.MatchString(line) {
p.AddCommentLine(line, index)
continue
}
err := p.AddLine(line, index)
if err != nil {
errors = append(errors, common.ParseError{
Line: uint32(index),
Err: err,
})
}
}
return errors
}
func (p *FstabParser) GetEntry(line uint32) (*FstabEntry, bool) {
rawEntry, found := p.Entries.Get(line)
if !found {
return nil, false
}
entry := rawEntry.(FstabEntry)
return &entry, true
}
func (p *FstabParser) Clear() {
p.Entries = treemap.NewWith(gods.UInt32Comparator)
}
func (p *FstabParser) AnalyzeValues() []protocol.Diagnostic {
diagnostics := []protocol.Diagnostic{}
it := p.Entries.Iterator()
for it.Next() {
entry := it.Value().(FstabEntry)
switch entry.Type {
case FstabEntryTypeLine:
newDiagnostics := entry.Line.CheckIsValid()
if len(newDiagnostics) > 0 {
diagnostics = append(diagnostics, newDiagnostics...)
}
case FstabEntryTypeComment:
// Do nothing
}
}
return diagnostics
}

View File

@ -1,8 +1,13 @@
package shared
import (
"config-lsp/handlers/fstab/parser"
"config-lsp/handlers/fstab/ast"
protocol "github.com/tliron/glsp/protocol_3_16"
)
var DocumentParserMap = map[protocol.DocumentUri]*parser.FstabParser{}
type FstabDocument struct {
Config *ast.FstabConfig
}
var DocumentParserMap = map[protocol.DocumentUri]*FstabDocument{}

View File

@ -0,0 +1,25 @@
package testutils_test
import (
"config-lsp/handlers/fstab/ast"
"config-lsp/handlers/fstab/shared"
"config-lsp/utils"
"testing"
)
func DocumentFromInput(
t *testing.T,
content string,
) *shared.FstabDocument {
input := utils.Dedent(content)
c := ast.NewFstabConfig()
errors := c.Parse(input)
if len(errors) > 0 {
t.Fatalf("Parse error: %v", errors)
}
return &shared.FstabDocument{
Config: c,
}
}

View File

@ -2,6 +2,7 @@ package analyzer
import (
"config-lsp/common"
docvalues "config-lsp/doc-values"
"config-lsp/handlers/ssh_config/ast"
"config-lsp/handlers/ssh_config/fields"
"config-lsp/utils"
@ -40,7 +41,7 @@ func checkOption(
checkIsUsingDoubleQuotes(ctx, option.Key.Value, option.Key.LocationRange)
checkQuotesAreClosed(ctx, option.Key.Value, option.Key.LocationRange)
_, found := fields.Options[option.Key.Key]
docOption, found := fields.Options[option.Key.Key]
if !found {
// Diagnostics will be handled by `values.go`
@ -61,6 +62,19 @@ func checkOption(
if option.OptionValue != nil {
checkIsUsingDoubleQuotes(ctx, option.OptionValue.Value, option.OptionValue.LocationRange)
checkQuotesAreClosed(ctx, option.OptionValue.Value, option.OptionValue.LocationRange)
invalidValues := docOption.DeprecatedCheckIsValid(option.OptionValue.Value.Value)
for _, invalidValue := range invalidValues {
err := docvalues.LSPErrorFromInvalidValue(option.Start.Line, *invalidValue)
err.ShiftCharacter(option.OptionValue.Start.Character)
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: err.Range.ToLSPRange(),
Message: err.Err.Error(),
Severity: &common.SeverityError,
})
}
}
if option.Separator == nil || option.Separator.Value.Value == "" {

View File

@ -42,7 +42,7 @@ User root
analyzeStructureIsValid(ctx)
if len(ctx.diagnostics) != 0 {
if len(ctx.diagnostics) != 1 {
t.Fatalf("Expected no errors, got %v", ctx.diagnostics)
}
}
@ -85,8 +85,8 @@ Match
analyzeStructureIsValid(ctx)
if len(ctx.diagnostics) != 1 {
t.Fatalf("Expected 1 error, got %v", ctx.diagnostics)
if len(ctx.diagnostics) != 2 {
t.Fatalf("Expected 2 errors (separator error and value error), got %v", ctx.diagnostics)
}
}
@ -108,7 +108,7 @@ Match
analyzeStructureIsValid(ctx)
if len(ctx.diagnostics) != 0 {
t.Fatalf("Expected no errors, got %v", ctx.diagnostics)
if len(ctx.diagnostics) != 1 {
t.Fatalf("Expected 1 error, got %v", ctx.diagnostics)
}
}

View File

@ -26,6 +26,13 @@ func createListenerContext() *sshListenerContext {
return context
}
type sshParserListener struct {
*parser.BaseConfigListener
Config *SSHConfig
Errors []common.LSPError
sshContext *sshListenerContext
}
func createListener(
config *SSHConfig,
context *sshListenerContext,
@ -37,13 +44,6 @@ func createListener(
}
}
type sshParserListener struct {
*parser.BaseConfigListener
Config *SSHConfig
Errors []common.LSPError
sshContext *sshListenerContext
}
func (s *sshParserListener) EnterEntry(ctx *parser.EntryContext) {
location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext)
location.ChangeBothLines(s.sshContext.line)

View File

@ -5,6 +5,9 @@ ROOT=$(git rev-parse --show-toplevel)/server
# aliases
cd $ROOT/handlers/aliases && antlr4 -Dlanguage=Go -o ast/parser Aliases.g4
# fstab
cd $ROOT/hanlders/fstab && antlr4 -Dlanguage=Go -o ast/parser Fstab.g4
# sshd_config
cd $ROOT/handlers/sshd_config && antlr4 -Dlanguage=Go -o ast/parser Config.g4
cd $ROOT/handlers/sshd_config/match-parser && antlr4 -Dlanguage=Go -o parser Match.g4