refactor(fstab): Overall refactoring to new style

This commit is contained in:
Myzel394 2024-10-08 16:11:33 +02:00
parent 716440cf4c
commit 41239654c8
No known key found for this signature in database
GPG Key ID: ED20A1D1D423AF3F
25 changed files with 556 additions and 541 deletions

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,131 @@
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 missing",
Severity: &common.SeverityError,
})
continue
}
if entry.Fields.Freq == nil || entry.Fields.Freq.Value.Value == "" {
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: protocol.Range{
Start: protocol.Position{
Line: entry.Fields.Start.Line,
Character: entry.Fields.Options.End.Character,
},
End: protocol.Position{
Line: entry.Fields.Start.Line,
Character: entry.Fields.Options.End.Character,
},
},
Message: "The freq field is missing",
Severity: &common.SeverityError,
})
continue
}
if entry.Fields.Pass == nil || entry.Fields.Pass.Value.Value == "" {
ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{
Range: protocol.Range{
Start: protocol.Position{
Line: entry.Fields.Start.Line,
Character: entry.Fields.Freq.End.Character,
},
End: protocol.Position{
Line: entry.Fields.Start.Line,
Character: entry.Fields.Freq.End.Character,
},
},
Message: "The pass field is missing",
Severity: &common.SeverityError,
})
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,46 @@
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)
checkField(ctx, entry.Fields.Options, entry.GetMountOptionsField())
checkField(ctx, entry.Fields.Freq, fields.FreqField)
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)
err.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

@ -43,4 +43,3 @@ type FstabConfig struct {
// [uint32]{} - line number to empty struct for comments // [uint32]{} - line number to empty struct for comments
CommentLines map[uint32]struct{} CommentLines map[uint32]struct{}
} }

View File

@ -1,5 +1,11 @@
package ast package ast
import (
"config-lsp/common"
docvalues "config-lsp/doc-values"
"config-lsp/handlers/fstab/fields"
)
// func (c FstabConfig) GetEntry(line uint32) *FstabEntry { // func (c FstabConfig) GetEntry(line uint32) *FstabEntry {
// entry, found := c.Entries.Get(line) // entry, found := c.Entries.Get(line)
// //
@ -10,26 +16,40 @@ package ast
// return entry.(*FstabEntry) // return entry.(*FstabEntry)
// } // }
func (e FstabEntry) GetFieldAtPosition(cursor uint32) FstabFieldName { func (e FstabEntry) GetFieldAtPosition(position common.Position) FstabFieldName {
if e.Fields.Spec == nil || (cursor >= e.Fields.Spec.Start.Character && cursor <= e.Fields.Spec.End.Character) { if e.Fields.Spec == nil || (e.Fields.Spec.ContainsPosition(position)) {
return FstabFieldSpec return FstabFieldSpec
} }
if e.Fields.MountPoint == nil || (cursor >= e.Fields.MountPoint.Start.Character && cursor <= e.Fields.MountPoint.End.Character) { if e.Fields.MountPoint == nil || (e.Fields.MountPoint.ContainsPosition(position)) {
return FstabFieldMountPoint return FstabFieldMountPoint
} }
if e.Fields.FilesystemType == nil || (cursor >= e.Fields.FilesystemType.Start.Character && cursor <= e.Fields.FilesystemType.End.Character) { if e.Fields.FilesystemType == nil || (e.Fields.FilesystemType.ContainsPosition(position)) {
return FstabFieldFileSystemType return FstabFieldFileSystemType
} }
if e.Fields.Options == nil || (cursor >= e.Fields.Options.Start.Character && cursor <= e.Fields.Options.End.Character) { if e.Fields.Options == nil || (e.Fields.Options.ContainsPosition(position)) {
return FstabFieldOptions return FstabFieldOptions
} }
if e.Fields.Freq == nil || (cursor >= e.Fields.Freq.Start.Character && cursor <= e.Fields.Freq.End.Character) { if e.Fields.Freq == nil || (e.Fields.Freq.ContainsPosition(position)) {
return FstabFieldFreq return FstabFieldFreq
} }
return FstabFieldPass return FstabFieldPass
} }
func (e FstabEntry) GetMountOptionsField() docvalues.DeprecatedValue {
fileSystemType := e.Fields.FilesystemType.Value.Value
var optionsField docvalues.DeprecatedValue
if foundField, found := fields.MountOptionsMapField[fileSystemType]; found {
optionsField = foundField
} else {
optionsField = fields.DefaultMountOptionsField
}
return optionsField
}

