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

@ -20,7 +20,7 @@ var BtrfsDocumentationAssignable = map[docvalues.EnumString]docvalues.Deprecated
Values: []docvalues.DeprecatedValue{ Values: []docvalues.DeprecatedValue{
docvalues.EnumValue{ docvalues.EnumValue{
Values: []docvalues.EnumString{ Values: []docvalues.EnumString{
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"no", "no",
"No compression, used for remounting.", "No compression, used for remounting.",
), ),
@ -38,7 +38,7 @@ var BtrfsDocumentationAssignable = map[docvalues.EnumString]docvalues.Deprecated
15, 15,
), ),
}, },
Separator: ":", Separator: ":",
ValueIsOptional: true, ValueIsOptional: true,
}, },
}, },

View File

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

View File

@ -165,3 +165,25 @@ func TestStringsIncompleteQuotes3FullFeatures(
t.Errorf("Expected %v, got %v", expected, actual) 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,worl[d]
// hello,world[,] // hello,world[,]
// hello[,]world,how,are,you // hello[,]world,how,are,you
relativePosition, found := utils.FindPreviousCharacter( relativePosition, found := utils.FindPreviousCharacter(
line, line,
v.Separator, v.Separator,

View File

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

View File

@ -108,13 +108,14 @@ func (v KeyValueAssignmentValue) DeprecatedFetchCompletions(line string, cursor
return v.Key.DeprecatedFetchCompletions(line, cursor) return v.Key.DeprecatedFetchCompletions(line, cursor)
} }
relativePosition, found := utils.FindPreviousCharacter( foundPosition, found := utils.FindPreviousCharacter(
line, line,
v.Separator, v.Separator,
int(cursor), int(cursor),
) )
if found { if found {
relativePosition := max(1, foundPosition) - 1
selectedKey := line[:uint32(relativePosition)] selectedKey := line[:uint32(relativePosition)]
line = line[uint32(relativePosition+len(v.Separator)):] line = line[uint32(relativePosition+len(v.Separator)):]
cursor -= uint32(relativePosition) 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" import docvalues "config-lsp/doc-values"

View File

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

View File

@ -1,4 +1,4 @@
package fstabdocumentation package fields
import ( import (
commondocumentation "config-lsp/common-documentation" commondocumentation "config-lsp/common-documentation"
@ -6,7 +6,7 @@ import (
"strings" "strings"
) )
var mountOptionsExtractor = func(value string) string { var MountOptionsExtractor = func(value string) string {
separatorIndex := strings.Index(value, "=") separatorIndex := strings.Index(value, "=")
if separatorIndex == -1 { 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 // From https://www.man7.org/linux/man-pages/man8/mount.8.html
var defaultOptions = []docvalues.EnumString{ var DefaultOptions = []docvalues.EnumString{
// Default options // Default options
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"async", "async",
@ -39,22 +39,6 @@ var defaultOptions = []docvalues.EnumString{
"noauto", "noauto",
"Can only be mounted explicitly (i.e., the -a option will not cause the filesystem to be mounted).", "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( docvalues.CreateEnumStringWithDoc(
"defaults", "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.", "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.",
@ -193,13 +177,13 @@ var defaultOptions = []docvalues.EnumString{
), ),
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"x-systemd.automount", "x-systemd.automount",
`An automount unit will be created for the file system. See systemd.automount(5) for details. `An automount unit will be created for the file system. See systemd.automount(5) for details.
Added in version 215.`, Added in version 215.`,
), ),
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"x-systemd.makefs", "x-systemd.makefs",
`The file system will be initialized on the device. If the device is not "empty", i.e. it contains any signature, the operation will be skipped. It is hence expected that this option remains set even after the device has been initialized. `The file system will be initialized on the device. If the device is not "empty", i.e. it contains any signature, the operation will be skipped. It is hence expected that this option remains set even after the device has been initialized.
Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file. Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file.
@ -211,7 +195,7 @@ Added in version 236.`,
), ),
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"x-systemd.growfs", "x-systemd.growfs",
`The file system will be grown to occupy the full block device. If the file system is already at maximum size, no action will be performed. It is hence expected that this option remains set even after the file system has been grown. Only certain file system types are supported, see systemd-makefs@.service(8) for details. `The file system will be grown to occupy the full block device. If the file system is already at maximum size, no action will be performed. It is hence expected that this option remains set even after the file system has been grown. Only certain file system types are supported, see systemd-makefs@.service(8) for details.
Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file. Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file.
@ -219,7 +203,7 @@ Added in version 236.`,
), ),
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"x-systemd.pcrfs", "x-systemd.pcrfs",
`Measures file system identity information (mount point, type, label, UUID, partition label, partition UUID) into PCR 15 after the file system has been mounted. This ensures the systemd-pcrfs@.service(8) or systemd-pcrfs-root.service services are pulled in by the mount unit. `Measures file system identity information (mount point, type, label, UUID, partition label, partition UUID) into PCR 15 after the file system has been mounted. This ensures the systemd-pcrfs@.service(8) or systemd-pcrfs-root.service services are pulled in by the mount unit.
Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file. It is also implied for the root and /usr/ partitions discovered by systemd-gpt-auto-generator(8). Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file. It is also implied for the root and /usr/ partitions discovered by systemd-gpt-auto-generator(8).
@ -227,13 +211,13 @@ Added in version 253.`,
), ),
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"x-systemd.rw-only", "x-systemd.rw-only",
`If a mount operation fails to mount the file system read-write, it normally tries mounting the file system read-only instead. This option disables that behaviour, and causes the mount to fail immediately instead. This option is translated into the ReadWriteOnly= setting in a unit file. `If a mount operation fails to mount the file system read-write, it normally tries mounting the file system read-only instead. This option disables that behaviour, and causes the mount to fail immediately instead. This option is translated into the ReadWriteOnly= setting in a unit file.
Added in version 246.`, Added in version 246.`,
), ),
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"x-initrd.mount", "x-initrd.mount",
`An additional filesystem to be mounted in the initrd. See initrd-fs.target description in systemd.special(7). This is both an indicator to the initrd to mount this partition early and an indicator to the host to leave the partition mounted until final shutdown. Or in other words, if this flag is set it is assumed the mount shall be active during the entire regular runtime of the system, i.e. established before the initrd transitions into the host all the way until the host transitions to the final shutdown phase. `An additional filesystem to be mounted in the initrd. See initrd-fs.target description in systemd.special(7). This is both an indicator to the initrd to mount this partition early and an indicator to the host to leave the partition mounted until final shutdown. Or in other words, if this flag is set it is assumed the mount shall be active during the entire regular runtime of the system, i.e. established before the initrd transitions into the host all the way until the host transitions to the final shutdown phase.
Added in version 215.`, Added in version 215.`,
), ),
@ -244,7 +228,7 @@ type assignOption struct {
Handler func(context docvalues.KeyValueAssignmentContext) docvalues.DeprecatedValue Handler func(context docvalues.KeyValueAssignmentContext) docvalues.DeprecatedValue
} }
var defaultAssignOptions = map[docvalues.EnumString]docvalues.DeprecatedValue{ var DefaultAssignOptions = map[docvalues.EnumString]docvalues.DeprecatedValue{
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"context", "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\".", "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\".",
@ -299,19 +283,19 @@ Added in version 245.`,
): docvalues.StringValue{}, ): docvalues.StringValue{},
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"x-systemd.wants-mounts-for", "x-systemd.wants-mounts-for",
`Configures a RequiresMountsFor= or WantsMountsFor= dependency between the created mount unit and other mount units. The argument must be an absolute path. This option may be specified more than once. See RequiresMountsFor= or WantsMountsFor= in systemd.unit(5) for details. `Configures a RequiresMountsFor= or WantsMountsFor= dependency between the created mount unit and other mount units. The argument must be an absolute path. This option may be specified more than once. See RequiresMountsFor= or WantsMountsFor= in systemd.unit(5) for details.
Added in version 220.`, Added in version 220.`,
): docvalues.StringValue{}, ): docvalues.StringValue{},
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"x-systemd.requires-mounts-for", "x-systemd.requires-mounts-for",
`Configures a RequiresMountsFor= or WantsMountsFor= dependency between the created mount unit and other mount units. The argument must be an absolute path. This option may be specified more than once. See RequiresMountsFor= or WantsMountsFor= in systemd.unit(5) for details. `Configures a RequiresMountsFor= or WantsMountsFor= dependency between the created mount unit and other mount units. The argument must be an absolute path. This option may be specified more than once. See RequiresMountsFor= or WantsMountsFor= in systemd.unit(5) for details.
Added in version 220.`, Added in version 220.`,
): docvalues.StringValue{}, ): docvalues.StringValue{},
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"x-systemd.device-bound", "x-systemd.device-bound",
`Takes a boolean argument. If true or no argument, a BindsTo= dependency on the backing device is set. If false, the mount unit is not stopped no matter whether the backing device is still present. This is useful when the file system is backed by volume managers. If not set, and the mount comes from unit fragments, i.e. generated from /etc/fstab by systemd-fstab-generator(8) or loaded from a manually configured mount unit, a combination of Requires= and StopPropagatedFrom= dependencies is set on the backing device. If doesn't, only Requires= is used. `Takes a boolean argument. If true or no argument, a BindsTo= dependency on the backing device is set. If false, the mount unit is not stopped no matter whether the backing device is still present. This is useful when the file system is backed by volume managers. If not set, and the mount comes from unit fragments, i.e. generated from /etc/fstab by systemd-fstab-generator(8) or loaded from a manually configured mount unit, a combination of Requires= and StopPropagatedFrom= dependencies is set on the backing device. If doesn't, only Requires= is used.
Added in version 233.`, Added in version 233.`,
): docvalues.EnumValue{ ): docvalues.EnumValue{
@ -323,13 +307,13 @@ Added in version 233.`,
}, },
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"x-systemd.idle-timeout", "x-systemd.idle-timeout",
`Configures the idle timeout of the automount unit. See TimeoutIdleSec= in systemd.automount(5) for details. `Configures the idle timeout of the automount unit. See TimeoutIdleSec= in systemd.automount(5) for details.
Added in version 220.`, Added in version 220.`,
): docvalues.StringValue{}, ): docvalues.StringValue{},
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"x-systemd.device-timeout", "x-systemd.device-timeout",
`Configure how long systemd should wait for a device to show up before giving up on an entry from /etc/fstab. Specify a time in seconds or explicitly append a unit such as "s", "min", "h", "ms". `Configure how long systemd should wait for a device to show up before giving up on an entry from /etc/fstab. Specify a time in seconds or explicitly append a unit such as "s", "min", "h", "ms".
Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file. Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file.
@ -337,7 +321,7 @@ Added in version 215.`,
): docvalues.StringValue{}, ): docvalues.StringValue{},
docvalues.CreateEnumStringWithDoc( docvalues.CreateEnumStringWithDoc(
"x-systemd.mount-timeout", "x-systemd.mount-timeout",
`Configure how long systemd should wait for the mount command to finish before giving up on an entry from /etc/fstab. Specify a time in seconds or explicitly append a unit such as "s", "min", "h", "ms". `Configure how long systemd should wait for the mount command to finish before giving up on an entry from /etc/fstab. Specify a time in seconds or explicitly append a unit such as "s", "min", "h", "ms".
Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file. Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file.
@ -345,130 +329,149 @@ See TimeoutSec= below for details.
Added in version 233.`, Added in version 233.`,
): docvalues.StringValue{}, ): 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( func createMountOptionField(
options []docvalues.EnumString, options []docvalues.EnumString,
assignOption map[docvalues.EnumString]docvalues.DeprecatedValue, assignOption map[docvalues.EnumString]docvalues.DeprecatedValue,
) docvalues.DeprecatedValue { ) docvalues.DeprecatedValue {
dynamicOptions := docvalues.MergeKeyEnumAssignmentMaps(defaultAssignOptions, assignOption) // dynamicOptions := docvalues.MergeKeyEnumAssignmentMaps(defaultAssignOptions, assignOption)
return docvalues.ArrayValue{ return docvalues.ArrayValue{
Separator: ",", Separator: ",",
DuplicatesExtractor: &mountOptionsExtractor, DuplicatesExtractor: &MountOptionsExtractor,
SubValue: docvalues.OrValue{ SubValue: docvalues.OrValue{
Values: []docvalues.DeprecatedValue{ Values: []docvalues.DeprecatedValue{
docvalues.KeyEnumAssignmentValue{ docvalues.KeyEnumAssignmentValue{
Values: dynamicOptions, Values: assignOption,
ValueIsOptional: false, ValueIsOptional: false,
Separator: "=", Separator: "=",
}, },
docvalues.EnumValue{ docvalues.EnumValue{
EnforceValues: true, 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
var MountOptionsMapField = map[string]docvalues.DeprecatedValue{ Enums []docvalues.EnumString
"adfs": createMountOptionField( }
commondocumentation.AdfsDocumentationEnums,
commondocumentation.AdfsDocumentationAssignable, var DefaultMountOptionsField = createMountOptionField(DefaultOptions, DefaultAssignOptions)
),
"affs": createMountOptionField( var MountOptionsMapField = map[string]optionField{
commondocumentation.AffsDocumentationEnums, "adfs": {
commondocumentation.AffsDocumentationAssignable, Enums: commondocumentation.AdfsDocumentationEnums,
), Assignable: commondocumentation.AdfsDocumentationAssignable,
"btrfs": createMountOptionField( },
commondocumentation.BtrfsDocumentationEnums, "affs": {
commondocumentation.BtrfsDocumentationAssignable, Enums: commondocumentation.AffsDocumentationEnums,
), Assignable: commondocumentation.AffsDocumentationAssignable,
"debugfs": createMountOptionField( },
commondocumentation.DebugfsDocumentationEnums, "btrfs": {
commondocumentation.DebugfsDocumentationAssignable, Enums: commondocumentation.BtrfsDocumentationEnums,
), Assignable: commondocumentation.BtrfsDocumentationAssignable,
"ext2": createMountOptionField( },
commondocumentation.Ext2DocumentationEnums, "debugfs": {
commondocumentation.Ext2DocumentationAssignable, Enums: commondocumentation.DebugfsDocumentationEnums,
), Assignable: commondocumentation.DebugfsDocumentationAssignable,
"ext3": createMountOptionField( },
append(commondocumentation.Ext2DocumentationEnums, commondocumentation.Ext3DocumentationEnums...), "ext2": {
docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext2DocumentationAssignable, commondocumentation.Ext3DocumentationAssignable), Enums: commondocumentation.Ext2DocumentationEnums,
), Assignable: commondocumentation.Ext2DocumentationAssignable,
"ext4": createMountOptionField( },
append(append(commondocumentation.Ext2DocumentationEnums, commondocumentation.Ext3DocumentationEnums...), commondocumentation.Ext4DocumentationEnums...), "ext3": {
docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext2DocumentationAssignable, docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext3DocumentationAssignable, commondocumentation.Ext4DocumentationAssignable)), Enums: append(commondocumentation.Ext2DocumentationEnums, commondocumentation.Ext3DocumentationEnums...),
), Assignable: docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext2DocumentationAssignable, commondocumentation.Ext3DocumentationAssignable),
"devpts": createMountOptionField( },
commondocumentation.DevptsDocumentationEnums, "ext4": {
commondocumentation.DevptsDocumentationAssignable, Enums: append(
), append(
"fat": createMountOptionField( commondocumentation.Ext2DocumentationEnums,
commondocumentation.FatDocumentationEnums, commondocumentation.Ext3DocumentationEnums...,
commondocumentation.FatDocumentationAssignable, ),
), commondocumentation.Ext4DocumentationEnums...,
"hfs": createMountOptionField( ),
commondocumentation.HfsDocumentationEnums, Assignable: docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext2DocumentationAssignable, docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext3DocumentationAssignable, commondocumentation.Ext4DocumentationAssignable)),
commondocumentation.HfsDocumentationAssignable, },
), "devpts": {
"hpfs": createMountOptionField( Enums: commondocumentation.DevptsDocumentationEnums,
commondocumentation.HpfsDocumentationEnums, Assignable: commondocumentation.DevptsDocumentationAssignable,
commondocumentation.HpfsDocumentationAssignable, },
), "fat": {
"iso9660": createMountOptionField( Enums: commondocumentation.FatDocumentationEnums,
commondocumentation.Iso9660DocumentationEnums, Assignable: commondocumentation.FatDocumentationAssignable,
commondocumentation.Iso9660DocumentationAssignable, },
), "hfs": {
"jfs": createMountOptionField( Enums: commondocumentation.HfsDocumentationEnums,
commondocumentation.JfsDocumentationEnums, Assignable: commondocumentation.HfsDocumentationAssignable,
commondocumentation.JfsDocumentationAssignable, },
), "hpfs": {
"msdos": createMountOptionField( Enums: commondocumentation.HpfsDocumentationEnums,
commondocumentation.MsdosDocumentationEnums, Assignable: commondocumentation.HpfsDocumentationAssignable,
commondocumentation.MsdosDocumentationAssignable, },
), "iso9660": {
"ncpfs": createMountOptionField( Enums: commondocumentation.Iso9660DocumentationEnums,
commondocumentation.NcpfsDocumentationEnums, Assignable: commondocumentation.Iso9660DocumentationAssignable,
commondocumentation.NcpfsDocumentationAssignable, },
), "jfs": {
"ntfs": createMountOptionField( Enums: commondocumentation.JfsDocumentationEnums,
commondocumentation.NtfsDocumentationEnums, Assignable: commondocumentation.JfsDocumentationAssignable,
commondocumentation.NtfsDocumentationAssignable, },
), "msdos": {
"overlay": createMountOptionField( Enums: commondocumentation.MsdosDocumentationEnums,
commondocumentation.OverlayDocumentationEnums, Assignable: commondocumentation.MsdosDocumentationAssignable,
commondocumentation.OverlayDocumentationAssignable, },
), "ncpfs": {
"reiserfs": createMountOptionField( Enums: commondocumentation.NcpfsDocumentationEnums,
commondocumentation.ReiserfsDocumentationEnums, Assignable: commondocumentation.NcpfsDocumentationAssignable,
commondocumentation.ReiserfsDocumentationAssignable, },
), "ntfs": {
"usbfs": createMountOptionField( Enums: commondocumentation.NtfsDocumentationEnums,
commondocumentation.UsbfsDocumentationEnums, Assignable: commondocumentation.NtfsDocumentationAssignable,
commondocumentation.UsbfsDocumentationAssignable, },
), "overlay": {
"ubifs": createMountOptionField( Enums: commondocumentation.OverlayDocumentationEnums,
commondocumentation.UbifsDocumentationEnums, Assignable: commondocumentation.OverlayDocumentationAssignable,
commondocumentation.UbifsDocumentationAssignable, },
), "reiserfs": {
"udf": createMountOptionField( Enums: commondocumentation.ReiserfsDocumentationEnums,
commondocumentation.UdfDocumentationEnums, Assignable: commondocumentation.ReiserfsDocumentationAssignable,
commondocumentation.UdfDocumentationAssignable, },
), "usbfs": {
"ufs": createMountOptionField( Enums: commondocumentation.UsbfsDocumentationEnums,
commondocumentation.UfsDocumentationEnums, Assignable: commondocumentation.UsbfsDocumentationAssignable,
commondocumentation.UfsDocumentationAssignable, },
), "ubifs": {
"umsdos": createMountOptionField( Enums: commondocumentation.UbifsDocumentationEnums,
commondocumentation.UmsdosDocumentationEnums, Assignable: commondocumentation.UbifsDocumentationAssignable,
commondocumentation.UmsdosDocumentationAssignable, },
), "udf": {
"vfat": createMountOptionField( Enums: commondocumentation.UdfDocumentationEnums,
commondocumentation.VfatDocumentationEnums, Assignable: commondocumentation.UdfDocumentationAssignable,
commondocumentation.VfatDocumentationAssignable, },
), "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" import docvalues "config-lsp/doc-values"

View File

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

View File

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

View File

@ -1,9 +1,10 @@
package fstab package fstab
import ( 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" handlers "config-lsp/handlers/fstab/handlers"
"config-lsp/handlers/fstab/parser"
"config-lsp/utils" "config-lsp/utils"
"testing" "testing"
) )
@ -12,22 +13,21 @@ func TestValidBasicExample(t *testing.T) {
input := utils.Dedent(` input := utils.Dedent(`
LABEL=test /mnt/test ext4 defaults 0 0 LABEL=test /mnt/test ext4 defaults 0 0
`) `)
p := parser.FstabParser{} p := ast.NewFstabConfig()
p.Clear()
errors := p.ParseFromContent(input) errors := p.Parse(input)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatal("ParseFromContent failed with error", errors) t.Fatal("Parse failed with error", errors)
} }
// Get hover for first field // Get hover for first field
rawEntry, _ := p.Entries.Get(uint32(0)) rawEntry, _ := p.Entries.Get(uint32(0))
entry := rawEntry.(parser.FstabEntry) entry := rawEntry.(*ast.FstabEntry)
println("Getting hover info") println("Getting hover info")
{ {
hover, err := handlers.GetHoverInfo(&entry, uint32(0)) hover, err := handlers.GetHoverInfo(uint32(0), common.IndexPosition(0), entry)
if err != nil { if err != nil {
t.Fatal("getHoverInfo failed with error", err) t.Fatal("getHoverInfo failed with error", err)
@ -38,7 +38,7 @@ LABEL=test /mnt/test ext4 defaults 0 0
} }
// Get hover for second field // 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 { if err != nil {
t.Fatal("getHoverInfo failed with error", err) 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) 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 { if err != nil {
t.Fatal("getHoverInfo failed with error", err) 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") println("Getting completions")
{ {
completions, err := handlers.GetCompletion(entry.Line, uint32(0)) completions, err := handlers.GetCompletion(entry, common.CursorPosition(0))
if err != nil { if err != nil {
t.Fatal("getCompletion failed with error", err) 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 { if err != nil {
t.Fatal("getCompletion failed with error", err) t.Fatal("getCompletion failed with error", err)
} }
expectedLength := len(utils.KeysOfMap(fstabdocumentation.MountOptionsMapField)) expectedLength := len(utils.KeysOfMap(fields.MountOptionsMapField))
if len(completions) != expectedLength { if len(completions) != expectedLength {
t.Fatal("getCompletion failed to return correct number of completions. Got:", len(completions), "but expected:", 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) { func TestInvalidOptionsExample(t *testing.T) {
input := utils.Dedent(` input := utils.Dedent(`
LABEL=test /mnt/test btrfs subvol=backup,fat=32 0 0 LABEL=test /mnt/test btrfs subvol=backup,fat=32 0 0
`) `)
p := parser.FstabParser{} p := ast.NewFstabConfig()
p.Clear()
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 { if len(errors) > 0 {
t.Fatal("ParseFromContent returned error", errors) t.Fatal("ParseFromContent returned error", errors)
} }
// Get hover for first field rawFirstEntry, _ := p.Entries.Get(uint32(0))
println("Checking values") firstEntry := rawFirstEntry.(*ast.FstabEntry)
{ name := firstEntry.GetFieldAtPosition(common.CursorPosition(0))
diagnostics := p.AnalyzeValues() if !(name == ast.FstabFieldSpec) {
t.Errorf("GetFieldAtPosition failed to return correct field name. Got: %v but expected: %v", name, ast.FstabFieldSpec)
}
if len(diagnostics) == 0 { name = firstEntry.GetFieldAtPosition(common.CursorPosition(9))
t.Fatal("AnalyzeValues should have returned error") 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=f9fe0b69-a280-415d-a03a-a32752370dee none swap defaults 0 0
UUID=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2 UUID=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2
`) `)
p := parser.FstabParser{} p := ast.NewFstabConfig()
p.Clear()
errors := p.ParseFromContent(input) errors := p.Parse(input)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors) 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) { func TestArchExample2(t *testing.T) {
@ -167,10 +191,9 @@ func TestArchExample2(t *testing.T) {
/dev/sda3 /home ext4 defaults 0 2 /dev/sda3 /home ext4 defaults 0 2
/dev/sda4 none swap defaults 0 0 /dev/sda4 none swap defaults 0 0
`) `)
p := parser.FstabParser{} p := ast.NewFstabConfig()
p.Clear()
errors := p.ParseFromContent(input) errors := p.Parse(input)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors) 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=Data /home ext4 defaults 0 2
LABEL=Swap none swap defaults 0 0 LABEL=Swap none swap defaults 0 0
`) `)
p := parser.FstabParser{} p := ast.NewFstabConfig()
p.Clear()
errors := p.ParseFromContent(input) errors := p.Parse(input)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors) t.Fatalf("ParseFromContent failed with error %v", errors)
} }
} }
func TestArchExample4(t *testing.T) { 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=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2
UUID=f9fe0b69-a280-415d-a03a-a32752370dee none swap defaults 0 0 UUID=f9fe0b69-a280-415d-a03a-a32752370dee none swap defaults 0 0
`) `)
p := parser.FstabParser{} p := ast.NewFstabConfig()
p.Clear()
errors := p.ParseFromContent(input) errors := p.Parse(input)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors) 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=Home /home ext4 defaults 0 2
PARTLABEL=Swap none swap defaults 0 0 PARTLABEL=Swap none swap defaults 0 0
`) `)
p := parser.FstabParser{} p := ast.NewFstabConfig()
p.Clear()
errors := p.ParseFromContent(input) errors := p.Parse(input)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors) 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=7280201c-fc5d-40f2-a9b2-466611d3d49e /home ext4 defaults 0 2
PARTUUID=039b6c1c-7553-4455-9537-1befbc9fbc5b none swap defaults 0 0 PARTUUID=039b6c1c-7553-4455-9537-1befbc9fbc5b none swap defaults 0 0
`) `)
p := parser.FstabParser{} p := ast.NewFstabConfig()
p.Clear()
errors := p.ParseFromContent(input) errors := p.Parse(input)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("ParseFromContent failed with error %v", errors) 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 package handlers
import ( import (
"config-lsp/doc-values" "config-lsp/common"
"config-lsp/handlers/fstab/documentation" "config-lsp/handlers/fstab/ast"
"config-lsp/handlers/fstab/parser" "config-lsp/handlers/fstab/fields"
"fmt"
"github.com/tliron/glsp/protocol_3_16" "github.com/tliron/glsp/protocol_3_16"
) )
func GetCompletion( func GetCompletion(
line parser.FstabLine, entry *ast.FstabEntry,
cursor uint32, cursor common.CursorPosition,
) ([]protocol.CompletionItem, error) { ) ([]protocol.CompletionItem, error) {
targetField := line.GetFieldAtPosition(cursor) targetField := entry.GetFieldAtPosition(cursor)
switch targetField { switch targetField {
case parser.FstabFieldSpec: case ast.FstabFieldSpec:
value, cursor := GetFieldSafely(line.Fields.Spec, cursor) value, cursor := getFieldSafely(entry.Fields.Spec, cursor)
return fstabdocumentation.SpecField.DeprecatedFetchCompletions( return fields.SpecField.DeprecatedFetchCompletions(
value, value,
cursor, cursor,
), nil ), nil
case parser.FstabFieldMountPoint: case ast.FstabFieldMountPoint:
value, cursor := GetFieldSafely(line.Fields.MountPoint, cursor) value, cursor := getFieldSafely(entry.Fields.MountPoint, cursor)
return fstabdocumentation.MountPointField.DeprecatedFetchCompletions( return fields.MountPointField.DeprecatedFetchCompletions(
value, value,
cursor, cursor,
), nil ), nil
case parser.FstabFieldFileSystemType: case ast.FstabFieldFileSystemType:
value, cursor := GetFieldSafely(line.Fields.FilesystemType, cursor) value, cursor := getFieldSafely(entry.Fields.FilesystemType, cursor)
return fstabdocumentation.FileSystemTypeField.DeprecatedFetchCompletions( return fields.FileSystemTypeField.DeprecatedFetchCompletions(
value, value,
cursor, cursor,
), nil ), nil
case parser.FstabFieldOptions: case ast.FstabFieldOptions:
fileSystemType := line.Fields.FilesystemType.Value 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 { switch completion.Documentation.(type) {
optionsField = foundField case string:
} else { documentation = completion.Documentation.(string)
optionsField = fstabdocumentation.DefaultMountOptionsField case *string:
documentation = *completion.Documentation.(*string)
}
completion.Documentation = protocol.MarkupContent{
Kind: protocol.MarkupKindMarkdown,
Value: documentation + "\n\n" + "From: _Default Mount Options_",
}
completions = append(completions, completion)
} }
value, cursor := GetFieldSafely(line.Fields.Options, cursor) for _, completion := range entry.FetchMountOptionsField(false).DeprecatedFetchCompletions(line, cursor) {
var documentation string
completions := optionsField.DeprecatedFetchCompletions( switch completion.Documentation.(type) {
value, case string:
cursor, 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 return completions, nil
case parser.FstabFieldFreq: case ast.FstabFieldFreq:
value, cursor := GetFieldSafely(line.Fields.Freq, cursor) value, cursor := getFieldSafely(entry.Fields.Freq, cursor)
return fstabdocumentation.FreqField.DeprecatedFetchCompletions( return fields.FreqField.DeprecatedFetchCompletions(
value, value,
cursor, cursor,
), nil ), nil
case parser.FstabFieldPass: case ast.FstabFieldPass:
value, cursor := GetFieldSafely(line.Fields.Pass, cursor) value, cursor := getFieldSafely(entry.Fields.Pass, cursor)
return fstabdocumentation.PassField.DeprecatedFetchCompletions( return fields.PassField.DeprecatedFetchCompletions(
value, value,
cursor, cursor,
), nil ), nil
@ -75,14 +98,18 @@ func GetCompletion(
// Safely get value and new cursor position // Safely get value and new cursor position
// If field is nil, return empty string and 0 // 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 { if field == nil {
return "", 0 return "", 0
} }
if field.Value == "" { if field.Value.Value == "" {
return "", 0 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 package handlers
import ( import (
"config-lsp/doc-values" "config-lsp/common"
"config-lsp/handlers/fstab/documentation" "config-lsp/handlers/fstab/ast"
"config-lsp/handlers/fstab/parser"
"github.com/tliron/glsp/protocol_3_16"
"strings" "strings"
"github.com/tliron/glsp/protocol_3_16"
) )
func GetHoverInfo(entry *parser.FstabEntry, cursor uint32) (*protocol.Hover, error) { func GetHoverInfo(
line := entry.Line line uint32,
targetField := line.GetFieldAtPosition(cursor) index common.IndexPosition,
entry *ast.FstabEntry,
) (*protocol.Hover, error) {
targetField := entry.GetFieldAtPosition(index)
switch targetField { switch targetField {
case parser.FstabFieldSpec: case ast.FstabFieldSpec:
return &SpecHoverField, nil return &SpecHoverField, nil
case parser.FstabFieldMountPoint: case ast.FstabFieldMountPoint:
return &MountPointHoverField, nil return &MountPointHoverField, nil
case parser.FstabFieldFileSystemType: case ast.FstabFieldFileSystemType:
return &FileSystemTypeField, nil return &FileSystemTypeField, nil
case parser.FstabFieldOptions: case ast.FstabFieldOptions:
fileSystemType := line.Fields.FilesystemType.Value optionsField := entry.FetchMountOptionsField(true)
var optionsField docvalues.DeprecatedValue
if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found { if optionsField == nil {
optionsField = foundField return nil, nil
} else {
optionsField = fstabdocumentation.DefaultMountOptionsField
} }
relativeCursor := cursor - line.Fields.Options.Start relativeCursor := uint32(index) - entry.Fields.Options.Start.Character
fieldInfo := optionsField.DeprecatedFetchHoverInfo(line.Fields.Options.Value, relativeCursor) fieldInfo := optionsField.DeprecatedFetchHoverInfo(entry.Fields.Options.Value.Value, relativeCursor)
hover := protocol.Hover{ hover := protocol.Hover{
Contents: protocol.MarkupContent{ Contents: protocol.MarkupContent{
@ -40,9 +40,9 @@ func GetHoverInfo(entry *parser.FstabEntry, cursor uint32) (*protocol.Hover, err
} }
return &hover, nil return &hover, nil
case parser.FstabFieldFreq: case ast.FstabFieldFreq:
return &FreqHoverField, nil return &FreqHoverField, nil
case parser.FstabFieldPass: case ast.FstabFieldPass:
return &PassHoverField, nil return &PassHoverField, nil
} }

View File

@ -2,9 +2,9 @@ package lsp
import ( import (
"config-lsp/common" "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/handlers"
"config-lsp/handlers/fstab/parser"
"config-lsp/handlers/fstab/shared" "config-lsp/handlers/fstab/shared"
"github.com/tliron/glsp" "github.com/tliron/glsp"
@ -12,9 +12,10 @@ import (
) )
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) { 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 { if !found {
// Empty line, return spec completions // Empty line, return spec completions
@ -24,12 +25,7 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa
), nil ), nil
} }
if entry.Type == parser.FstabEntryTypeComment { entry := rawEntry.(*ast.FstabEntry)
return nil, nil
}
cursor := common.CursorToCharacterIndex(params.Position.Character) return handlers.GetCompletion(entry, cursor)
line := entry.Line
return handlers.GetCompletion(line, cursor)
} }

View File

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

View File

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

View File

@ -1,29 +1,33 @@
package lsp package lsp
import ( import (
"config-lsp/common"
"config-lsp/handlers/fstab/ast"
"config-lsp/handlers/fstab/handlers" "config-lsp/handlers/fstab/handlers"
"config-lsp/handlers/fstab/parser"
"config-lsp/handlers/fstab/shared" "config-lsp/handlers/fstab/shared"
"github.com/tliron/glsp" "github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16" protocol "github.com/tliron/glsp/protocol_3_16"
) )
func TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) { 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 // Empty line
if !found { if !found {
return nil, nil return nil, nil
} }
// Comment line entry := rawEntry.(*ast.FstabEntry)
if entry.Type == parser.FstabEntryTypeComment {
return nil, nil
}
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 package shared
import ( import (
"config-lsp/handlers/fstab/parser" "config-lsp/handlers/fstab/ast"
protocol "github.com/tliron/glsp/protocol_3_16" 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 ( import (
"config-lsp/common" "config-lsp/common"
docvalues "config-lsp/doc-values"
"config-lsp/handlers/ssh_config/ast" "config-lsp/handlers/ssh_config/ast"
"config-lsp/handlers/ssh_config/fields" "config-lsp/handlers/ssh_config/fields"
"config-lsp/utils" "config-lsp/utils"
@ -40,7 +41,7 @@ func checkOption(
checkIsUsingDoubleQuotes(ctx, option.Key.Value, option.Key.LocationRange) checkIsUsingDoubleQuotes(ctx, option.Key.Value, option.Key.LocationRange)
checkQuotesAreClosed(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 { if !found {
// Diagnostics will be handled by `values.go` // Diagnostics will be handled by `values.go`
@ -61,6 +62,19 @@ func checkOption(
if option.OptionValue != nil { if option.OptionValue != nil {
checkIsUsingDoubleQuotes(ctx, option.OptionValue.Value, option.OptionValue.LocationRange) checkIsUsingDoubleQuotes(ctx, option.OptionValue.Value, option.OptionValue.LocationRange)
checkQuotesAreClosed(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 == "" { if option.Separator == nil || option.Separator.Value.Value == "" {

View File

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

View File

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

View File

@ -5,6 +5,9 @@ ROOT=$(git rev-parse --show-toplevel)/server
# aliases # aliases
cd $ROOT/handlers/aliases && antlr4 -Dlanguage=Go -o ast/parser Aliases.g4 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 # sshd_config
cd $ROOT/handlers/sshd_config && antlr4 -Dlanguage=Go -o ast/parser Config.g4 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 cd $ROOT/handlers/sshd_config/match-parser && antlr4 -Dlanguage=Go -o parser Match.g4