From 41239654c8d17d3a9c68c27ae82475910936ec3e Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:11:33 +0200 Subject: [PATCH] refactor(fstab): Overall refactoring to new style --- server/handlers/fstab/analyzer/analyzer.go | 30 ++ server/handlers/fstab/analyzer/fields.go | 131 ++++++ server/handlers/fstab/analyzer/fields_test.go | 38 ++ server/handlers/fstab/analyzer/values.go | 46 +++ server/handlers/fstab/analyzer/values_test.go | 61 +++ server/handlers/fstab/ast/fstab.go | 1 - server/handlers/fstab/ast/fstab_fields.go | 32 +- server/handlers/fstab/ast/parser.go | 52 ++- server/handlers/fstab/ast/parser_test.go | 65 ++- server/handlers/fstab/fields/freq.go | 2 +- server/handlers/fstab/fields/mount-point.go | 2 +- server/handlers/fstab/fields/mountoptions.go | 2 +- server/handlers/fstab/fields/pass.go | 2 +- server/handlers/fstab/fields/spec.go | 2 +- server/handlers/fstab/fields/type.go | 2 +- server/handlers/fstab/fstab_test.go | 144 ++----- server/handlers/fstab/handlers/completions.go | 21 +- server/handlers/fstab/handlers/hover.go | 11 +- .../fstab/lsp/text-document-completion.go | 7 +- .../fstab/lsp/text-document-did-change.go | 10 +- .../fstab/lsp/text-document-did-open.go | 12 +- .../handlers/fstab/lsp/text-document-hover.go | 9 +- server/handlers/fstab/parser/parser.go | 385 ------------------ server/handlers/fstab/shared/document.go | 5 +- server/handlers/fstab/test_utils/input.go | 25 ++ 25 files changed, 556 insertions(+), 541 deletions(-) create mode 100644 server/handlers/fstab/analyzer/analyzer.go create mode 100644 server/handlers/fstab/analyzer/fields.go create mode 100644 server/handlers/fstab/analyzer/fields_test.go create mode 100644 server/handlers/fstab/analyzer/values.go create mode 100644 server/handlers/fstab/analyzer/values_test.go delete mode 100644 server/handlers/fstab/parser/parser.go create mode 100644 server/handlers/fstab/test_utils/input.go diff --git a/server/handlers/fstab/analyzer/analyzer.go b/server/handlers/fstab/analyzer/analyzer.go new file mode 100644 index 0000000..465ead5 --- /dev/null +++ b/server/handlers/fstab/analyzer/analyzer.go @@ -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 +} diff --git a/server/handlers/fstab/analyzer/fields.go b/server/handlers/fstab/analyzer/fields.go new file mode 100644 index 0000000..1f8b054 --- /dev/null +++ b/server/handlers/fstab/analyzer/fields.go @@ -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 + } + } +} diff --git a/server/handlers/fstab/analyzer/fields_test.go b/server/handlers/fstab/analyzer/fields_test.go new file mode 100644 index 0000000..354518d --- /dev/null +++ b/server/handlers/fstab/analyzer/fields_test.go @@ -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)) + } +} diff --git a/server/handlers/fstab/analyzer/values.go b/server/handlers/fstab/analyzer/values.go new file mode 100644 index 0000000..8ef69b9 --- /dev/null +++ b/server/handlers/fstab/analyzer/values.go @@ -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, + }) + } +} diff --git a/server/handlers/fstab/analyzer/values_test.go b/server/handlers/fstab/analyzer/values_test.go new file mode 100644 index 0000000..5b0fa53 --- /dev/null +++ b/server/handlers/fstab/analyzer/values_test.go @@ -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)) + } +} diff --git a/server/handlers/fstab/ast/fstab.go b/server/handlers/fstab/ast/fstab.go index 6346ecb..125e624 100644 --- a/server/handlers/fstab/ast/fstab.go +++ b/server/handlers/fstab/ast/fstab.go @@ -43,4 +43,3 @@ type FstabConfig struct { // [uint32]{} - line number to empty struct for comments CommentLines map[uint32]struct{} } - diff --git a/server/handlers/fstab/ast/fstab_fields.go b/server/handlers/fstab/ast/fstab_fields.go index bbf52db..b72877e 100644 --- a/server/handlers/fstab/ast/fstab_fields.go +++ b/server/handlers/fstab/ast/fstab_fields.go @@ -1,5 +1,11 @@ package ast +import ( + "config-lsp/common" + docvalues "config-lsp/doc-values" + "config-lsp/handlers/fstab/fields" +) + // func (c FstabConfig) GetEntry(line uint32) *FstabEntry { // entry, found := c.Entries.Get(line) // @@ -10,26 +16,40 @@ package ast // return entry.(*FstabEntry) // } -func (e FstabEntry) GetFieldAtPosition(cursor uint32) FstabFieldName { - if e.Fields.Spec == nil || (cursor >= e.Fields.Spec.Start.Character && cursor <= e.Fields.Spec.End.Character) { +func (e FstabEntry) GetFieldAtPosition(position common.Position) FstabFieldName { + if e.Fields.Spec == nil || (e.Fields.Spec.ContainsPosition(position)) { 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 } - 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 } - 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 } - 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 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 +} diff --git a/server/handlers/fstab/ast/parser.go b/server/handlers/fstab/ast/parser.go index 78acef8..e99c8f3 100644 --- a/server/handlers/fstab/ast/parser.go +++ b/server/handlers/fstab/ast/parser.go @@ -88,21 +88,45 @@ func (c *FstabConfig) parseStatement( fallthrough case 5: freq = parseField(line, input, fields[4]) + + if pass == nil && fields[4][1] < len(input) { + pass = createPartialField(line, input, fields[4][1], len(input)) + } + fallthrough case 4: options = parseField(line, input, fields[3]) + + if freq == nil && fields[3][1] < len(input) { + freq = createPartialField(line, input, fields[3][1], len(input)) + } + fallthrough case 3: filesystemType = parseField(line, input, fields[2]) + + if options == nil && fields[2][1] < len(input) { + options = createPartialField(line, input, fields[2][1], len(input)) + } + fallthrough case 2: mountPoint = parseField(line, input, fields[1]) + + if filesystemType == nil && fields[1][1] < len(input) { + filesystemType = createPartialField(line, input, fields[1][1], len(input)) + } + fallthrough case 1: 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{ LocationRange: common.LocationRange{ 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{}, + }), + } +} diff --git a/server/handlers/fstab/ast/parser_test.go b/server/handlers/fstab/ast/parser_test.go index 199eb02..6e3a2c7 100644 --- a/server/handlers/fstab/ast/parser_test.go +++ b/server/handlers/fstab/ast/parser_test.go @@ -1,6 +1,7 @@ package ast import ( + "config-lsp/common" "config-lsp/utils" "testing" ) @@ -24,7 +25,7 @@ LABEL=test /mnt/test ext4 defaults 0 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") { 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) { 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) + } } diff --git a/server/handlers/fstab/fields/freq.go b/server/handlers/fstab/fields/freq.go index 5b42b3e..70a98e4 100644 --- a/server/handlers/fstab/fields/freq.go +++ b/server/handlers/fstab/fields/freq.go @@ -1,4 +1,4 @@ -package fstabdocumentation +package fields import docvalues "config-lsp/doc-values" diff --git a/server/handlers/fstab/fields/mount-point.go b/server/handlers/fstab/fields/mount-point.go index 9de4c6d..b11daa6 100644 --- a/server/handlers/fstab/fields/mount-point.go +++ b/server/handlers/fstab/fields/mount-point.go @@ -1,4 +1,4 @@ -package fstabdocumentation +package fields import ( docvalues "config-lsp/doc-values" diff --git a/server/handlers/fstab/fields/mountoptions.go b/server/handlers/fstab/fields/mountoptions.go index a268551..f6227ef 100644 --- a/server/handlers/fstab/fields/mountoptions.go +++ b/server/handlers/fstab/fields/mountoptions.go @@ -1,4 +1,4 @@ -package fstabdocumentation +package fields import ( commondocumentation "config-lsp/common-documentation" diff --git a/server/handlers/fstab/fields/pass.go b/server/handlers/fstab/fields/pass.go index 6163c3b..5306a11 100644 --- a/server/handlers/fstab/fields/pass.go +++ b/server/handlers/fstab/fields/pass.go @@ -1,4 +1,4 @@ -package fstabdocumentation +package fields import docvalues "config-lsp/doc-values" diff --git a/server/handlers/fstab/fields/spec.go b/server/handlers/fstab/fields/spec.go index 5d63a39..ae465ee 100644 --- a/server/handlers/fstab/fields/spec.go +++ b/server/handlers/fstab/fields/spec.go @@ -1,4 +1,4 @@ -package fstabdocumentation +package fields import ( docvalues "config-lsp/doc-values" diff --git a/server/handlers/fstab/fields/type.go b/server/handlers/fstab/fields/type.go index 8a6f8f8..b4d0b05 100644 --- a/server/handlers/fstab/fields/type.go +++ b/server/handlers/fstab/fields/type.go @@ -1,4 +1,4 @@ -package fstabdocumentation +package fields import ( docvalues "config-lsp/doc-values" diff --git a/server/handlers/fstab/fstab_test.go b/server/handlers/fstab/fstab_test.go index 9f16e01..f6b21c1 100644 --- a/server/handlers/fstab/fstab_test.go +++ b/server/handlers/fstab/fstab_test.go @@ -1,9 +1,10 @@ package fstab 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" - "config-lsp/handlers/fstab/parser" "config-lsp/utils" "testing" ) @@ -12,22 +13,21 @@ func TestValidBasicExample(t *testing.T) { input := utils.Dedent(` LABEL=test /mnt/test ext4 defaults 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { - t.Fatal("ParseFromContent failed with error", errors) + t.Fatal("Parse failed with error", errors) } // Get hover for first field rawEntry, _ := p.Entries.Get(uint32(0)) - entry := rawEntry.(parser.FstabEntry) + entry := rawEntry.(*ast.FstabEntry) println("Getting hover info") { - hover, err := handlers.GetHoverInfo(&entry, uint32(0)) + hover, err := handlers.GetHoverInfo(uint32(0), common.IndexPosition(0), entry) if err != nil { t.Fatal("getHoverInfo failed with error", err) @@ -38,7 +38,7 @@ LABEL=test /mnt/test ext4 defaults 0 0 } // Get hover for second field - hover, err = handlers.GetHoverInfo(&entry, uint32(11)) + hover, err = handlers.GetHoverInfo(uint32(0), common.IndexPosition(11), entry) if err != nil { t.Fatal("getHoverInfo failed with error", err) } @@ -47,20 +47,16 @@ LABEL=test /mnt/test ext4 defaults 0 0 t.Fatal("getHoverInfo failed to return correct hover content. Got:", hover.Contents, "but expected:", handlers.MountPointHoverField.Contents) } - hover, err = handlers.GetHoverInfo(&entry, uint32(20)) + hover, err = handlers.GetHoverInfo(uint32(0), common.IndexPosition(20), entry) if err != nil { t.Fatal("getHoverInfo failed with error", err) } - - if hover.Contents != handlers.MountPointHoverField.Contents { - t.Fatal("getHoverInfo failed to return correct hover content. Got:", hover.Contents, "but expected:", handlers.MountPointHoverField.Contents) - } } println("Getting completions") { - completions, err := handlers.GetCompletion(entry.Line, uint32(0)) + completions, err := handlers.GetCompletion(entry, common.CursorPosition(0)) if err != nil { t.Fatal("getCompletion failed with error", err) @@ -79,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 { t.Fatal("getCompletion failed with error", err) } - expectedLength := len(utils.KeysOfMap(fstabdocumentation.MountOptionsMapField)) + expectedLength := len(utils.KeysOfMap(fields.MountOptionsMapField)) if len(completions) != expectedLength { t.Fatal("getCompletion failed to return correct number of completions. Got:", len(completions), "but expected:", expectedLength) } } - - println("Checking values") - { - diagnostics := p.AnalyzeValues() - - if len(diagnostics) > 0 { - t.Fatal("AnalyzeValues failed with error", diagnostics) - } - } } func TestInvalidOptionsExample(t *testing.T) { input := utils.Dedent(` LABEL=test /mnt/test btrfs subvol=backup,fat=32 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatal("ParseFromContent returned error", errors) } - - // 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) { @@ -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=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) } - diagnostics := p.AnalyzeValues() - - if len(diagnostics) > 0 { - t.Errorf("AnalyzeValues failed with error %v", diagnostics) - } } func TestArchExample2(t *testing.T) { @@ -173,10 +143,9 @@ func TestArchExample2(t *testing.T) { /dev/sda3 /home ext4 defaults 0 2 /dev/sda4 none swap defaults 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) @@ -190,20 +159,14 @@ LABEL=System / ext4 defaults 0 1 LABEL=Data /home ext4 defaults 0 2 LABEL=Swap none swap defaults 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) } - diagnostics := p.AnalyzeValues() - - if len(diagnostics) > 0 { - t.Errorf("AnalyzeValues failed with error %v", diagnostics) - } } 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=f9fe0b69-a280-415d-a03a-a32752370dee none swap defaults 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) } - - diagnostics := p.AnalyzeValues() - - if len(diagnostics) > 0 { - t.Errorf("AnalyzeValues failed with error %v", diagnostics) - } } func TestArchExample5(t *testing.T) { @@ -236,20 +192,13 @@ PARTLABEL=GNU/Linux / ext4 defaults 0 1 PARTLABEL=Home /home ext4 defaults 0 2 PARTLABEL=Swap none swap defaults 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) } - - diagnostics := p.AnalyzeValues() - - if len(diagnostics) > 0 { - t.Errorf("AnalyzeValues failed with error %v", diagnostics) - } } 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=039b6c1c-7553-4455-9537-1befbc9fbc5b none swap defaults 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) } - - diagnostics := p.AnalyzeValues() - - if len(diagnostics) > 0 { - t.Errorf("AnalyzeValues failed with error %v", diagnostics) - } } func TestLinuxConfigExample(t *testing.T) { input := utils.Dedent(` UUID=80b496fa-ce2d-4dcf-9afc-bcaa731a67f1 /mnt/example ext4 defaults 0 2 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) } - - diagnostics := p.AnalyzeValues() - - if len(diagnostics) > 0 { - t.Errorf("AnalyzeValues failed with error %v", diagnostics) - } } func Test1(t *testing.T) { input := utils.Dedent(` PARTLABEL="rootfs" / ext4 noatime,lazytime,rw 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) } - - diagnostics := p.AnalyzeValues() - - if len(diagnostics) > 0 { - t.Errorf("AnalyzeValues failed with error %v", diagnostics) - } } 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/sde /homeE xfs defaults 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) @@ -344,10 +271,9 @@ func Test3(t *testing.T) { 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 := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) diff --git a/server/handlers/fstab/handlers/completions.go b/server/handlers/fstab/handlers/completions.go index 8743fe6..e965789 100644 --- a/server/handlers/fstab/handlers/completions.go +++ b/server/handlers/fstab/handlers/completions.go @@ -1,6 +1,7 @@ package handlers import ( + "config-lsp/common" "config-lsp/doc-values" "config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/fields" @@ -10,7 +11,7 @@ import ( func GetCompletion( entry *ast.FstabEntry, - cursor uint32, + cursor common.CursorPosition, ) ([]protocol.CompletionItem, error) { targetField := entry.GetFieldAtPosition(cursor) @@ -18,21 +19,21 @@ func GetCompletion( case ast.FstabFieldSpec: value, cursor := getFieldSafely(entry.Fields.Spec, cursor) - return fstabdocumentation.SpecField.DeprecatedFetchCompletions( + return fields.SpecField.DeprecatedFetchCompletions( value, cursor, ), nil case ast.FstabFieldMountPoint: value, cursor := getFieldSafely(entry.Fields.MountPoint, cursor) - return fstabdocumentation.MountPointField.DeprecatedFetchCompletions( + return fields.MountPointField.DeprecatedFetchCompletions( value, cursor, ), nil case ast.FstabFieldFileSystemType: value, cursor := getFieldSafely(entry.Fields.FilesystemType, cursor) - return fstabdocumentation.FileSystemTypeField.DeprecatedFetchCompletions( + return fields.FileSystemTypeField.DeprecatedFetchCompletions( value, cursor, ), nil @@ -41,10 +42,10 @@ func GetCompletion( var optionsField docvalues.DeprecatedValue - if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found { + if foundField, found := fields.MountOptionsMapField[fileSystemType]; found { optionsField = foundField } else { - optionsField = fstabdocumentation.DefaultMountOptionsField + optionsField = fields.DefaultMountOptionsField } value, cursor := getFieldSafely(entry.Fields.Options, cursor) @@ -58,14 +59,14 @@ func GetCompletion( case ast.FstabFieldFreq: value, cursor := getFieldSafely(entry.Fields.Freq, cursor) - return fstabdocumentation.FreqField.DeprecatedFetchCompletions( + return fields.FreqField.DeprecatedFetchCompletions( value, cursor, ), nil case ast.FstabFieldPass: value, cursor := getFieldSafely(entry.Fields.Pass, cursor) - return fstabdocumentation.PassField.DeprecatedFetchCompletions( + return fields.PassField.DeprecatedFetchCompletions( value, cursor, ), nil @@ -76,7 +77,7 @@ func GetCompletion( // Safely get value and new cursor position // 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 { return "", 0 } @@ -85,5 +86,5 @@ func getFieldSafely(field *ast.FstabField, character uint32) (string, uint32) { return "", 0 } - return field.Value.Raw, character - field.Start.Character + return field.Value.Raw, common.CursorToCharacterIndex(uint32(cursor)) - field.Start.Character } diff --git a/server/handlers/fstab/handlers/hover.go b/server/handlers/fstab/handlers/hover.go index 7f07045..1c4997d 100644 --- a/server/handlers/fstab/handlers/hover.go +++ b/server/handlers/fstab/handlers/hover.go @@ -1,6 +1,7 @@ package handlers import ( + "config-lsp/common" "config-lsp/doc-values" "config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/fields" @@ -11,10 +12,10 @@ import ( func GetHoverInfo( line uint32, - cursor uint32, + index common.IndexPosition, entry *ast.FstabEntry, ) (*protocol.Hover, error) { - targetField := entry.GetFieldAtPosition(cursor) + targetField := entry.GetFieldAtPosition(index) switch targetField { case ast.FstabFieldSpec: @@ -27,13 +28,13 @@ func GetHoverInfo( fileSystemType := entry.Fields.FilesystemType.Value.Value var optionsField docvalues.DeprecatedValue - if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found { + if foundField, found := fields.MountOptionsMapField[fileSystemType]; found { optionsField = foundField } 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) hover := protocol.Hover{ diff --git a/server/handlers/fstab/lsp/text-document-completion.go b/server/handlers/fstab/lsp/text-document-completion.go index f91c598..f2fc9cc 100644 --- a/server/handlers/fstab/lsp/text-document-completion.go +++ b/server/handlers/fstab/lsp/text-document-completion.go @@ -12,9 +12,10 @@ import ( ) 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 { // Empty line, return spec completions @@ -26,7 +27,5 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa entry := rawEntry.(*ast.FstabEntry) - cursor := common.CursorToCharacterIndex(params.Position.Character) - return handlers.GetCompletion(entry, cursor) } diff --git a/server/handlers/fstab/lsp/text-document-did-change.go b/server/handlers/fstab/lsp/text-document-did-change.go index a997aec..c095cf6 100644 --- a/server/handlers/fstab/lsp/text-document-did-change.go +++ b/server/handlers/fstab/lsp/text-document-did-change.go @@ -2,8 +2,10 @@ package lsp import ( "config-lsp/common" + "config-lsp/handlers/fstab/analyzer" "config-lsp/handlers/fstab/shared" "config-lsp/utils" + "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) @@ -15,11 +17,11 @@ func TextDocumentDidChange( content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text common.ClearDiagnostics(context, params.TextDocument.URI) - p := shared.DocumentParserMap[params.TextDocument.URI] - p.Clear() + d := shared.DocumentParserMap[params.TextDocument.URI] + d.Config.Clear() diagnostics := make([]protocol.Diagnostic, 0) - errors := p.Parse(content) + errors := d.Config.Parse(content) if len(errors) > 0 { diagnostics = append(diagnostics, utils.Map( @@ -29,7 +31,7 @@ func TextDocumentDidChange( }, )...) } else { - // diagnostics = append(diagnostics, p.AnalyzeValues()...) + diagnostics = append(diagnostics, analyzer.Analyze(d)...) } if len(diagnostics) > 0 { diff --git a/server/handlers/fstab/lsp/text-document-did-open.go b/server/handlers/fstab/lsp/text-document-did-open.go index 7e89ca6..1bbe1ae 100644 --- a/server/handlers/fstab/lsp/text-document-did-open.go +++ b/server/handlers/fstab/lsp/text-document-did-open.go @@ -2,6 +2,7 @@ package lsp import ( "config-lsp/common" + "config-lsp/handlers/fstab/analyzer" "config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/shared" "config-lsp/utils" @@ -16,13 +17,16 @@ func TextDocumentDidOpen( ) error { common.ClearDiagnostics(context, params.TextDocument.URI) - p := ast.NewFstabConfig() - shared.DocumentParserMap[params.TextDocument.URI] = p + config := ast.NewFstabConfig() + d := &shared.FstabDocument{ + Config: config, + } + shared.DocumentParserMap[params.TextDocument.URI] = d content := params.TextDocument.Text diagnostics := make([]protocol.Diagnostic, 0) - errors := p.Parse(content) + errors := d.Config.Parse(content) if len(errors) > 0 { diagnostics = append(diagnostics, utils.Map( @@ -32,7 +36,7 @@ func TextDocumentDidOpen( }, )...) } else { - // diagnostics = append(diagnostics, p.AnalyzeValues()...) + diagnostics = append(diagnostics, analyzer.Analyze(d)...) } if len(diagnostics) > 0 { diff --git a/server/handlers/fstab/lsp/text-document-hover.go b/server/handlers/fstab/lsp/text-document-hover.go index c29ab2f..62c6d6b 100644 --- a/server/handlers/fstab/lsp/text-document-hover.go +++ b/server/handlers/fstab/lsp/text-document-hover.go @@ -1,6 +1,7 @@ package lsp import ( + "config-lsp/common" "config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/handlers" "config-lsp/handlers/fstab/shared" @@ -11,11 +12,11 @@ import ( func TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) { 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 if !found { @@ -26,7 +27,7 @@ func TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*pr return handlers.GetHoverInfo( line, - cursor, + index, entry, ) } diff --git a/server/handlers/fstab/parser/parser.go b/server/handlers/fstab/parser/parser.go deleted file mode 100644 index 3d9b2a7..0000000 --- a/server/handlers/fstab/parser/parser.go +++ /dev/null @@ -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 -} diff --git a/server/handlers/fstab/shared/document.go b/server/handlers/fstab/shared/document.go index a8fc0d2..aeaf132 100644 --- a/server/handlers/fstab/shared/document.go +++ b/server/handlers/fstab/shared/document.go @@ -6,5 +6,8 @@ import ( 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{} diff --git a/server/handlers/fstab/test_utils/input.go b/server/handlers/fstab/test_utils/input.go new file mode 100644 index 0000000..3e7b856 --- /dev/null +++ b/server/handlers/fstab/test_utils/input.go @@ -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, + } +}