View File

@ -88,21 +88,45 @@ func (c *FstabConfig) parseStatement(
fallthrough fallthrough
case 5: case 5:
freq = parseField(line, input, fields[4]) freq = parseField(line, input, fields[4])
if pass == nil && fields[4][1] < len(input) {
pass = createPartialField(line, input, fields[4][1], len(input))
}
fallthrough fallthrough
case 4: case 4:
options = parseField(line, input, fields[3]) options = parseField(line, input, fields[3])
if freq == nil && fields[3][1] < len(input) {
freq = createPartialField(line, input, fields[3][1], len(input))
}
fallthrough fallthrough
case 3: case 3:
filesystemType = parseField(line, input, fields[2]) filesystemType = parseField(line, input, fields[2])
if options == nil && fields[2][1] < len(input) {
options = createPartialField(line, input, fields[2][1], len(input))
}
fallthrough fallthrough
case 2: case 2:
mountPoint = parseField(line, input, fields[1]) mountPoint = parseField(line, input, fields[1])
if filesystemType == nil && fields[1][1] < len(input) {
filesystemType = createPartialField(line, input, fields[1][1], len(input))
}
fallthrough fallthrough
case 1: case 1:
spec = parseField(line, input, fields[0]) spec = parseField(line, input, fields[0])
if mountPoint == nil && fields[0][1] < len(input) {
mountPoint = createPartialField(line, input, fields[0][1], len(input))
}
} }
fstabLine := FstabEntry{ fstabLine := &FstabEntry{
Fields: FstabFields{ Fields: FstabFields{
LocationRange: common.LocationRange{ LocationRange: common.LocationRange{
Start: common.Location{ Start: common.Location{
@ -157,3 +181,29 @@ func parseField(
}), }),
} }
} }
func createPartialField(
line uint32,
input string,
start int,
end int,
) *FstabField {
return nil
return &FstabField{
LocationRange: common.LocationRange{
Start: common.Location{
Line: line,
Character: uint32(start),
},
End: common.Location{
Line: line,
Character: uint32(end),
},
},
Value: commonparser.ParseRawString(input[end:end], commonparser.ParseFeatures{
ParseEscapedCharacters: true,
ParseDoubleQuotes: true,
Replacements: &map[string]string{},
}),
}
}

View File

@ -1,6 +1,7 @@
package ast package ast
import ( import (
"config-lsp/common"
"config-lsp/utils" "config-lsp/utils"
"testing" "testing"
) )
@ -24,7 +25,7 @@ LABEL=test /mnt/test ext4 defaults 0 0
} }
rawFirstEntry, _ := c.Entries.Get(uint32(0)) rawFirstEntry, _ := c.Entries.Get(uint32(0))
firstEntry := rawFirstEntry.(FstabEntry) 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") { 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) t.Fatalf("Expected entry to be LABEL=test /mnt/test ext4 defaults 0 0, got %v", firstEntry)
} }
@ -72,4 +73,66 @@ LABEL=test /mnt/test ext4 defaults 0 0
if !(firstEntry.Fields.Pass.LocationRange.Start.Line == 0 && firstEntry.Fields.Pass.LocationRange.Start.Character == 37) { 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) 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)
}
}
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"

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 (
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/fields" "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,50 +75,30 @@ 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 { if len(errors) > 0 {
t.Fatal("ParseFromContent returned error", errors) t.Fatal("ParseFromContent returned error", errors)
} }
// Get hover for first field
println("Checking values")
{
diagnostics := p.AnalyzeValues()
if len(diagnostics) == 0 {
t.Fatal("AnalyzeValues should have returned error")
}
}
} }
// func TestExample1(t *testing.T) { // func TestExample1(t *testing.T) {
@ -150,20 +126,14 @@ 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)
} }
diagnostics := p.AnalyzeValues()
if len(diagnostics) > 0 {
t.Errorf("AnalyzeValues failed with error %v", diagnostics)
}
} }
func TestArchExample2(t *testing.T) { func TestArchExample2(t *testing.T) {
@ -173,10 +143,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)
@ -190,20 +159,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)
} }
diagnostics := p.AnalyzeValues()
if len(diagnostics) > 0 {
t.Errorf("AnalyzeValues failed with error %v", diagnostics)
}
} }
func TestArchExample4(t *testing.T) { func TestArchExample4(t *testing.T) {
@ -213,20 +176,13 @@ 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)
} }
diagnostics := p.AnalyzeValues()
if len(diagnostics) > 0 {
t.Errorf("AnalyzeValues failed with error %v", diagnostics)
}
} }
func TestArchExample5(t *testing.T) { func TestArchExample5(t *testing.T) {
@ -236,20 +192,13 @@ 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)
} }
diagnostics := p.AnalyzeValues()
if len(diagnostics) > 0 {
t.Errorf("AnalyzeValues failed with error %v", diagnostics)
}
} }
func TestArchExample6(t *testing.T) { func TestArchExample6(t *testing.T) {
@ -259,60 +208,39 @@ 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)
} }
diagnostics := p.AnalyzeValues()
if len(diagnostics) > 0 {
t.Errorf("AnalyzeValues failed with error %v", diagnostics)
}
} }
func TestLinuxConfigExample(t *testing.T) { func TestLinuxConfigExample(t *testing.T) {
input := utils.Dedent(` input := utils.Dedent(`
UUID=80b496fa-ce2d-4dcf-9afc-bcaa731a67f1 /mnt/example ext4 defaults 0 2 UUID=80b496fa-ce2d-4dcf-9afc-bcaa731a67f1 /mnt/example 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)
} }
diagnostics := p.AnalyzeValues()
if len(diagnostics) > 0 {
t.Errorf("AnalyzeValues failed with error %v", diagnostics)
}
} }
func Test1(t *testing.T) { func Test1(t *testing.T) {
input := utils.Dedent(` input := utils.Dedent(`
PARTLABEL="rootfs" / ext4 noatime,lazytime,rw 0 0 PARTLABEL="rootfs" / ext4 noatime,lazytime,rw 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)
} }
diagnostics := p.AnalyzeValues()
if len(diagnostics) > 0 {
t.Errorf("AnalyzeValues failed with error %v", diagnostics)
}
} }
func Test2(t *testing.T) { func Test2(t *testing.T) {
@ -323,10 +251,9 @@ func Test2(t *testing.T) {
/dev/sdd /homeD xfs noauto,rw,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0 /dev/sdd /homeD xfs noauto,rw,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0
/dev/sde /homeE xfs defaults 0 0 /dev/sde /homeE xfs 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)
@ -344,10 +271,9 @@ func Test3(t *testing.T) {
tmpfs /var tmpfs rw,nosuid,nodev,size=128M,mode=755 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 tmpfs /tmp tmpfs rw,nosuid,nodev,size=150M,mode=1777 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)

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"config-lsp/common"
"config-lsp/doc-values" "config-lsp/doc-values"
"config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/ast"
"config-lsp/handlers/fstab/fields" "config-lsp/handlers/fstab/fields"
@ -10,7 +11,7 @@ import (
func GetCompletion( func GetCompletion(
entry *ast.FstabEntry, entry *ast.FstabEntry,
cursor uint32, cursor common.CursorPosition,
) ([]protocol.CompletionItem, error) { ) ([]protocol.CompletionItem, error) {
targetField := entry.GetFieldAtPosition(cursor) targetField := entry.GetFieldAtPosition(cursor)
@ -18,21 +19,21 @@ func GetCompletion(
case ast.FstabFieldSpec: case ast.FstabFieldSpec:
value, cursor := getFieldSafely(entry.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 ast.FstabFieldMountPoint: case ast.FstabFieldMountPoint:
value, cursor := getFieldSafely(entry.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 ast.FstabFieldFileSystemType: case ast.FstabFieldFileSystemType:
value, cursor := getFieldSafely(entry.Fields.FilesystemType, cursor) value, cursor := getFieldSafely(entry.Fields.FilesystemType, cursor)
return fstabdocumentation.FileSystemTypeField.DeprecatedFetchCompletions( return fields.FileSystemTypeField.DeprecatedFetchCompletions(
value, value,
cursor, cursor,
), nil ), nil
@ -41,10 +42,10 @@ func GetCompletion(
var optionsField docvalues.DeprecatedValue var optionsField docvalues.DeprecatedValue
if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found { if foundField, found := fields.MountOptionsMapField[fileSystemType]; found {
optionsField = foundField optionsField = foundField
} else { } else {
optionsField = fstabdocumentation.DefaultMountOptionsField optionsField = fields.DefaultMountOptionsField
} }
value, cursor := getFieldSafely(entry.Fields.Options, cursor) value, cursor := getFieldSafely(entry.Fields.Options, cursor)
@ -58,14 +59,14 @@ func GetCompletion(
case ast.FstabFieldFreq: case ast.FstabFieldFreq:
value, cursor := getFieldSafely(entry.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 ast.FstabFieldPass: case ast.FstabFieldPass:
value, cursor := getFieldSafely(entry.Fields.Pass, cursor) value, cursor := getFieldSafely(entry.Fields.Pass, cursor)
return fstabdocumentation.PassField.DeprecatedFetchCompletions( return fields.PassField.DeprecatedFetchCompletions(
value, value,
cursor, cursor,
), nil ), nil
@ -76,7 +77,7 @@ 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 *ast.FstabField, character uint32) (string, uint32) { func getFieldSafely(field *ast.FstabField, cursor common.CursorPosition) (string, uint32) {
if field == nil { if field == nil {
return "", 0 return "", 0
} }
@ -85,5 +86,5 @@ func getFieldSafely(field *ast.FstabField, character uint32) (string, uint32) {
return "", 0 return "", 0
} }
return field.Value.Raw, character - field.Start.Character return field.Value.Raw, common.CursorToCharacterIndex(uint32(cursor)) - field.Start.Character
} }

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"config-lsp/common"
"config-lsp/doc-values" "config-lsp/doc-values"
"config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/ast"
"config-lsp/handlers/fstab/fields" "config-lsp/handlers/fstab/fields"
@ -11,10 +12,10 @@ import (
func GetHoverInfo( func GetHoverInfo(
line uint32, line uint32,
cursor uint32, index common.IndexPosition,
entry *ast.FstabEntry, entry *ast.FstabEntry,
) (*protocol.Hover, error) { ) (*protocol.Hover, error) {
targetField := entry.GetFieldAtPosition(cursor) targetField := entry.GetFieldAtPosition(index)
switch targetField { switch targetField {
case ast.FstabFieldSpec: case ast.FstabFieldSpec:
@ -27,13 +28,13 @@ func GetHoverInfo(
fileSystemType := entry.Fields.FilesystemType.Value.Value fileSystemType := entry.Fields.FilesystemType.Value.Value
var optionsField docvalues.DeprecatedValue var optionsField docvalues.DeprecatedValue
if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found { if foundField, found := fields.MountOptionsMapField[fileSystemType]; found {
optionsField = foundField optionsField = foundField
} else { } else {
optionsField = fstabdocumentation.DefaultMountOptionsField optionsField = fields.DefaultMountOptionsField
} }
relativeCursor := cursor - entry.Fields.Options.Start.Character relativeCursor := uint32(index) - entry.Fields.Options.Start.Character
fieldInfo := optionsField.DeprecatedFetchHoverInfo(entry.Fields.Options.Value.Value, relativeCursor) fieldInfo := optionsField.DeprecatedFetchHoverInfo(entry.Fields.Options.Value.Value, relativeCursor)
hover := protocol.Hover{ hover := protocol.Hover{

View File

@ -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) {
c := shared.DocumentParserMap[params.TextDocument.URI] d := shared.DocumentParserMap[params.TextDocument.URI]
cursor := common.LSPCharacterAsCursorPosition(params.Position.Character)
rawEntry, found := c.Entries.Get(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
@ -26,7 +27,5 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa
entry := rawEntry.(*ast.FstabEntry) entry := rawEntry.(*ast.FstabEntry)
cursor := common.CursorToCharacterIndex(params.Position.Character)
return handlers.GetCompletion(entry, cursor) return handlers.GetCompletion(entry, 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,11 +17,11 @@ 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.Parse(content) errors := d.Config.Parse(content)
if len(errors) > 0 { if len(errors) > 0 {
diagnostics = append(diagnostics, utils.Map( diagnostics = append(diagnostics, utils.Map(
@ -29,7 +31,7 @@ func TextDocumentDidChange(
}, },
)...) )...)
} else { } else {
// diagnostics = append(diagnostics, p.AnalyzeValues()...) diagnostics = append(diagnostics, analyzer.Analyze(d)...)
} }
if len(diagnostics) > 0 { if len(diagnostics) > 0 {

View File

@ -2,6 +2,7 @@ package lsp
import ( import (
"config-lsp/common" "config-lsp/common"
"config-lsp/handlers/fstab/analyzer"
"config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/ast"
"config-lsp/handlers/fstab/shared" "config-lsp/handlers/fstab/shared"
"config-lsp/utils" "config-lsp/utils"
@ -16,13 +17,16 @@ func TextDocumentDidOpen(
) error { ) error {
common.ClearDiagnostics(context, params.TextDocument.URI) common.ClearDiagnostics(context, params.TextDocument.URI)
p := ast.NewFstabConfig() config := ast.NewFstabConfig()
shared.DocumentParserMap[params.TextDocument.URI] = p d := &shared.FstabDocument{
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.Parse(content) errors := d.Config.Parse(content)
if len(errors) > 0 { if len(errors) > 0 {
diagnostics = append(diagnostics, utils.Map( diagnostics = append(diagnostics, utils.Map(
@ -32,7 +36,7 @@ func TextDocumentDidOpen(
}, },
)...) )...)
} else { } else {
// diagnostics = append(diagnostics, p.AnalyzeValues()...) diagnostics = append(diagnostics, analyzer.Analyze(d)...)
} }
if len(diagnostics) > 0 { if len(diagnostics) > 0 {

View File

@ -1,6 +1,7 @@
package lsp package lsp
import ( import (
"config-lsp/common"
"config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/ast"
"config-lsp/handlers/fstab/handlers" "config-lsp/handlers/fstab/handlers"
"config-lsp/handlers/fstab/shared" "config-lsp/handlers/fstab/shared"
@ -11,11 +12,11 @@ import (
func TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) { func TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
line := params.Position.Line line := params.Position.Line
cursor := params.Position.Character index := common.LSPCharacterAsIndexPosition(params.Position.Character)
p := shared.DocumentParserMap[params.TextDocument.URI] d := shared.DocumentParserMap[params.TextDocument.URI]
rawEntry, found := p.Entries.Get(params.Position.Line) rawEntry, found := d.Config.Entries.Get(params.Position.Line)
// Empty line // Empty line
if !found { if !found {
@ -26,7 +27,7 @@ func TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*pr
return handlers.GetHoverInfo( return handlers.GetHoverInfo(
line, line,
cursor, index,
entry, entry,
) )
} }

View File

@ -1,385 +0,0 @@
package parser
import (
"config-lsp/common"
docvalues "config-lsp/doc-values"
fstabdocumentation "config-lsp/handlers/fstab/fields"
"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...)
}
}
}
return diagnostics
}

View File

@ -6,5 +6,8 @@ import (
protocol "github.com/tliron/glsp/protocol_3_16" protocol "github.com/tliron/glsp/protocol_3_16"
) )
var DocumentParserMap = map[protocol.DocumentUri]*ast.FstabConfig{} 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,
}
}