From 1f4717debaad8d93a5d2281e83408d700abe1eac Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:11:02 +0200 Subject: [PATCH 01/12] refactor(fstab): Adapt fstab to new style; Adapting the parser --- server/handlers/fstab/ast/fstab.go | 45 +++++++ server/handlers/fstab/ast/parser.go | 159 +++++++++++++++++++++++ server/handlers/fstab/ast/parser_test.go | 75 +++++++++++ 3 files changed, 279 insertions(+) create mode 100644 server/handlers/fstab/ast/fstab.go create mode 100644 server/handlers/fstab/ast/parser.go create mode 100644 server/handlers/fstab/ast/parser_test.go diff --git a/server/handlers/fstab/ast/fstab.go b/server/handlers/fstab/ast/fstab.go new file mode 100644 index 0000000..125e624 --- /dev/null +++ b/server/handlers/fstab/ast/fstab.go @@ -0,0 +1,45 @@ +package ast + +import ( + "config-lsp/common" + commonparser "config-lsp/common/parser" + "github.com/emirpasic/gods/maps/treemap" +) + +type FstabFieldName string + +const ( + FstabFieldSpec FstabFieldName = "spec" + FstabFieldMountPoint FstabFieldName = "mountpoint" + FstabFieldFileSystemType FstabFieldName = "filesystemtype" + FstabFieldOptions FstabFieldName = "options" + FstabFieldFreq FstabFieldName = "freq" + FstabFieldPass FstabFieldName = "pass" +) + +type FstabField struct { + common.LocationRange + Value commonparser.ParsedString +} + +type FstabFields struct { + common.LocationRange + Spec *FstabField + MountPoint *FstabField + FilesystemType *FstabField + Options *FstabField + Freq *FstabField + Pass *FstabField +} + +type FstabEntry struct { + Fields FstabFields +} + +type FstabConfig struct { + // [uint32]FstabEntry - line number to line mapping + Entries *treemap.Map + + // [uint32]{} - line number to empty struct for comments + CommentLines map[uint32]struct{} +} diff --git a/server/handlers/fstab/ast/parser.go b/server/handlers/fstab/ast/parser.go new file mode 100644 index 0000000..78acef8 --- /dev/null +++ b/server/handlers/fstab/ast/parser.go @@ -0,0 +1,159 @@ +package ast + +import ( + "config-lsp/common" + commonparser "config-lsp/common/parser" + "config-lsp/utils" + "regexp" + + "github.com/emirpasic/gods/maps/treemap" + + gods "github.com/emirpasic/gods/utils" +) + +func NewFstabConfig() *FstabConfig { + config := &FstabConfig{} + config.Clear() + + return config +} + +func (c *FstabConfig) Clear() { + c.Entries = treemap.NewWith(gods.UInt32Comparator) + c.CommentLines = map[uint32]struct{}{} +} + +var commentPattern = regexp.MustCompile(`^\s*#`) +var emptyPattern = regexp.MustCompile(`^\s*$`) +var whitespacePattern = regexp.MustCompile(`\S+`) + +func (c *FstabConfig) Parse(input string) []common.LSPError { + errors := make([]common.LSPError, 0) + lines := utils.SplitIntoLines(input) + + for rawLineNumber, line := range lines { + lineNumber := uint32(rawLineNumber) + + if emptyPattern.MatchString(line) { + continue + } + + if commentPattern.MatchString(line) { + c.CommentLines[lineNumber] = struct{}{} + continue + } + + errors = append( + errors, + c.parseStatement(lineNumber, line)..., + ) + } + + return errors +} + +func (c *FstabConfig) parseStatement( + line uint32, + input string, +) []common.LSPError { + fields := whitespacePattern.FindAllStringIndex(input, -1) + + if len(fields) == 0 { + return []common.LSPError{ + { + Range: common.LocationRange{ + Start: common.Location{ + Line: line, + Character: 0, + }, + End: common.Location{ + Line: line, + Character: 0, + }, + }, + }, + } + } + + var spec *FstabField + var mountPoint *FstabField + var filesystemType *FstabField + var options *FstabField + var freq *FstabField + var pass *FstabField + + switch len(fields) { + case 6: + pass = parseField(line, input, fields[5]) + fallthrough + case 5: + freq = parseField(line, input, fields[4]) + fallthrough + case 4: + options = parseField(line, input, fields[3]) + fallthrough + case 3: + filesystemType = parseField(line, input, fields[2]) + fallthrough + case 2: + mountPoint = parseField(line, input, fields[1]) + fallthrough + case 1: + spec = parseField(line, input, fields[0]) + } + + fstabLine := FstabEntry{ + Fields: FstabFields{ + LocationRange: common.LocationRange{ + Start: common.Location{ + Line: line, + Character: 0, + }, + End: common.Location{ + Line: line, + Character: uint32(len(input)), + }, + }, + Spec: spec, + MountPoint: mountPoint, + FilesystemType: filesystemType, + Options: options, + Freq: freq, + Pass: pass, + }, + } + + c.Entries.Put(line, fstabLine) + + return nil +} + +func parseField( + line uint32, + input string, + field []int, +) *FstabField { + start := uint32(field[0]) + end := uint32(field[1]) + value := input[start:end] + + return &FstabField{ + LocationRange: common.LocationRange{ + Start: common.Location{ + Line: line, + Character: start, + }, + End: common.Location{ + Line: line, + Character: end, + }, + }, + Value: commonparser.ParseRawString(value, commonparser.ParseFeatures{ + ParseEscapedCharacters: true, + ParseDoubleQuotes: true, + Replacements: &map[string]string{ + `\\040`: " ", + }, + }), + } +} diff --git a/server/handlers/fstab/ast/parser_test.go b/server/handlers/fstab/ast/parser_test.go new file mode 100644 index 0000000..199eb02 --- /dev/null +++ b/server/handlers/fstab/ast/parser_test.go @@ -0,0 +1,75 @@ +package ast + +import ( + "config-lsp/utils" + "testing" +) + +func TestExample1( + t *testing.T, +) { + input := utils.Dedent(` +LABEL=test /mnt/test ext4 defaults 0 0 +`) + c := NewFstabConfig() + + errors := c.Parse(input) + + if len(errors) > 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + if c.Entries.Size() != 1 { + t.Fatalf("Expected 1 entry, got %d", c.Entries.Size()) + } + + rawFirstEntry, _ := c.Entries.Get(uint32(0)) + firstEntry := rawFirstEntry.(FstabEntry) + if !(firstEntry.Fields.Spec.Value.Value == "LABEL=test" && firstEntry.Fields.MountPoint.Value.Value == "/mnt/test" && firstEntry.Fields.FilesystemType.Value.Value == "ext4" && firstEntry.Fields.Options.Value.Value == "defaults" && firstEntry.Fields.Freq.Value.Value == "0" && firstEntry.Fields.Pass.Value.Value == "0") { + t.Fatalf("Expected entry to be LABEL=test /mnt/test ext4 defaults 0 0, got %v", firstEntry) + } + + if !(firstEntry.Fields.Spec.LocationRange.Start.Line == 0 && firstEntry.Fields.Spec.LocationRange.Start.Character == 0) { + t.Errorf("Expected spec start to be 0:0, got %v", firstEntry.Fields.Spec.LocationRange.Start) + } + + if !(firstEntry.Fields.Spec.LocationRange.End.Line == 0 && firstEntry.Fields.Spec.LocationRange.End.Character == 10) { + t.Errorf("Expected spec end to be 0:10, got %v", firstEntry.Fields.Spec.LocationRange.End) + } + + if !(firstEntry.Fields.MountPoint.LocationRange.Start.Line == 0 && firstEntry.Fields.MountPoint.LocationRange.Start.Character == 11) { + t.Errorf("Expected mountpoint start to be 0:11, got %v", firstEntry.Fields.MountPoint.LocationRange.Start) + } + + if !(firstEntry.Fields.MountPoint.LocationRange.End.Line == 0 && firstEntry.Fields.MountPoint.LocationRange.End.Character == 20) { + t.Errorf("Expected mountpoint end to be 0:20, got %v", firstEntry.Fields.MountPoint.LocationRange.End) + } + + if !(firstEntry.Fields.FilesystemType.LocationRange.Start.Line == 0 && firstEntry.Fields.FilesystemType.LocationRange.Start.Character == 21) { + t.Errorf("Expected filesystemtype start to be 0:21, got %v", firstEntry.Fields.FilesystemType.LocationRange.Start) + } + + if !(firstEntry.Fields.FilesystemType.LocationRange.End.Line == 0 && firstEntry.Fields.FilesystemType.LocationRange.End.Character == 25) { + t.Errorf("Expected filesystemtype end to be 0:25, got %v", firstEntry.Fields.FilesystemType.LocationRange.End) + } + + if !(firstEntry.Fields.Options.LocationRange.Start.Line == 0 && firstEntry.Fields.Options.LocationRange.Start.Character == 26) { + t.Errorf("Expected options start to be 0:26, got %v", firstEntry.Fields.Options.LocationRange.Start) + } + + if !(firstEntry.Fields.Options.LocationRange.End.Line == 0 && firstEntry.Fields.Options.LocationRange.End.Character == 34) { + t.Errorf("Expected options end to be 0:34, got %v", firstEntry.Fields.Options.LocationRange.End) + } + + if !(firstEntry.Fields.Freq.LocationRange.Start.Line == 0 && firstEntry.Fields.Freq.LocationRange.Start.Character == 35) { + t.Errorf("Expected freq start to be 0:35, got %v", firstEntry.Fields.Freq.LocationRange.Start) + } + + if !(firstEntry.Fields.Freq.LocationRange.End.Line == 0 && firstEntry.Fields.Freq.LocationRange.End.Character == 36) { + t.Errorf("Expected freq end to be 0:36, got %v", firstEntry.Fields.Freq.LocationRange.End) + } + + if !(firstEntry.Fields.Pass.LocationRange.Start.Line == 0 && firstEntry.Fields.Pass.LocationRange.Start.Character == 37) { + t.Errorf("Expected pass start to be 0:37, got %v", firstEntry.Fields.Pass.LocationRange.Start) + } +} From 558b9b7c984f7a1eaa5488518d7a02823195e780 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:33:19 +0200 Subject: [PATCH 02/12] refactor(fstab): Migrating more stuff --- server/handlers/fstab/ast/fstab.go | 1 + server/handlers/fstab/ast/fstab_fields.go | 35 ++++++ .../documentation-freq.go => fields/freq.go} | 0 .../mount-point.go} | 0 .../mountoptions.go} | 24 ++-- .../documentation-pass.go => fields/pass.go} | 0 .../documentation-spec.go => fields/spec.go} | 2 +- .../documentation-type.go => fields/type.go} | 0 server/handlers/fstab/fstab_test.go | 110 +++++++++++++++++- server/handlers/fstab/handlers/completions.go | 41 +++---- server/handlers/fstab/handlers/hover.go | 34 +++--- .../fstab/lsp/text-document-completion.go | 15 +-- .../fstab/lsp/text-document-did-change.go | 6 +- .../fstab/lsp/text-document-did-open.go | 14 +-- .../handlers/fstab/lsp/text-document-hover.go | 17 +-- server/handlers/fstab/parser/parser.go | 4 +- server/handlers/fstab/shared/document.go | 6 +- 17 files changed, 229 insertions(+), 80 deletions(-) create mode 100644 server/handlers/fstab/ast/fstab_fields.go rename server/handlers/fstab/{documentation/documentation-freq.go => fields/freq.go} (100%) rename server/handlers/fstab/{documentation/documentation-mount-point.go => fields/mount-point.go} (100%) rename server/handlers/fstab/{documentation/documentation-mountoptions.go => fields/mountoptions.go} (86%) rename server/handlers/fstab/{documentation/documentation-pass.go => fields/pass.go} (100%) rename server/handlers/fstab/{documentation/documentation-spec.go => fields/spec.go} (90%) rename server/handlers/fstab/{documentation/documentation-type.go => fields/type.go} (100%) diff --git a/server/handlers/fstab/ast/fstab.go b/server/handlers/fstab/ast/fstab.go index 125e624..6346ecb 100644 --- a/server/handlers/fstab/ast/fstab.go +++ b/server/handlers/fstab/ast/fstab.go @@ -43,3 +43,4 @@ 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 new file mode 100644 index 0000000..bbf52db --- /dev/null +++ b/server/handlers/fstab/ast/fstab_fields.go @@ -0,0 +1,35 @@ +package ast + +// func (c FstabConfig) GetEntry(line uint32) *FstabEntry { +// entry, found := c.Entries.Get(line) +// +// if !found { +// return nil +// } +// +// 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) { + return FstabFieldSpec + } + + if e.Fields.MountPoint == nil || (cursor >= e.Fields.MountPoint.Start.Character && cursor <= e.Fields.MountPoint.End.Character) { + return FstabFieldMountPoint + } + + if e.Fields.FilesystemType == nil || (cursor >= e.Fields.FilesystemType.Start.Character && cursor <= e.Fields.FilesystemType.End.Character) { + return FstabFieldFileSystemType + } + + if e.Fields.Options == nil || (cursor >= e.Fields.Options.Start.Character && cursor <= e.Fields.Options.End.Character) { + return FstabFieldOptions + } + + if e.Fields.Freq == nil || (cursor >= e.Fields.Freq.Start.Character && cursor <= e.Fields.Freq.End.Character) { + return FstabFieldFreq + } + + return FstabFieldPass +} diff --git a/server/handlers/fstab/documentation/documentation-freq.go b/server/handlers/fstab/fields/freq.go similarity index 100% rename from server/handlers/fstab/documentation/documentation-freq.go rename to server/handlers/fstab/fields/freq.go diff --git a/server/handlers/fstab/documentation/documentation-mount-point.go b/server/handlers/fstab/fields/mount-point.go similarity index 100% rename from server/handlers/fstab/documentation/documentation-mount-point.go rename to server/handlers/fstab/fields/mount-point.go diff --git a/server/handlers/fstab/documentation/documentation-mountoptions.go b/server/handlers/fstab/fields/mountoptions.go similarity index 86% rename from server/handlers/fstab/documentation/documentation-mountoptions.go rename to server/handlers/fstab/fields/mountoptions.go index 191bd3d..a268551 100644 --- a/server/handlers/fstab/documentation/documentation-mountoptions.go +++ b/server/handlers/fstab/fields/mountoptions.go @@ -193,13 +193,13 @@ var defaultOptions = []docvalues.EnumString{ ), docvalues.CreateEnumStringWithDoc( "x-systemd.automount", - `An automount unit will be created for the file system. See systemd.automount(5) for details. + `An automount unit will be created for the file system. See systemd.automount(5) for details. Added in version 215.`, ), docvalues.CreateEnumStringWithDoc( "x-systemd.makefs", - `The file system will be initialized on the device. If the device is not "empty", i.e. it contains any signature, the operation will be skipped. It is hence expected that this option remains set even after the device has been initialized. + `The file system will be initialized on the device. If the device is not "empty", i.e. it contains any signature, the operation will be skipped. It is hence expected that this option remains set even after the device has been initialized. Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file. @@ -211,7 +211,7 @@ Added in version 236.`, ), docvalues.CreateEnumStringWithDoc( "x-systemd.growfs", - `The file system will be grown to occupy the full block device. If the file system is already at maximum size, no action will be performed. It is hence expected that this option remains set even after the file system has been grown. Only certain file system types are supported, see systemd-makefs@.service(8) for details. + `The file system will be grown to occupy the full block device. If the file system is already at maximum size, no action will be performed. It is hence expected that this option remains set even after the file system has been grown. Only certain file system types are supported, see systemd-makefs@.service(8) for details. Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file. @@ -219,7 +219,7 @@ Added in version 236.`, ), docvalues.CreateEnumStringWithDoc( "x-systemd.pcrfs", - `Measures file system identity information (mount point, type, label, UUID, partition label, partition UUID) into PCR 15 after the file system has been mounted. This ensures the systemd-pcrfs@.service(8) or systemd-pcrfs-root.service services are pulled in by the mount unit. + `Measures file system identity information (mount point, type, label, UUID, partition label, partition UUID) into PCR 15 after the file system has been mounted. This ensures the systemd-pcrfs@.service(8) or systemd-pcrfs-root.service services are pulled in by the mount unit. Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file. It is also implied for the root and /usr/ partitions discovered by systemd-gpt-auto-generator(8). @@ -227,13 +227,13 @@ Added in version 253.`, ), docvalues.CreateEnumStringWithDoc( "x-systemd.rw-only", - `If a mount operation fails to mount the file system read-write, it normally tries mounting the file system read-only instead. This option disables that behaviour, and causes the mount to fail immediately instead. This option is translated into the ReadWriteOnly= setting in a unit file. + `If a mount operation fails to mount the file system read-write, it normally tries mounting the file system read-only instead. This option disables that behaviour, and causes the mount to fail immediately instead. This option is translated into the ReadWriteOnly= setting in a unit file. Added in version 246.`, ), docvalues.CreateEnumStringWithDoc( "x-initrd.mount", - `An additional filesystem to be mounted in the initrd. See initrd-fs.target description in systemd.special(7). This is both an indicator to the initrd to mount this partition early and an indicator to the host to leave the partition mounted until final shutdown. Or in other words, if this flag is set it is assumed the mount shall be active during the entire regular runtime of the system, i.e. established before the initrd transitions into the host all the way until the host transitions to the final shutdown phase. + `An additional filesystem to be mounted in the initrd. See initrd-fs.target description in systemd.special(7). This is both an indicator to the initrd to mount this partition early and an indicator to the host to leave the partition mounted until final shutdown. Or in other words, if this flag is set it is assumed the mount shall be active during the entire regular runtime of the system, i.e. established before the initrd transitions into the host all the way until the host transitions to the final shutdown phase. Added in version 215.`, ), @@ -299,19 +299,19 @@ Added in version 245.`, ): docvalues.StringValue{}, docvalues.CreateEnumStringWithDoc( "x-systemd.wants-mounts-for", - `Configures a RequiresMountsFor= or WantsMountsFor= dependency between the created mount unit and other mount units. The argument must be an absolute path. This option may be specified more than once. See RequiresMountsFor= or WantsMountsFor= in systemd.unit(5) for details. + `Configures a RequiresMountsFor= or WantsMountsFor= dependency between the created mount unit and other mount units. The argument must be an absolute path. This option may be specified more than once. See RequiresMountsFor= or WantsMountsFor= in systemd.unit(5) for details. Added in version 220.`, ): docvalues.StringValue{}, docvalues.CreateEnumStringWithDoc( "x-systemd.requires-mounts-for", - `Configures a RequiresMountsFor= or WantsMountsFor= dependency between the created mount unit and other mount units. The argument must be an absolute path. This option may be specified more than once. See RequiresMountsFor= or WantsMountsFor= in systemd.unit(5) for details. + `Configures a RequiresMountsFor= or WantsMountsFor= dependency between the created mount unit and other mount units. The argument must be an absolute path. This option may be specified more than once. See RequiresMountsFor= or WantsMountsFor= in systemd.unit(5) for details. Added in version 220.`, ): docvalues.StringValue{}, docvalues.CreateEnumStringWithDoc( "x-systemd.device-bound", - `Takes a boolean argument. If true or no argument, a BindsTo= dependency on the backing device is set. If false, the mount unit is not stopped no matter whether the backing device is still present. This is useful when the file system is backed by volume managers. If not set, and the mount comes from unit fragments, i.e. generated from /etc/fstab by systemd-fstab-generator(8) or loaded from a manually configured mount unit, a combination of Requires= and StopPropagatedFrom= dependencies is set on the backing device. If doesn't, only Requires= is used. + `Takes a boolean argument. If true or no argument, a BindsTo= dependency on the backing device is set. If false, the mount unit is not stopped no matter whether the backing device is still present. This is useful when the file system is backed by volume managers. If not set, and the mount comes from unit fragments, i.e. generated from /etc/fstab by systemd-fstab-generator(8) or loaded from a manually configured mount unit, a combination of Requires= and StopPropagatedFrom= dependencies is set on the backing device. If doesn't, only Requires= is used. Added in version 233.`, ): docvalues.EnumValue{ @@ -323,13 +323,13 @@ Added in version 233.`, }, docvalues.CreateEnumStringWithDoc( "x-systemd.idle-timeout", - `Configures the idle timeout of the automount unit. See TimeoutIdleSec= in systemd.automount(5) for details. + `Configures the idle timeout of the automount unit. See TimeoutIdleSec= in systemd.automount(5) for details. Added in version 220.`, ): docvalues.StringValue{}, docvalues.CreateEnumStringWithDoc( "x-systemd.device-timeout", - `Configure how long systemd should wait for a device to show up before giving up on an entry from /etc/fstab. Specify a time in seconds or explicitly append a unit such as "s", "min", "h", "ms". + `Configure how long systemd should wait for a device to show up before giving up on an entry from /etc/fstab. Specify a time in seconds or explicitly append a unit such as "s", "min", "h", "ms". Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file. @@ -337,7 +337,7 @@ Added in version 215.`, ): docvalues.StringValue{}, docvalues.CreateEnumStringWithDoc( "x-systemd.mount-timeout", - `Configure how long systemd should wait for the mount command to finish before giving up on an entry from /etc/fstab. Specify a time in seconds or explicitly append a unit such as "s", "min", "h", "ms". + `Configure how long systemd should wait for the mount command to finish before giving up on an entry from /etc/fstab. Specify a time in seconds or explicitly append a unit such as "s", "min", "h", "ms". Note that this option can only be used in /etc/fstab, and will be ignored when part of the Options= setting in a unit file. diff --git a/server/handlers/fstab/documentation/documentation-pass.go b/server/handlers/fstab/fields/pass.go similarity index 100% rename from server/handlers/fstab/documentation/documentation-pass.go rename to server/handlers/fstab/fields/pass.go diff --git a/server/handlers/fstab/documentation/documentation-spec.go b/server/handlers/fstab/fields/spec.go similarity index 90% rename from server/handlers/fstab/documentation/documentation-spec.go rename to server/handlers/fstab/fields/spec.go index 2cf7db2..5d63a39 100644 --- a/server/handlers/fstab/documentation/documentation-spec.go +++ b/server/handlers/fstab/fields/spec.go @@ -15,7 +15,7 @@ var LabelField = docvalues.RegexValue{ var SpecField = docvalues.OrValue{ Values: []docvalues.DeprecatedValue{ docvalues.PathValue{ - RequiredType: docvalues.PathTypeFile & docvalues.PathTypeExistenceOptional, + RequiredType: docvalues.PathTypeFile, }, docvalues.KeyEnumAssignmentValue{ Separator: "=", diff --git a/server/handlers/fstab/documentation/documentation-type.go b/server/handlers/fstab/fields/type.go similarity index 100% rename from server/handlers/fstab/documentation/documentation-type.go rename to server/handlers/fstab/fields/type.go diff --git a/server/handlers/fstab/fstab_test.go b/server/handlers/fstab/fstab_test.go index 629c690..9f16e01 100644 --- a/server/handlers/fstab/fstab_test.go +++ b/server/handlers/fstab/fstab_test.go @@ -1,7 +1,7 @@ package fstab import ( - fstabdocumentation "config-lsp/handlers/fstab/documentation" + fstabdocumentation "config-lsp/handlers/fstab/fields" handlers "config-lsp/handlers/fstab/handlers" "config-lsp/handlers/fstab/parser" "config-lsp/utils" @@ -158,6 +158,12 @@ UUID=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2 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) { @@ -192,6 +198,12 @@ LABEL=Swap none swap defaults 0 0 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) { @@ -209,6 +221,12 @@ UUID=f9fe0b69-a280-415d-a03a-a32752370dee none swap defaults 0 0 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) { @@ -226,6 +244,12 @@ PARTLABEL=Swap none swap defaults 0 0 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) { @@ -243,5 +267,89 @@ PARTUUID=039b6c1c-7553-4455-9537-1befbc9fbc5b none swap defaults 0 0 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() + + errors := p.ParseFromContent(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() + + errors := p.ParseFromContent(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) { + input := utils.Dedent(` +/dev/sda /home1 xfs defaults 1 2 +/dev/sdb /homeB xfs noauto,nobarrier,rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0 +/dev/sdc /homeC xfs noauto,defaults 0 0 +/dev/sdd /homeD xfs noauto,rw,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0 +/dev/sde /homeE xfs defaults 0 0 +`) + p := parser.FstabParser{} + p.Clear() + + errors := p.ParseFromContent(input) + + if len(errors) > 0 { + t.Fatalf("ParseFromContent failed with error %v", errors) + } +} + +func Test3(t *testing.T) { + input := utils.Dedent(` +/dev/disk/by-label/dsp /dsp auto ro +/dev/disk/by-partlabel/modem_a /firmware auto ro +/dev/disk/by-partlabel/persist /persist auto ro,discard,nosuid,nodev,noexec +/dev/disk/by-partlabel/userdata /data auto discard,noatime,nodiratime,nosuid,nodev,nofail 0 0 +/dev/disk/by-partlabel/cache /cache ext4 relatime,data=ordered,noauto_da_alloc,discard,noexec,nodev,nosuid,x-systemd.makefs 0 0 +/dev/nvme0n1 /data/media auto discard,nosuid,nodev,nofail,x-systemd.device-timeout=5s 0 0 +tmpfs /var tmpfs rw,nosuid,nodev,size=128M,mode=755 0 0 +tmpfs /tmp tmpfs rw,nosuid,nodev,size=150M,mode=1777 0 0 +`) + p := parser.FstabParser{} + p.Clear() + + errors := p.ParseFromContent(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 35b79e2..8743fe6 100644 --- a/server/handlers/fstab/handlers/completions.go +++ b/server/handlers/fstab/handlers/completions.go @@ -2,41 +2,42 @@ package handlers import ( "config-lsp/doc-values" - "config-lsp/handlers/fstab/documentation" - "config-lsp/handlers/fstab/parser" + "config-lsp/handlers/fstab/ast" + "config-lsp/handlers/fstab/fields" + "github.com/tliron/glsp/protocol_3_16" ) func GetCompletion( - line parser.FstabLine, + entry *ast.FstabEntry, cursor uint32, ) ([]protocol.CompletionItem, error) { - targetField := line.GetFieldAtPosition(cursor) + targetField := entry.GetFieldAtPosition(cursor) switch targetField { - case parser.FstabFieldSpec: - value, cursor := GetFieldSafely(line.Fields.Spec, cursor) + case ast.FstabFieldSpec: + value, cursor := getFieldSafely(entry.Fields.Spec, cursor) return fstabdocumentation.SpecField.DeprecatedFetchCompletions( value, cursor, ), nil - case parser.FstabFieldMountPoint: - value, cursor := GetFieldSafely(line.Fields.MountPoint, cursor) + case ast.FstabFieldMountPoint: + value, cursor := getFieldSafely(entry.Fields.MountPoint, cursor) return fstabdocumentation.MountPointField.DeprecatedFetchCompletions( value, cursor, ), nil - case parser.FstabFieldFileSystemType: - value, cursor := GetFieldSafely(line.Fields.FilesystemType, cursor) + case ast.FstabFieldFileSystemType: + value, cursor := getFieldSafely(entry.Fields.FilesystemType, cursor) return fstabdocumentation.FileSystemTypeField.DeprecatedFetchCompletions( value, cursor, ), nil - case parser.FstabFieldOptions: - fileSystemType := line.Fields.FilesystemType.Value + case ast.FstabFieldOptions: + fileSystemType := entry.Fields.FilesystemType.Value.Value var optionsField docvalues.DeprecatedValue @@ -46,7 +47,7 @@ func GetCompletion( optionsField = fstabdocumentation.DefaultMountOptionsField } - value, cursor := GetFieldSafely(line.Fields.Options, cursor) + value, cursor := getFieldSafely(entry.Fields.Options, cursor) completions := optionsField.DeprecatedFetchCompletions( value, @@ -54,15 +55,15 @@ func GetCompletion( ) return completions, nil - case parser.FstabFieldFreq: - value, cursor := GetFieldSafely(line.Fields.Freq, cursor) + case ast.FstabFieldFreq: + value, cursor := getFieldSafely(entry.Fields.Freq, cursor) return fstabdocumentation.FreqField.DeprecatedFetchCompletions( value, cursor, ), nil - case parser.FstabFieldPass: - value, cursor := GetFieldSafely(line.Fields.Pass, cursor) + case ast.FstabFieldPass: + value, cursor := getFieldSafely(entry.Fields.Pass, cursor) return fstabdocumentation.PassField.DeprecatedFetchCompletions( value, @@ -75,14 +76,14 @@ func GetCompletion( // Safely get value and new cursor position // If field is nil, return empty string and 0 -func GetFieldSafely(field *parser.Field, character uint32) (string, uint32) { +func getFieldSafely(field *ast.FstabField, character uint32) (string, uint32) { if field == nil { return "", 0 } - if field.Value == "" { + if field.Value.Value == "" { return "", 0 } - return field.Value, character - field.Start + return field.Value.Raw, character - field.Start.Character } diff --git a/server/handlers/fstab/handlers/hover.go b/server/handlers/fstab/handlers/hover.go index 33fd213..7f07045 100644 --- a/server/handlers/fstab/handlers/hover.go +++ b/server/handlers/fstab/handlers/hover.go @@ -2,25 +2,29 @@ package handlers import ( "config-lsp/doc-values" - "config-lsp/handlers/fstab/documentation" - "config-lsp/handlers/fstab/parser" - "github.com/tliron/glsp/protocol_3_16" + "config-lsp/handlers/fstab/ast" + "config-lsp/handlers/fstab/fields" "strings" + + "github.com/tliron/glsp/protocol_3_16" ) -func GetHoverInfo(entry *parser.FstabEntry, cursor uint32) (*protocol.Hover, error) { - line := entry.Line - targetField := line.GetFieldAtPosition(cursor) +func GetHoverInfo( + line uint32, + cursor uint32, + entry *ast.FstabEntry, +) (*protocol.Hover, error) { + targetField := entry.GetFieldAtPosition(cursor) switch targetField { - case parser.FstabFieldSpec: + case ast.FstabFieldSpec: return &SpecHoverField, nil - case parser.FstabFieldMountPoint: + case ast.FstabFieldMountPoint: return &MountPointHoverField, nil - case parser.FstabFieldFileSystemType: + case ast.FstabFieldFileSystemType: return &FileSystemTypeField, nil - case parser.FstabFieldOptions: - fileSystemType := line.Fields.FilesystemType.Value + case ast.FstabFieldOptions: + fileSystemType := entry.Fields.FilesystemType.Value.Value var optionsField docvalues.DeprecatedValue if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found { @@ -29,8 +33,8 @@ func GetHoverInfo(entry *parser.FstabEntry, cursor uint32) (*protocol.Hover, err optionsField = fstabdocumentation.DefaultMountOptionsField } - relativeCursor := cursor - line.Fields.Options.Start - fieldInfo := optionsField.DeprecatedFetchHoverInfo(line.Fields.Options.Value, relativeCursor) + relativeCursor := cursor - entry.Fields.Options.Start.Character + fieldInfo := optionsField.DeprecatedFetchHoverInfo(entry.Fields.Options.Value.Value, relativeCursor) hover := protocol.Hover{ Contents: protocol.MarkupContent{ @@ -40,9 +44,9 @@ func GetHoverInfo(entry *parser.FstabEntry, cursor uint32) (*protocol.Hover, err } return &hover, nil - case parser.FstabFieldFreq: + case ast.FstabFieldFreq: return &FreqHoverField, nil - case parser.FstabFieldPass: + case ast.FstabFieldPass: return &PassHoverField, nil } diff --git a/server/handlers/fstab/lsp/text-document-completion.go b/server/handlers/fstab/lsp/text-document-completion.go index 375fbd2..f91c598 100644 --- a/server/handlers/fstab/lsp/text-document-completion.go +++ b/server/handlers/fstab/lsp/text-document-completion.go @@ -2,9 +2,9 @@ package lsp import ( "config-lsp/common" - fstabdocumentation "config-lsp/handlers/fstab/documentation" + "config-lsp/handlers/fstab/ast" + fstabdocumentation "config-lsp/handlers/fstab/fields" "config-lsp/handlers/fstab/handlers" - "config-lsp/handlers/fstab/parser" "config-lsp/handlers/fstab/shared" "github.com/tliron/glsp" @@ -12,9 +12,9 @@ import ( ) func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) { - p := shared.DocumentParserMap[params.TextDocument.URI] + c := shared.DocumentParserMap[params.TextDocument.URI] - entry, found := p.GetEntry(params.Position.Line) + rawEntry, found := c.Entries.Get(params.Position.Line) if !found { // Empty line, return spec completions @@ -24,12 +24,9 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa ), nil } - if entry.Type == parser.FstabEntryTypeComment { - return nil, nil - } + entry := rawEntry.(*ast.FstabEntry) cursor := common.CursorToCharacterIndex(params.Position.Character) - line := entry.Line - return handlers.GetCompletion(line, cursor) + return handlers.GetCompletion(entry, cursor) } diff --git a/server/handlers/fstab/lsp/text-document-did-change.go b/server/handlers/fstab/lsp/text-document-did-change.go index 2864e19..a997aec 100644 --- a/server/handlers/fstab/lsp/text-document-did-change.go +++ b/server/handlers/fstab/lsp/text-document-did-change.go @@ -19,17 +19,17 @@ func TextDocumentDidChange( p.Clear() diagnostics := make([]protocol.Diagnostic, 0) - errors := p.ParseFromContent(content) + errors := p.Parse(content) if len(errors) > 0 { diagnostics = append(diagnostics, utils.Map( errors, - func(err common.ParseError) protocol.Diagnostic { + func(err common.LSPError) protocol.Diagnostic { return err.ToDiagnostic() }, )...) } else { - diagnostics = append(diagnostics, p.AnalyzeValues()...) + // diagnostics = append(diagnostics, p.AnalyzeValues()...) } 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 6658b21..7e89ca6 100644 --- a/server/handlers/fstab/lsp/text-document-did-open.go +++ b/server/handlers/fstab/lsp/text-document-did-open.go @@ -2,9 +2,10 @@ package lsp import ( "config-lsp/common" - "config-lsp/handlers/fstab/parser" + "config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/shared" "config-lsp/utils" + "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) @@ -15,24 +16,23 @@ func TextDocumentDidOpen( ) error { common.ClearDiagnostics(context, params.TextDocument.URI) - p := parser.FstabParser{} - p.Clear() - shared.DocumentParserMap[params.TextDocument.URI] = &p + p := ast.NewFstabConfig() + shared.DocumentParserMap[params.TextDocument.URI] = p content := params.TextDocument.Text diagnostics := make([]protocol.Diagnostic, 0) - errors := p.ParseFromContent(content) + errors := p.Parse(content) if len(errors) > 0 { diagnostics = append(diagnostics, utils.Map( errors, - func(err common.ParseError) protocol.Diagnostic { + func(err common.LSPError) protocol.Diagnostic { return err.ToDiagnostic() }, )...) } else { - diagnostics = append(diagnostics, p.AnalyzeValues()...) + // diagnostics = append(diagnostics, p.AnalyzeValues()...) } 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 310706e..c29ab2f 100644 --- a/server/handlers/fstab/lsp/text-document-hover.go +++ b/server/handlers/fstab/lsp/text-document-hover.go @@ -1,29 +1,32 @@ package lsp import ( + "config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/handlers" - "config-lsp/handlers/fstab/parser" "config-lsp/handlers/fstab/shared" + "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) func TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) { + line := params.Position.Line cursor := params.Position.Character p := shared.DocumentParserMap[params.TextDocument.URI] - entry, found := p.GetEntry(params.Position.Line) + rawEntry, found := p.Entries.Get(params.Position.Line) // Empty line if !found { return nil, nil } - // Comment line - if entry.Type == parser.FstabEntryTypeComment { - return nil, nil - } + entry := rawEntry.(*ast.FstabEntry) - return handlers.GetHoverInfo(entry, cursor) + return handlers.GetHoverInfo( + line, + cursor, + entry, + ) } diff --git a/server/handlers/fstab/parser/parser.go b/server/handlers/fstab/parser/parser.go index a57165a..3d9b2a7 100644 --- a/server/handlers/fstab/parser/parser.go +++ b/server/handlers/fstab/parser/parser.go @@ -3,7 +3,7 @@ package parser import ( "config-lsp/common" docvalues "config-lsp/doc-values" - fstabdocumentation "config-lsp/handlers/fstab/documentation" + fstabdocumentation "config-lsp/handlers/fstab/fields" "fmt" "regexp" "strings" @@ -378,8 +378,6 @@ func (p *FstabParser) AnalyzeValues() []protocol.Diagnostic { if len(newDiagnostics) > 0 { diagnostics = append(diagnostics, newDiagnostics...) } - case FstabEntryTypeComment: - // Do nothing } } diff --git a/server/handlers/fstab/shared/document.go b/server/handlers/fstab/shared/document.go index e25de3b..a8fc0d2 100644 --- a/server/handlers/fstab/shared/document.go +++ b/server/handlers/fstab/shared/document.go @@ -1,8 +1,10 @@ package shared import ( - "config-lsp/handlers/fstab/parser" + "config-lsp/handlers/fstab/ast" + protocol "github.com/tliron/glsp/protocol_3_16" ) -var DocumentParserMap = map[protocol.DocumentUri]*parser.FstabParser{} +var DocumentParserMap = map[protocol.DocumentUri]*ast.FstabConfig{} + From 0257a246688ca6a6498532add862109c66123ff9 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:20:33 +0200 Subject: [PATCH 03/12] feat(ssh_config): Check doc values in options.go --- server/handlers/ssh_config/analyzer/options.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/server/handlers/ssh_config/analyzer/options.go b/server/handlers/ssh_config/analyzer/options.go index 34dc670..275b444 100644 --- a/server/handlers/ssh_config/analyzer/options.go +++ b/server/handlers/ssh_config/analyzer/options.go @@ -2,6 +2,7 @@ package analyzer import ( "config-lsp/common" + docvalues "config-lsp/doc-values" "config-lsp/handlers/ssh_config/ast" "config-lsp/handlers/ssh_config/fields" "config-lsp/utils" @@ -40,7 +41,7 @@ func checkOption( checkIsUsingDoubleQuotes(ctx, option.Key.Value, option.Key.LocationRange) checkQuotesAreClosed(ctx, option.Key.Value, option.Key.LocationRange) - _, found := fields.Options[option.Key.Key] + docOption, found := fields.Options[option.Key.Key] if !found { // Diagnostics will be handled by `values.go` @@ -61,6 +62,19 @@ func checkOption( if option.OptionValue != nil { checkIsUsingDoubleQuotes(ctx, option.OptionValue.Value, option.OptionValue.LocationRange) checkQuotesAreClosed(ctx, option.OptionValue.Value, option.OptionValue.LocationRange) + + invalidValues := docOption.DeprecatedCheckIsValid(option.OptionValue.Value.Value) + + for _, invalidValue := range invalidValues { + err := docvalues.LSPErrorFromInvalidValue(option.Start.Line, *invalidValue) + err.ShiftCharacter(option.OptionValue.Start.Character) + + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: err.Range.ToLSPRange(), + Message: err.Err.Error(), + Severity: &common.SeverityError, + }) + } } if option.Separator == nil || option.Separator.Value.Value == "" { From 716440cf4c729af824169a22c7c3cf313e9daa20 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:10:53 +0200 Subject: [PATCH 04/12] test(ssh_config): Fix tests --- server/handlers/ssh_config/analyzer/options_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/handlers/ssh_config/analyzer/options_test.go b/server/handlers/ssh_config/analyzer/options_test.go index e7a1301..f4ba881 100644 --- a/server/handlers/ssh_config/analyzer/options_test.go +++ b/server/handlers/ssh_config/analyzer/options_test.go @@ -42,7 +42,7 @@ User root analyzeStructureIsValid(ctx) - if len(ctx.diagnostics) != 0 { + if len(ctx.diagnostics) != 1 { t.Fatalf("Expected no errors, got %v", ctx.diagnostics) } } @@ -85,8 +85,8 @@ Match analyzeStructureIsValid(ctx) - if len(ctx.diagnostics) != 1 { - t.Fatalf("Expected 1 error, got %v", ctx.diagnostics) + if len(ctx.diagnostics) != 2 { + t.Fatalf("Expected 2 errors (separator error and value error), got %v", ctx.diagnostics) } } @@ -108,7 +108,7 @@ Match analyzeStructureIsValid(ctx) - if len(ctx.diagnostics) != 0 { - t.Fatalf("Expected no errors, got %v", ctx.diagnostics) + if len(ctx.diagnostics) != 1 { + t.Fatalf("Expected 1 error, got %v", ctx.diagnostics) } } 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 05/12] 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, + } +} From b7120f3a58b362aba2212695a269c0753de03bb6 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:13:26 +0200 Subject: [PATCH 06/12] feat(common): Improve string parser --- server/common/parser/strings.go | 21 +++++++++++++++++++++ server/common/parser/strings_test.go | 22 ++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/server/common/parser/strings.go b/server/common/parser/strings.go index 931983f..17aaee0 100644 --- a/server/common/parser/strings.go +++ b/server/common/parser/strings.go @@ -1,13 +1,17 @@ package parser +import "strings" + type ParseFeatures struct { ParseDoubleQuotes bool ParseEscapedCharacters bool + Replacements *map[string]string } var FullFeatures = ParseFeatures{ ParseDoubleQuotes: true, ParseEscapedCharacters: true, + Replacements: &map[string]string{}, } type ParsedString struct { @@ -21,6 +25,10 @@ func ParseRawString( ) ParsedString { value := raw + if len(*features.Replacements) > 0 { + value = ParseReplacements(value, *features.Replacements) + } + // Parse double quotes if features.ParseDoubleQuotes { value = ParseDoubleQuotes(value) @@ -85,6 +93,19 @@ func ParseEscapedCharacters( return value } +func ParseReplacements( + raw string, + replacements map[string]string, +) string { + value := raw + + for key, replacement := range replacements { + value = strings.ReplaceAll(value, key, replacement) + } + + return value +} + func modifyString( input string, start int, diff --git a/server/common/parser/strings_test.go b/server/common/parser/strings_test.go index e82352f..0a05916 100644 --- a/server/common/parser/strings_test.go +++ b/server/common/parser/strings_test.go @@ -165,3 +165,25 @@ func TestStringsIncompleteQuotes3FullFeatures( t.Errorf("Expected %v, got %v", expected, actual) } } + +func TestStringsReplacements( + t *testing.T, +) { + input := `Hello\\040World` + expected := ParsedString{ + Raw: input, + Value: `Hello World`, + } + + actual := ParseRawString(input, ParseFeatures{ + ParseDoubleQuotes: true, + ParseEscapedCharacters: true, + Replacements: &map[string]string{ + `\\040`: " ", + }, + }) + + if !(cmp.Equal(expected, actual)) { + t.Errorf("Expected %v, got %v", expected, actual) + } +} From 2b04182a1498e5ea7ac69293b83e294a06f29a3e Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:13:50 +0200 Subject: [PATCH 07/12] fix(common-documentation): Fix formatting --- server/common-documentation/mnt-btrfs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/common-documentation/mnt-btrfs.go b/server/common-documentation/mnt-btrfs.go index 2b8efc0..64b6276 100644 --- a/server/common-documentation/mnt-btrfs.go +++ b/server/common-documentation/mnt-btrfs.go @@ -20,7 +20,7 @@ var BtrfsDocumentationAssignable = map[docvalues.EnumString]docvalues.Deprecated Values: []docvalues.DeprecatedValue{ docvalues.EnumValue{ Values: []docvalues.EnumString{ - docvalues.CreateEnumStringWithDoc( + docvalues.CreateEnumStringWithDoc( "no", "No compression, used for remounting.", ), @@ -38,7 +38,7 @@ var BtrfsDocumentationAssignable = map[docvalues.EnumString]docvalues.Deprecated 15, ), }, - Separator: ":", + Separator: ":", ValueIsOptional: true, }, }, From 30c68e0d99cab61445003500821b487e438130a9 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Wed, 9 Oct 2024 11:05:18 +0200 Subject: [PATCH 08/12] fix(server): Overall improvements --- server/doc-values/value-array.go | 1 - .../doc-values/value-key-enum-assignment.go | 3 +- .../doc-values/value-key-value-assignment.go | 3 +- server/handlers/fstab/ast/parser.go | 50 ------------------- server/handlers/fstab/fields/mountoptions.go | 32 +++++------- server/handlers/fstab/handlers/completions.go | 45 +++++++++++++---- 6 files changed, 50 insertions(+), 84 deletions(-) diff --git a/server/doc-values/value-array.go b/server/doc-values/value-array.go index 0bb9f48..3d330f7 100644 --- a/server/doc-values/value-array.go +++ b/server/doc-values/value-array.go @@ -132,7 +132,6 @@ func (v ArrayValue) getCurrentValue(line string, cursor uint32) (string, uint32) // hello,worl[d] // hello,world[,] // hello[,]world,how,are,you - relativePosition, found := utils.FindPreviousCharacter( line, v.Separator, diff --git a/server/doc-values/value-key-enum-assignment.go b/server/doc-values/value-key-enum-assignment.go index b39127a..9e12c03 100644 --- a/server/doc-values/value-key-enum-assignment.go +++ b/server/doc-values/value-key-enum-assignment.go @@ -170,13 +170,14 @@ func (v KeyEnumAssignmentValue) DeprecatedFetchCompletions(line string, cursor u return v.FetchEnumCompletions() } - relativePosition, found := utils.FindPreviousCharacter( + foundPosition, found := utils.FindPreviousCharacter( line, v.Separator, int(cursor), ) if found { + relativePosition := max(1, foundPosition) - 1 selectedKey := line[:uint32(relativePosition)] line = line[uint32(relativePosition+len(v.Separator)):] cursor -= uint32(relativePosition) diff --git a/server/doc-values/value-key-value-assignment.go b/server/doc-values/value-key-value-assignment.go index 5a2efe7..ccefdaf 100644 --- a/server/doc-values/value-key-value-assignment.go +++ b/server/doc-values/value-key-value-assignment.go @@ -108,13 +108,14 @@ func (v KeyValueAssignmentValue) DeprecatedFetchCompletions(line string, cursor return v.Key.DeprecatedFetchCompletions(line, cursor) } - relativePosition, found := utils.FindPreviousCharacter( + foundPosition, found := utils.FindPreviousCharacter( line, v.Separator, int(cursor), ) if found { + relativePosition := max(1, foundPosition) - 1 selectedKey := line[:uint32(relativePosition)] line = line[uint32(relativePosition+len(v.Separator)):] cursor -= uint32(relativePosition) diff --git a/server/handlers/fstab/ast/parser.go b/server/handlers/fstab/ast/parser.go index e99c8f3..03dd0bf 100644 --- a/server/handlers/fstab/ast/parser.go +++ b/server/handlers/fstab/ast/parser.go @@ -88,42 +88,18 @@ 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{ @@ -181,29 +157,3 @@ 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/fields/mountoptions.go b/server/handlers/fstab/fields/mountoptions.go index f6227ef..ed2d4ed 100644 --- a/server/handlers/fstab/fields/mountoptions.go +++ b/server/handlers/fstab/fields/mountoptions.go @@ -39,22 +39,6 @@ var defaultOptions = []docvalues.EnumString{ "noauto", "Can only be mounted explicitly (i.e., the -a option will not cause the filesystem to be mounted).", ), - docvalues.CreateEnumStringWithDoc( - "context", - "The context= option is useful when mounting filesystems that do not support extended attributes, such as a floppy or hard disk formatted with VFAT, or systems that are not normally running under SELinux, such as an ext3 or ext4 formatted disk from a non-SELinux workstation. You can also use context= on filesystems you do not trust, such as a floppy. It also helps in compatibility with xattr-supporting filesystems on earlier 2.4. kernel versions. Even where xattrs are supported, you can save time not having to label every file by assigning the entire disk one security context.", - ), - docvalues.CreateEnumStringWithDoc( - "fscontext", - "The fscontext= option works for all filesystems, regardless of their xattr support. The fscontext option sets the overarching filesystem label to a specific security context. This filesystem label is separate from the individual labels on the files. It represents the entire filesystem for certain kinds of permission checks, such as during mount or file creation. Individual file labels are still obtained from the xattrs on the files themselves. The context option actually sets the aggregate context that fscontext provides, in addition to supplying the same label for individual files.", - ), - docvalues.CreateEnumStringWithDoc( - "defcontext", - "You can set the default security context for unlabeled files using defcontext= option. This overrides the value set for unlabeled files in the policy and requires a filesystem that supports xattr labeling.", - ), - docvalues.CreateEnumStringWithDoc( - "rootcontext", - "The rootcontext= option allows you to explicitly label the root inode of a FS being mounted before that FS or inode becomes visible to userspace. This was found to be useful for things like stateless Linux. The special value @target can be used to assign the current context of the target mountpoint location.", - ), docvalues.CreateEnumStringWithDoc( "defaults", "Use the default options: rw, suid, dev, exec, auto, nouser, and async. Note that the real set of all default mount options depends on the kernel and filesystem type. See the beginning of this section for more details.", @@ -345,13 +329,21 @@ See TimeoutSec= below for details. Added in version 233.`, ): docvalues.StringValue{}, + docvalues.CreateEnumStringWithDoc( + "fscontext", + "The fscontext= option works for all filesystems, regardless of their xattr support. The fscontext option sets the overarching filesystem label to a specific security context. This filesystem label is separate from the individual labels on the files. It represents the entire filesystem for certain kinds of permission checks, such as during mount or file creation. Individual file labels are still obtained from the xattrs on the files themselves. The context option actually sets the aggregate context that fscontext provides, in addition to supplying the same label for individual files.", + ): docvalues.StringValue{}, + docvalues.CreateEnumStringWithDoc( + "defcontext", + "You can set the default security context for unlabeled files using defcontext= option. This overrides the value set for unlabeled files in the policy and requires a filesystem that supports xattr labeling.", + ): docvalues.StringValue{}, } func createMountOptionField( options []docvalues.EnumString, assignOption map[docvalues.EnumString]docvalues.DeprecatedValue, ) docvalues.DeprecatedValue { - dynamicOptions := docvalues.MergeKeyEnumAssignmentMaps(defaultAssignOptions, assignOption) + // dynamicOptions := docvalues.MergeKeyEnumAssignmentMaps(defaultAssignOptions, assignOption) return docvalues.ArrayValue{ Separator: ",", @@ -359,20 +351,20 @@ func createMountOptionField( SubValue: docvalues.OrValue{ Values: []docvalues.DeprecatedValue{ docvalues.KeyEnumAssignmentValue{ - Values: dynamicOptions, + Values: assignOption, ValueIsOptional: false, Separator: "=", }, docvalues.EnumValue{ EnforceValues: true, - Values: append(defaultOptions, options...), + Values: options, }, }, }, } } -var DefaultMountOptionsField = createMountOptionField([]docvalues.EnumString{}, map[docvalues.EnumString]docvalues.DeprecatedValue{}) +var DefaultMountOptionsField = createMountOptionField(defaultOptions, defaultAssignOptions) var MountOptionsMapField = map[string]docvalues.DeprecatedValue{ "adfs": createMountOptionField( diff --git a/server/handlers/fstab/handlers/completions.go b/server/handlers/fstab/handlers/completions.go index e965789..8b4598c 100644 --- a/server/handlers/fstab/handlers/completions.go +++ b/server/handlers/fstab/handlers/completions.go @@ -2,9 +2,9 @@ package handlers import ( "config-lsp/common" - "config-lsp/doc-values" "config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/fields" + "fmt" "github.com/tliron/glsp/protocol_3_16" ) @@ -38,22 +38,45 @@ func GetCompletion( cursor, ), nil case ast.FstabFieldOptions: + line, cursor := getFieldSafely(entry.Fields.Options, cursor) fileSystemType := entry.Fields.FilesystemType.Value.Value + completions := make([]protocol.CompletionItem, 0, 50) - var optionsField docvalues.DeprecatedValue + for _, completion := range fields.DefaultMountOptionsField.DeprecatedFetchCompletions(line, cursor) { + var documentation string - if foundField, found := fields.MountOptionsMapField[fileSystemType]; found { - optionsField = foundField - } else { - optionsField = fields.DefaultMountOptionsField + switch completion.Documentation.(type) { + case string: + documentation = completion.Documentation.(string) + case *string: + documentation = *completion.Documentation.(*string) + } + + completion.Documentation = protocol.MarkupContent{ + Kind: protocol.MarkupKindMarkdown, + Value: documentation + "\n\n" + "From: _Default Mount Options_", + } + completions = append(completions, completion) } - value, cursor := getFieldSafely(entry.Fields.Options, cursor) + if optionsField, found := fields.MountOptionsMapField[fileSystemType]; found { + for _, completion := range optionsField.DeprecatedFetchCompletions(line, cursor) { + var documentation string - completions := optionsField.DeprecatedFetchCompletions( - value, - cursor, - ) + switch completion.Documentation.(type) { + case string: + documentation = completion.Documentation.(string) + case *string: + documentation = *completion.Documentation.(*string) + } + + completion.Documentation = protocol.MarkupContent{ + Kind: protocol.MarkupKindMarkdown, + Value: documentation + "\n\n" + fmt.Sprintf("From: _%s_", fileSystemType), + } + completions = append(completions, completion) + } + } return completions, nil case ast.FstabFieldFreq: From 5a13487db893e2123294297b40ea86b0f2db71e1 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:41:40 +0200 Subject: [PATCH 09/12] fix(fstab): Improvements; Bugfixes --- server/handlers/fstab/analyzer/values.go | 10 +- server/handlers/fstab/ast/fstab_fields.go | 49 +++- server/handlers/fstab/fields/mountoptions.go | 217 +++++++++--------- server/handlers/fstab/handlers/completions.go | 31 ++- server/handlers/fstab/handlers/hover.go | 11 +- 5 files changed, 178 insertions(+), 140 deletions(-) diff --git a/server/handlers/fstab/analyzer/values.go b/server/handlers/fstab/analyzer/values.go index 8ef69b9..fd6d731 100644 --- a/server/handlers/fstab/analyzer/values.go +++ b/server/handlers/fstab/analyzer/values.go @@ -16,11 +16,16 @@ func analyzeValuesAreValid( for it.Next() { entry := it.Value().(*ast.FstabEntry) + mountOptions := entry.FetchMountOptionsField(true) 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()) + + if mountOptions != nil { + checkField(ctx, entry.Fields.Options, mountOptions) + } + checkField(ctx, entry.Fields.Freq, fields.FreqField) checkField(ctx, entry.Fields.Pass, fields.PassField) } @@ -34,8 +39,7 @@ func checkField( invalidValues := docOption.DeprecatedCheckIsValid(field.Value.Value) for _, invalidValue := range invalidValues { - err := docvalues.LSPErrorFromInvalidValue(field.Start.Line, *invalidValue) - err.ShiftCharacter(field.Start.Character) + err := docvalues.LSPErrorFromInvalidValue(field.Start.Line, *invalidValue).ShiftCharacter(field.Start.Character) ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ Range: err.Range.ToLSPRange(), diff --git a/server/handlers/fstab/ast/fstab_fields.go b/server/handlers/fstab/ast/fstab_fields.go index b72877e..468f625 100644 --- a/server/handlers/fstab/ast/fstab_fields.go +++ b/server/handlers/fstab/ast/fstab_fields.go @@ -4,6 +4,7 @@ import ( "config-lsp/common" docvalues "config-lsp/doc-values" "config-lsp/handlers/fstab/fields" + "config-lsp/utils" ) // func (c FstabConfig) GetEntry(line uint32) *FstabEntry { @@ -40,16 +41,44 @@ func (e FstabEntry) GetFieldAtPosition(position common.Position) FstabFieldName 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 +// Create a mount options field for the entry +func (e FstabEntry) FetchMountOptionsField(includeDefaults bool) docvalues.DeprecatedValue { + if e.Fields.FilesystemType == nil { + return nil } - return optionsField + option, found := fields.MountOptionsMapField[e.Fields.FilesystemType.Value.Value] + + if !found { + return nil + } + + var enums []docvalues.EnumString + var assignable map[docvalues.EnumString]docvalues.DeprecatedValue + + if includeDefaults { + enums = append(option.Enums, fields.DefaultOptions...) + assignable = utils.MergeMaps(option.Assignable, fields.DefaultAssignOptions) + } else { + enums = option.Enums + assignable = option.Assignable + } + + return &docvalues.ArrayValue{ + Separator: ",", + DuplicatesExtractor: &fields.MountOptionsExtractor, + SubValue: docvalues.OrValue{ + Values: []docvalues.DeprecatedValue{ + docvalues.KeyEnumAssignmentValue{ + Values: assignable, + ValueIsOptional: false, + Separator: "=", + }, + docvalues.EnumValue{ + EnforceValues: true, + Values: enums, + }, + }, + }, + } } diff --git a/server/handlers/fstab/fields/mountoptions.go b/server/handlers/fstab/fields/mountoptions.go index ed2d4ed..2ebf666 100644 --- a/server/handlers/fstab/fields/mountoptions.go +++ b/server/handlers/fstab/fields/mountoptions.go @@ -6,7 +6,7 @@ import ( "strings" ) -var mountOptionsExtractor = func(value string) string { +var MountOptionsExtractor = func(value string) string { separatorIndex := strings.Index(value, "=") if separatorIndex == -1 { @@ -17,7 +17,7 @@ var mountOptionsExtractor = func(value string) string { } // From https://www.man7.org/linux/man-pages/man8/mount.8.html -var defaultOptions = []docvalues.EnumString{ +var DefaultOptions = []docvalues.EnumString{ // Default options docvalues.CreateEnumStringWithDoc( "async", @@ -228,7 +228,7 @@ type assignOption struct { Handler func(context docvalues.KeyValueAssignmentContext) docvalues.DeprecatedValue } -var defaultAssignOptions = map[docvalues.EnumString]docvalues.DeprecatedValue{ +var DefaultAssignOptions = map[docvalues.EnumString]docvalues.DeprecatedValue{ docvalues.CreateEnumStringWithDoc( "context", "The context= option is useful when mounting filesystems that do not support extended attributes, such as a floppy or hard disk formatted with VFAT, or systems that are not normally running under SELinux, such as an ext3 or ext4 formatted disk from a non-SELinux workstation. You can also use context= on filesystems you do not trust, such as a floppy. It also helps in compatibility with xattr-supporting filesystems on earlier 2.4. kernel versions. Even where xattrs are supported, you can save time not having to label every file by assigning the entire disk one security context. A commonly used option for removable media is context=\"system_u:object_r:removable_t\".", @@ -347,7 +347,7 @@ func createMountOptionField( return docvalues.ArrayValue{ Separator: ",", - DuplicatesExtractor: &mountOptionsExtractor, + DuplicatesExtractor: &MountOptionsExtractor, SubValue: docvalues.OrValue{ Values: []docvalues.DeprecatedValue{ docvalues.KeyEnumAssignmentValue{ @@ -364,103 +364,114 @@ func createMountOptionField( } } -var DefaultMountOptionsField = createMountOptionField(defaultOptions, defaultAssignOptions) - -var MountOptionsMapField = map[string]docvalues.DeprecatedValue{ - "adfs": createMountOptionField( - commondocumentation.AdfsDocumentationEnums, - commondocumentation.AdfsDocumentationAssignable, - ), - "affs": createMountOptionField( - commondocumentation.AffsDocumentationEnums, - commondocumentation.AffsDocumentationAssignable, - ), - "btrfs": createMountOptionField( - commondocumentation.BtrfsDocumentationEnums, - commondocumentation.BtrfsDocumentationAssignable, - ), - "debugfs": createMountOptionField( - commondocumentation.DebugfsDocumentationEnums, - commondocumentation.DebugfsDocumentationAssignable, - ), - "ext2": createMountOptionField( - commondocumentation.Ext2DocumentationEnums, - commondocumentation.Ext2DocumentationAssignable, - ), - "ext3": createMountOptionField( - append(commondocumentation.Ext2DocumentationEnums, commondocumentation.Ext3DocumentationEnums...), - docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext2DocumentationAssignable, commondocumentation.Ext3DocumentationAssignable), - ), - "ext4": createMountOptionField( - append(append(commondocumentation.Ext2DocumentationEnums, commondocumentation.Ext3DocumentationEnums...), commondocumentation.Ext4DocumentationEnums...), - docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext2DocumentationAssignable, docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext3DocumentationAssignable, commondocumentation.Ext4DocumentationAssignable)), - ), - "devpts": createMountOptionField( - commondocumentation.DevptsDocumentationEnums, - commondocumentation.DevptsDocumentationAssignable, - ), - "fat": createMountOptionField( - commondocumentation.FatDocumentationEnums, - commondocumentation.FatDocumentationAssignable, - ), - "hfs": createMountOptionField( - commondocumentation.HfsDocumentationEnums, - commondocumentation.HfsDocumentationAssignable, - ), - "hpfs": createMountOptionField( - commondocumentation.HpfsDocumentationEnums, - commondocumentation.HpfsDocumentationAssignable, - ), - "iso9660": createMountOptionField( - commondocumentation.Iso9660DocumentationEnums, - commondocumentation.Iso9660DocumentationAssignable, - ), - "jfs": createMountOptionField( - commondocumentation.JfsDocumentationEnums, - commondocumentation.JfsDocumentationAssignable, - ), - "msdos": createMountOptionField( - commondocumentation.MsdosDocumentationEnums, - commondocumentation.MsdosDocumentationAssignable, - ), - "ncpfs": createMountOptionField( - commondocumentation.NcpfsDocumentationEnums, - commondocumentation.NcpfsDocumentationAssignable, - ), - "ntfs": createMountOptionField( - commondocumentation.NtfsDocumentationEnums, - commondocumentation.NtfsDocumentationAssignable, - ), - "overlay": createMountOptionField( - commondocumentation.OverlayDocumentationEnums, - commondocumentation.OverlayDocumentationAssignable, - ), - "reiserfs": createMountOptionField( - commondocumentation.ReiserfsDocumentationEnums, - commondocumentation.ReiserfsDocumentationAssignable, - ), - "usbfs": createMountOptionField( - commondocumentation.UsbfsDocumentationEnums, - commondocumentation.UsbfsDocumentationAssignable, - ), - "ubifs": createMountOptionField( - commondocumentation.UbifsDocumentationEnums, - commondocumentation.UbifsDocumentationAssignable, - ), - "udf": createMountOptionField( - commondocumentation.UdfDocumentationEnums, - commondocumentation.UdfDocumentationAssignable, - ), - "ufs": createMountOptionField( - commondocumentation.UfsDocumentationEnums, - commondocumentation.UfsDocumentationAssignable, - ), - "umsdos": createMountOptionField( - commondocumentation.UmsdosDocumentationEnums, - commondocumentation.UmsdosDocumentationAssignable, - ), - "vfat": createMountOptionField( - commondocumentation.VfatDocumentationEnums, - commondocumentation.VfatDocumentationAssignable, - ), +type optionField struct { + Assignable map[docvalues.EnumString]docvalues.DeprecatedValue + Enums []docvalues.EnumString +} + +var DefaultMountOptionsField = createMountOptionField(DefaultOptions, DefaultAssignOptions) + +var MountOptionsMapField = map[string]optionField{ + "adfs": { + Enums: commondocumentation.AdfsDocumentationEnums, + Assignable: commondocumentation.AdfsDocumentationAssignable, + }, + "affs": { + Enums: commondocumentation.AffsDocumentationEnums, + Assignable: commondocumentation.AffsDocumentationAssignable, + }, + "btrfs": { + Enums: commondocumentation.BtrfsDocumentationEnums, + Assignable: commondocumentation.BtrfsDocumentationAssignable, + }, + "debugfs": { + Enums: commondocumentation.DebugfsDocumentationEnums, + Assignable: commondocumentation.DebugfsDocumentationAssignable, + }, + "ext2": { + Enums: commondocumentation.Ext2DocumentationEnums, + Assignable: commondocumentation.Ext2DocumentationAssignable, + }, + "ext3": { + Enums: append(commondocumentation.Ext2DocumentationEnums, commondocumentation.Ext3DocumentationEnums...), + Assignable: docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext2DocumentationAssignable, commondocumentation.Ext3DocumentationAssignable), + }, + "ext4": { + Enums: append( + append( + commondocumentation.Ext2DocumentationEnums, + commondocumentation.Ext3DocumentationEnums..., + ), + commondocumentation.Ext4DocumentationEnums..., + ), + Assignable: docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext2DocumentationAssignable, docvalues.MergeKeyEnumAssignmentMaps(commondocumentation.Ext3DocumentationAssignable, commondocumentation.Ext4DocumentationAssignable)), + }, + "devpts": { + Enums: commondocumentation.DevptsDocumentationEnums, + Assignable: commondocumentation.DevptsDocumentationAssignable, + }, + "fat": { + Enums: commondocumentation.FatDocumentationEnums, + Assignable: commondocumentation.FatDocumentationAssignable, + }, + "hfs": { + Enums: commondocumentation.HfsDocumentationEnums, + Assignable: commondocumentation.HfsDocumentationAssignable, + }, + "hpfs": { + Enums: commondocumentation.HpfsDocumentationEnums, + Assignable: commondocumentation.HpfsDocumentationAssignable, + }, + "iso9660": { + Enums: commondocumentation.Iso9660DocumentationEnums, + Assignable: commondocumentation.Iso9660DocumentationAssignable, + }, + "jfs": { + Enums: commondocumentation.JfsDocumentationEnums, + Assignable: commondocumentation.JfsDocumentationAssignable, + }, + "msdos": { + Enums: commondocumentation.MsdosDocumentationEnums, + Assignable: commondocumentation.MsdosDocumentationAssignable, + }, + "ncpfs": { + Enums: commondocumentation.NcpfsDocumentationEnums, + Assignable: commondocumentation.NcpfsDocumentationAssignable, + }, + "ntfs": { + Enums: commondocumentation.NtfsDocumentationEnums, + Assignable: commondocumentation.NtfsDocumentationAssignable, + }, + "overlay": { + Enums: commondocumentation.OverlayDocumentationEnums, + Assignable: commondocumentation.OverlayDocumentationAssignable, + }, + "reiserfs": { + Enums: commondocumentation.ReiserfsDocumentationEnums, + Assignable: commondocumentation.ReiserfsDocumentationAssignable, + }, + "usbfs": { + Enums: commondocumentation.UsbfsDocumentationEnums, + Assignable: commondocumentation.UsbfsDocumentationAssignable, + }, + "ubifs": { + Enums: commondocumentation.UbifsDocumentationEnums, + Assignable: commondocumentation.UbifsDocumentationAssignable, + }, + "udf": { + Enums: commondocumentation.UdfDocumentationEnums, + Assignable: commondocumentation.UdfDocumentationAssignable, + }, + "ufs": { + Enums: commondocumentation.UfsDocumentationEnums, + Assignable: commondocumentation.UfsDocumentationAssignable, + }, + "umsdos": { + Enums: commondocumentation.UmsdosDocumentationEnums, + Assignable: commondocumentation.UmsdosDocumentationAssignable, + }, + "vfat": { + Enums: commondocumentation.VfatDocumentationEnums, + Assignable: commondocumentation.VfatDocumentationAssignable, + }, } diff --git a/server/handlers/fstab/handlers/completions.go b/server/handlers/fstab/handlers/completions.go index 8b4598c..9d10d90 100644 --- a/server/handlers/fstab/handlers/completions.go +++ b/server/handlers/fstab/handlers/completions.go @@ -42,6 +42,7 @@ func GetCompletion( fileSystemType := entry.Fields.FilesystemType.Value.Value completions := make([]protocol.CompletionItem, 0, 50) + println("fetching field options now", line, cursor) for _, completion := range fields.DefaultMountOptionsField.DeprecatedFetchCompletions(line, cursor) { var documentation string @@ -59,23 +60,21 @@ func GetCompletion( completions = append(completions, completion) } - if optionsField, found := fields.MountOptionsMapField[fileSystemType]; found { - for _, completion := range optionsField.DeprecatedFetchCompletions(line, cursor) { - var documentation string + for _, completion := range entry.FetchMountOptionsField(false).DeprecatedFetchCompletions(line, cursor) { + var documentation string - switch completion.Documentation.(type) { - case string: - documentation = completion.Documentation.(string) - case *string: - documentation = *completion.Documentation.(*string) - } - - completion.Documentation = protocol.MarkupContent{ - Kind: protocol.MarkupKindMarkdown, - Value: documentation + "\n\n" + fmt.Sprintf("From: _%s_", fileSystemType), - } - completions = append(completions, completion) + switch completion.Documentation.(type) { + case string: + documentation = completion.Documentation.(string) + case *string: + documentation = *completion.Documentation.(*string) } + + completion.Documentation = protocol.MarkupContent{ + Kind: protocol.MarkupKindMarkdown, + Value: documentation + "\n\n" + fmt.Sprintf("From: _%s_", fileSystemType), + } + completions = append(completions, completion) } return completions, nil @@ -109,5 +108,5 @@ func getFieldSafely(field *ast.FstabField, cursor common.CursorPosition) (string return "", 0 } - return field.Value.Raw, common.CursorToCharacterIndex(uint32(cursor)) - 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 1c4997d..0216a62 100644 --- a/server/handlers/fstab/handlers/hover.go +++ b/server/handlers/fstab/handlers/hover.go @@ -2,9 +2,7 @@ package handlers import ( "config-lsp/common" - "config-lsp/doc-values" "config-lsp/handlers/fstab/ast" - "config-lsp/handlers/fstab/fields" "strings" "github.com/tliron/glsp/protocol_3_16" @@ -25,13 +23,10 @@ func GetHoverInfo( case ast.FstabFieldFileSystemType: return &FileSystemTypeField, nil case ast.FstabFieldOptions: - fileSystemType := entry.Fields.FilesystemType.Value.Value - var optionsField docvalues.DeprecatedValue + optionsField := entry.FetchMountOptionsField(true) - if foundField, found := fields.MountOptionsMapField[fileSystemType]; found { - optionsField = foundField - } else { - optionsField = fields.DefaultMountOptionsField + if optionsField == nil { + return nil, nil } relativeCursor := uint32(index) - entry.Fields.Options.Start.Character From 3ca0ea8c352e2169fe08292421557721d770ed3d Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Wed, 9 Oct 2024 21:44:32 +0200 Subject: [PATCH 10/12] fix(fstab): Improvements; Use antlr parser now --- server/handlers/fstab/Fstab.g4 | 76 ++ server/handlers/fstab/ast/error-listener.go | 45 + server/handlers/fstab/ast/fstab.go | 2 +- server/handlers/fstab/ast/fstab_fields.go | 98 +- server/handlers/fstab/ast/listener.go | 135 ++ server/handlers/fstab/ast/parser.go | 169 ++- server/handlers/fstab/ast/parser/Fstab.interp | 36 + server/handlers/fstab/ast/parser/Fstab.tokens | 10 + .../fstab/ast/parser/FstabLexer.interp | 44 + .../fstab/ast/parser/FstabLexer.tokens | 10 + .../fstab/ast/parser/fstab_base_listener.go | 64 + .../handlers/fstab/ast/parser/fstab_lexer.go | 143 ++ .../fstab/ast/parser/fstab_listener.go | 52 + .../handlers/fstab/ast/parser/fstab_parser.go | 1171 +++++++++++++++++ server/handlers/fstab/ast/parser_test.go | 11 +- server/handlers/fstab/fields/spec.go | 2 +- server/handlers/fstab/fstab_test.go | 30 + server/handlers/fstab/handlers/completions.go | 5 +- server/handlers/ssh_config/ast/listener.go | 14 +- server/update_antlr_parser.sh | 3 + 20 files changed, 2036 insertions(+), 84 deletions(-) create mode 100644 server/handlers/fstab/Fstab.g4 create mode 100644 server/handlers/fstab/ast/error-listener.go create mode 100644 server/handlers/fstab/ast/listener.go create mode 100644 server/handlers/fstab/ast/parser/Fstab.interp create mode 100644 server/handlers/fstab/ast/parser/Fstab.tokens create mode 100644 server/handlers/fstab/ast/parser/FstabLexer.interp create mode 100644 server/handlers/fstab/ast/parser/FstabLexer.tokens create mode 100644 server/handlers/fstab/ast/parser/fstab_base_listener.go create mode 100644 server/handlers/fstab/ast/parser/fstab_lexer.go create mode 100644 server/handlers/fstab/ast/parser/fstab_listener.go create mode 100644 server/handlers/fstab/ast/parser/fstab_parser.go diff --git a/server/handlers/fstab/Fstab.g4 b/server/handlers/fstab/Fstab.g4 new file mode 100644 index 0000000..e5c6b1e --- /dev/null +++ b/server/handlers/fstab/Fstab.g4 @@ -0,0 +1,76 @@ +grammar Fstab; + +entry + : + WHITESPACE? spec? + WHITESPACE? mountPoint? + WHITESPACE? fileSystem? + WHITESPACE? mountOptions? + WHITESPACE? freq? + WHITESPACE? pass? WHITESPACE? + EOF + ; + +spec + : QUOTED_STRING | STRING + ; + +mountPoint + : QUOTED_STRING | STRING + ; + +fileSystem + : ADFS | AFFS | BTRFS | EXFAT + // Still match unknown file systems + | STRING | QUOTED_STRING + ; + +mountOptions + : QUOTED_STRING | STRING + ; + +freq + : DIGITS + ; + +pass + : DIGITS + ; + +DIGITS + : [0-9]+ + ; + +WHITESPACE + : [ \t]+ + ; + +HASH + : '#' + ; + +STRING + : ~(' ' | '\t' | '#')+ + ; + +QUOTED_STRING + : '"' WHITESPACE? (STRING WHITESPACE)* STRING? ('"')? + ; + +// ///// Supported file systems ///// + +ADFS + : ('A' | 'a') ('D' | 'd') ('F' | 'f') ('S' | 's') + ; + +AFFS + : ('A' | 'a') ('F' | 'f') ('F' | 'f') ('S' | 's') + ; + +BTRFS + : ('B' | 'b') ('T' | 't') ('R' | 'r') ('F' | 'f') ('S' | 's') + ; + +EXFAT + : ('E' | 'e') ('X' | 'x') ('F' | 'f') ('A' | 'a') ('T' | 't') + ; diff --git a/server/handlers/fstab/ast/error-listener.go b/server/handlers/fstab/ast/error-listener.go new file mode 100644 index 0000000..74d4387 --- /dev/null +++ b/server/handlers/fstab/ast/error-listener.go @@ -0,0 +1,45 @@ +package ast + +import ( + "config-lsp/common" + + "github.com/antlr4-go/antlr/v4" +) + +type errorListenerContext struct { + line uint32 +} + +type errorListener struct { + *antlr.DefaultErrorListener + Errors []common.LSPError + context errorListenerContext +} + +func (d *errorListener) SyntaxError( + recognizer antlr.Recognizer, + offendingSymbol interface{}, + _ int, + character int, + message string, + error antlr.RecognitionException, +) { + line := d.context.line + d.Errors = append(d.Errors, common.LSPError{ + Range: common.CreateSingleCharRange(uint32(line), uint32(character)), + Err: common.SyntaxError{ + Message: message, + }, + }) +} + +func createErrorListener( + line uint32, +) errorListener { + return errorListener{ + Errors: make([]common.LSPError, 0), + context: errorListenerContext{ + line: line, + }, + } +} diff --git a/server/handlers/fstab/ast/fstab.go b/server/handlers/fstab/ast/fstab.go index 125e624..8286ba0 100644 --- a/server/handlers/fstab/ast/fstab.go +++ b/server/handlers/fstab/ast/fstab.go @@ -33,7 +33,7 @@ type FstabFields struct { } type FstabEntry struct { - Fields FstabFields + Fields *FstabFields } type FstabConfig struct { diff --git a/server/handlers/fstab/ast/fstab_fields.go b/server/handlers/fstab/ast/fstab_fields.go index 468f625..77679b5 100644 --- a/server/handlers/fstab/ast/fstab_fields.go +++ b/server/handlers/fstab/ast/fstab_fields.go @@ -17,30 +17,118 @@ import ( // return entry.(*FstabEntry) // } +// LABEL=test ext4 defaults 0 0 func (e FstabEntry) GetFieldAtPosition(position common.Position) FstabFieldName { - if e.Fields.Spec == nil || (e.Fields.Spec.ContainsPosition(position)) { + // No fields defined, empty line + if e.Fields.Spec == nil && e.Fields.MountPoint == nil && e.Fields.FilesystemType == nil && e.Fields.Options == nil && e.Fields.Freq == nil && e.Fields.Pass == nil { return FstabFieldSpec } - if e.Fields.MountPoint == nil || (e.Fields.MountPoint.ContainsPosition(position)) { + // First, try if out of the existing fields the user wants to edit one of them + + if e.Fields.Spec != nil && e.Fields.Spec.ContainsPosition(position) { + return FstabFieldSpec + } + if e.Fields.MountPoint != nil && e.Fields.MountPoint.ContainsPosition(position) { + return FstabFieldMountPoint + } + if e.Fields.FilesystemType != nil && e.Fields.FilesystemType.ContainsPosition(position) { + return FstabFieldFileSystemType + } + if e.Fields.Options != nil && e.Fields.Options.ContainsPosition(position) { + return FstabFieldOptions + } + if e.Fields.Freq != nil && e.Fields.Freq.ContainsPosition(position) { + return FstabFieldFreq + } + if e.Fields.Pass != nil && e.Fields.Pass.ContainsPosition(position) { + return FstabFieldPass + } + + // Okay let's try to fetch the field by assuming the user is typing from left to right normally + + if e.Fields.Spec != nil && e.Fields.Spec.IsPositionAfterEnd(position) && (e.Fields.MountPoint == nil || e.Fields.MountPoint.IsPositionBeforeEnd(position)) { return FstabFieldMountPoint } - if e.Fields.FilesystemType == nil || (e.Fields.FilesystemType.ContainsPosition(position)) { + if e.Fields.MountPoint != nil && e.Fields.MountPoint.IsPositionAfterEnd(position) && (e.Fields.FilesystemType == nil || e.Fields.FilesystemType.IsPositionBeforeEnd(position)) { return FstabFieldFileSystemType } - if e.Fields.Options == nil || (e.Fields.Options.ContainsPosition(position)) { + if e.Fields.FilesystemType != nil && e.Fields.FilesystemType.IsPositionAfterEnd(position) && (e.Fields.Options == nil || e.Fields.Options.IsPositionBeforeEnd(position)) { return FstabFieldOptions } - if e.Fields.Freq == nil || (e.Fields.Freq.ContainsPosition(position)) { + if e.Fields.Options != nil && e.Fields.Options.IsPositionAfterEnd(position) && (e.Fields.Freq == nil || e.Fields.Freq.IsPositionBeforeEnd(position)) { + return FstabFieldFreq + } + + if e.Fields.Freq != nil && e.Fields.Freq.IsPositionAfterEnd(position) && (e.Fields.Pass == nil || e.Fields.Pass.IsPositionBeforeEnd(position)) { + return FstabFieldPass + } + + // Okay shit no idea, let's just give whatever is missing + + if e.Fields.Spec == nil { + return FstabFieldSpec + } + + if e.Fields.MountPoint == nil { + return FstabFieldMountPoint + } + + if e.Fields.FilesystemType == nil { + return FstabFieldFileSystemType + } + + if e.Fields.Options == nil { + return FstabFieldOptions + } + + if e.Fields.Freq == nil { return FstabFieldFreq } return FstabFieldPass } +// LABEL=test /mnt/test btrfs subvol=backup,fat=32 [0] [0] +func (e FstabEntry) getCursorIndex() uint8 { + definedAmount := e.getDefinedFieldsAmount() + + switch definedAmount { + case 5: + + } + + return 0 +} + +func (e FstabEntry) getDefinedFieldsAmount() uint8 { + var definedAmount uint8 = 0 + + if e.Fields.Spec != nil { + definedAmount++ + } + if e.Fields.MountPoint != nil { + definedAmount++ + } + if e.Fields.FilesystemType != nil { + definedAmount++ + } + if e.Fields.Options != nil { + definedAmount++ + } + if e.Fields.Freq != nil { + definedAmount++ + } + if e.Fields.Pass != nil { + definedAmount++ + } + + return definedAmount +} + // Create a mount options field for the entry func (e FstabEntry) FetchMountOptionsField(includeDefaults bool) docvalues.DeprecatedValue { if e.Fields.FilesystemType == nil { diff --git a/server/handlers/fstab/ast/listener.go b/server/handlers/fstab/ast/listener.go new file mode 100644 index 0000000..a9f16b4 --- /dev/null +++ b/server/handlers/fstab/ast/listener.go @@ -0,0 +1,135 @@ +package ast + +import ( + "config-lsp/common" + "config-lsp/handlers/fstab/ast/parser" + + commonparser "config-lsp/common/parser" +) + +type fstabListenerContext struct { + line uint32 + currentEntry *FstabEntry +} + +func createListenerContext() *fstabListenerContext { + context := new(fstabListenerContext) + + return context +} + +type fstabParserListener struct { + *parser.BaseFstabListener + Config *FstabConfig + Errors []common.LSPError + fstabContext *fstabListenerContext +} + +func createListener( + config *FstabConfig, + context *fstabListenerContext, +) fstabParserListener { + return fstabParserListener{ + Config: config, + Errors: make([]common.LSPError, 0), + fstabContext: context, + } +} + +func (s *fstabParserListener) EnterEntry(ctx *parser.EntryContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.fstabContext.line) + + s.fstabContext.currentEntry = &FstabEntry{ + Fields: &FstabFields{ + LocationRange: location, + }, + } + + s.Config.Entries.Put( + s.fstabContext.line, + s.fstabContext.currentEntry, + ) +} + +func (s *fstabParserListener) ExitEntry(ctx *parser.EntryContext) { + s.fstabContext.currentEntry = nil +} + +func (s *fstabParserListener) EnterSpec(ctx *parser.SpecContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.fstabContext.line) + + text := ctx.GetText() + value := commonparser.ParseRawString(text, commonparser.FullFeatures) + + s.fstabContext.currentEntry.Fields.Spec = &FstabField{ + Value: value, + LocationRange: location, + } +} + +func (s *fstabParserListener) EnterMountPoint(ctx *parser.MountPointContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.fstabContext.line) + + text := ctx.GetText() + value := commonparser.ParseRawString(text, commonparser.FullFeatures) + + s.fstabContext.currentEntry.Fields.MountPoint = &FstabField{ + LocationRange: location, + Value: value, + } +} + +func (s *fstabParserListener) EnterFileSystem(ctx *parser.FileSystemContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.fstabContext.line) + + text := ctx.GetText() + value := commonparser.ParseRawString(text, commonparser.FullFeatures) + + s.fstabContext.currentEntry.Fields.FilesystemType = &FstabField{ + LocationRange: location, + Value: value, + } +} + +func (s *fstabParserListener) EnterMountOptions(ctx *parser.MountOptionsContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.fstabContext.line) + + text := ctx.GetText() + value := commonparser.ParseRawString(text, commonparser.FullFeatures) + + s.fstabContext.currentEntry.Fields.Options = &FstabField{ + LocationRange: location, + Value: value, + } +} + +func (s *fstabParserListener) EnterFreq(ctx *parser.FreqContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.fstabContext.line) + + text := ctx.GetText() + value := commonparser.ParseRawString(text, commonparser.FullFeatures) + + s.fstabContext.currentEntry.Fields.Freq = &FstabField{ + LocationRange: location, + Value: value, + } +} + +func (s *fstabParserListener) EnterPass(ctx *parser.PassContext) { + location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) + location.ChangeBothLines(s.fstabContext.line) + + text := ctx.GetText() + value := commonparser.ParseRawString(text, commonparser.FullFeatures) + + s.fstabContext.currentEntry.Fields.Pass = &FstabField{ + LocationRange: location, + Value: value, + } +} diff --git a/server/handlers/fstab/ast/parser.go b/server/handlers/fstab/ast/parser.go index 03dd0bf..ac63d6f 100644 --- a/server/handlers/fstab/ast/parser.go +++ b/server/handlers/fstab/ast/parser.go @@ -3,9 +3,11 @@ package ast import ( "config-lsp/common" commonparser "config-lsp/common/parser" + "config-lsp/handlers/fstab/ast/parser" "config-lsp/utils" "regexp" + "github.com/antlr4-go/antlr/v4" "github.com/emirpasic/gods/maps/treemap" gods "github.com/emirpasic/gods/utils" @@ -25,14 +27,15 @@ func (c *FstabConfig) Clear() { var commentPattern = regexp.MustCompile(`^\s*#`) var emptyPattern = regexp.MustCompile(`^\s*$`) -var whitespacePattern = regexp.MustCompile(`\S+`) func (c *FstabConfig) Parse(input string) []common.LSPError { errors := make([]common.LSPError, 0) lines := utils.SplitIntoLines(input) + context := createListenerContext() for rawLineNumber, line := range lines { lineNumber := uint32(rawLineNumber) + context.line = lineNumber if emptyPattern.MatchString(line) { continue @@ -45,89 +48,121 @@ func (c *FstabConfig) Parse(input string) []common.LSPError { errors = append( errors, - c.parseStatement(lineNumber, line)..., + c.parseStatement(context, line)..., ) } return errors } +// TODO: Handle leading comments func (c *FstabConfig) parseStatement( - line uint32, + context *fstabListenerContext, input string, ) []common.LSPError { - fields := whitespacePattern.FindAllStringIndex(input, -1) + stream := antlr.NewInputStream(input) - if len(fields) == 0 { - return []common.LSPError{ - { - Range: common.LocationRange{ - Start: common.Location{ - Line: line, - Character: 0, - }, - End: common.Location{ - Line: line, - Character: 0, - }, - }, - }, - } - } + lexerErrorListener := createErrorListener(context.line) + lexer := parser.NewFstabLexer(stream) + lexer.RemoveErrorListeners() + lexer.AddErrorListener(&lexerErrorListener) - var spec *FstabField - var mountPoint *FstabField - var filesystemType *FstabField - var options *FstabField - var freq *FstabField - var pass *FstabField + tokenStream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel) - switch len(fields) { - case 6: - pass = parseField(line, input, fields[5]) - fallthrough - case 5: - freq = parseField(line, input, fields[4]) - fallthrough - case 4: - options = parseField(line, input, fields[3]) - fallthrough - case 3: - filesystemType = parseField(line, input, fields[2]) - fallthrough - case 2: - mountPoint = parseField(line, input, fields[1]) - fallthrough - case 1: - spec = parseField(line, input, fields[0]) - } + parserErrorListener := createErrorListener(context.line) + antlrParser := parser.NewFstabParser(tokenStream) + antlrParser.RemoveErrorListeners() + antlrParser.AddErrorListener(&parserErrorListener) - fstabLine := &FstabEntry{ - Fields: FstabFields{ - LocationRange: common.LocationRange{ - Start: common.Location{ - Line: line, - Character: 0, - }, - End: common.Location{ - Line: line, - Character: uint32(len(input)), - }, - }, - Spec: spec, - MountPoint: mountPoint, - FilesystemType: filesystemType, - Options: options, - Freq: freq, - Pass: pass, - }, - } + listener := createListener(c, context) + antlr.ParseTreeWalkerDefault.Walk( + &listener, + antlrParser.Entry(), + ) - c.Entries.Put(line, fstabLine) + errors := lexerErrorListener.Errors + errors = append(errors, parserErrorListener.Errors...) + errors = append(errors, listener.Errors...) - return nil + return errors } +// func (c *FstabConfig) parseStatement( +// line uint32, +// input string, +// ) []common.LSPError { +// fields := whitespacePattern.FindAllStringIndex(input, -1) +// +// if len(fields) == 0 { +// return []common.LSPError{ +// { +// Range: common.LocationRange{ +// Start: common.Location{ +// Line: line, +// Character: 0, +// }, +// End: common.Location{ +// Line: line, +// Character: 0, +// }, +// }, +// }, +// } +// } +// +// var spec *FstabField +// var mountPoint *FstabField +// var filesystemType *FstabField +// var options *FstabField +// var freq *FstabField +// var pass *FstabField +// +// switch len(fields) { +// case 6: +// pass = parseField(line, input, fields[5]) +// fallthrough +// case 5: +// freq = parseField(line, input, fields[4]) +// fallthrough +// case 4: +// options = parseField(line, input, fields[3]) +// fallthrough +// case 3: +// filesystemType = parseField(line, input, fields[2]) +// fallthrough +// case 2: +// mountPoint = parseField(line, input, fields[1]) +// fallthrough +// case 1: +// spec = parseField(line, input, fields[0]) +// } +// +// fstabLine := &FstabEntry{ +// Fields: FstabFields{ +// LocationRange: common.LocationRange{ +// Start: common.Location{ +// Line: line, +// Character: 0, +// }, +// End: common.Location{ +// Line: line, +// Character: uint32(len(input)), +// }, +// }, +// Spec: spec, +// MountPoint: mountPoint, +// FilesystemType: filesystemType, +// Options: options, +// Freq: freq, +// Pass: pass, +// }, +// } +// +// c.Entries.Put(line, fstabLine) +// +// return nil +// } + func parseField( line uint32, input string, diff --git a/server/handlers/fstab/ast/parser/Fstab.interp b/server/handlers/fstab/ast/parser/Fstab.interp new file mode 100644 index 0000000..eae0ba8 --- /dev/null +++ b/server/handlers/fstab/ast/parser/Fstab.interp @@ -0,0 +1,36 @@ +token literal names: +null +null +null +'#' +null +null +null +null +null +null + +token symbolic names: +null +DIGITS +WHITESPACE +HASH +STRING +QUOTED_STRING +ADFS +AFFS +BTRFS +EXFAT + +rule names: +entry +spec +mountPoint +fileSystem +mountOptions +freq +pass + + +atn: +[4, 1, 9, 68, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 1, 0, 3, 0, 16, 8, 0, 1, 0, 3, 0, 19, 8, 0, 1, 0, 3, 0, 22, 8, 0, 1, 0, 3, 0, 25, 8, 0, 1, 0, 3, 0, 28, 8, 0, 1, 0, 3, 0, 31, 8, 0, 1, 0, 3, 0, 34, 8, 0, 1, 0, 3, 0, 37, 8, 0, 1, 0, 3, 0, 40, 8, 0, 1, 0, 3, 0, 43, 8, 0, 1, 0, 3, 0, 46, 8, 0, 1, 0, 3, 0, 49, 8, 0, 1, 0, 3, 0, 52, 8, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 0, 0, 7, 0, 2, 4, 6, 8, 10, 12, 0, 2, 1, 0, 4, 5, 1, 0, 4, 9, 73, 0, 15, 1, 0, 0, 0, 2, 55, 1, 0, 0, 0, 4, 57, 1, 0, 0, 0, 6, 59, 1, 0, 0, 0, 8, 61, 1, 0, 0, 0, 10, 63, 1, 0, 0, 0, 12, 65, 1, 0, 0, 0, 14, 16, 5, 2, 0, 0, 15, 14, 1, 0, 0, 0, 15, 16, 1, 0, 0, 0, 16, 18, 1, 0, 0, 0, 17, 19, 3, 2, 1, 0, 18, 17, 1, 0, 0, 0, 18, 19, 1, 0, 0, 0, 19, 21, 1, 0, 0, 0, 20, 22, 5, 2, 0, 0, 21, 20, 1, 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 24, 1, 0, 0, 0, 23, 25, 3, 4, 2, 0, 24, 23, 1, 0, 0, 0, 24, 25, 1, 0, 0, 0, 25, 27, 1, 0, 0, 0, 26, 28, 5, 2, 0, 0, 27, 26, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 30, 1, 0, 0, 0, 29, 31, 3, 6, 3, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 33, 1, 0, 0, 0, 32, 34, 5, 2, 0, 0, 33, 32, 1, 0, 0, 0, 33, 34, 1, 0, 0, 0, 34, 36, 1, 0, 0, 0, 35, 37, 3, 8, 4, 0, 36, 35, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0, 37, 39, 1, 0, 0, 0, 38, 40, 5, 2, 0, 0, 39, 38, 1, 0, 0, 0, 39, 40, 1, 0, 0, 0, 40, 42, 1, 0, 0, 0, 41, 43, 3, 10, 5, 0, 42, 41, 1, 0, 0, 0, 42, 43, 1, 0, 0, 0, 43, 45, 1, 0, 0, 0, 44, 46, 5, 2, 0, 0, 45, 44, 1, 0, 0, 0, 45, 46, 1, 0, 0, 0, 46, 48, 1, 0, 0, 0, 47, 49, 3, 12, 6, 0, 48, 47, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 51, 1, 0, 0, 0, 50, 52, 5, 2, 0, 0, 51, 50, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 53, 1, 0, 0, 0, 53, 54, 5, 0, 0, 1, 54, 1, 1, 0, 0, 0, 55, 56, 7, 0, 0, 0, 56, 3, 1, 0, 0, 0, 57, 58, 7, 0, 0, 0, 58, 5, 1, 0, 0, 0, 59, 60, 7, 1, 0, 0, 60, 7, 1, 0, 0, 0, 61, 62, 7, 0, 0, 0, 62, 9, 1, 0, 0, 0, 63, 64, 5, 1, 0, 0, 64, 11, 1, 0, 0, 0, 65, 66, 5, 1, 0, 0, 66, 13, 1, 0, 0, 0, 13, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51] \ No newline at end of file diff --git a/server/handlers/fstab/ast/parser/Fstab.tokens b/server/handlers/fstab/ast/parser/Fstab.tokens new file mode 100644 index 0000000..9584238 --- /dev/null +++ b/server/handlers/fstab/ast/parser/Fstab.tokens @@ -0,0 +1,10 @@ +DIGITS=1 +WHITESPACE=2 +HASH=3 +STRING=4 +QUOTED_STRING=5 +ADFS=6 +AFFS=7 +BTRFS=8 +EXFAT=9 +'#'=3 diff --git a/server/handlers/fstab/ast/parser/FstabLexer.interp b/server/handlers/fstab/ast/parser/FstabLexer.interp new file mode 100644 index 0000000..51b46ca --- /dev/null +++ b/server/handlers/fstab/ast/parser/FstabLexer.interp @@ -0,0 +1,44 @@ +token literal names: +null +null +null +'#' +null +null +null +null +null +null + +token symbolic names: +null +DIGITS +WHITESPACE +HASH +STRING +QUOTED_STRING +ADFS +AFFS +BTRFS +EXFAT + +rule names: +DIGITS +WHITESPACE +HASH +STRING +QUOTED_STRING +ADFS +AFFS +BTRFS +EXFAT + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[4, 0, 9, 76, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 1, 0, 4, 0, 21, 8, 0, 11, 0, 12, 0, 22, 1, 1, 4, 1, 26, 8, 1, 11, 1, 12, 1, 27, 1, 2, 1, 2, 1, 3, 4, 3, 33, 8, 3, 11, 3, 12, 3, 34, 1, 4, 1, 4, 3, 4, 39, 8, 4, 1, 4, 1, 4, 1, 4, 5, 4, 44, 8, 4, 10, 4, 12, 4, 47, 9, 4, 1, 4, 3, 4, 50, 8, 4, 1, 4, 3, 4, 53, 8, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 0, 0, 9, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 1, 0, 12, 1, 0, 48, 57, 2, 0, 9, 9, 32, 32, 3, 0, 9, 9, 32, 32, 35, 35, 2, 0, 65, 65, 97, 97, 2, 0, 68, 68, 100, 100, 2, 0, 70, 70, 102, 102, 2, 0, 83, 83, 115, 115, 2, 0, 66, 66, 98, 98, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 69, 69, 101, 101, 2, 0, 88, 88, 120, 120, 82, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 1, 20, 1, 0, 0, 0, 3, 25, 1, 0, 0, 0, 5, 29, 1, 0, 0, 0, 7, 32, 1, 0, 0, 0, 9, 36, 1, 0, 0, 0, 11, 54, 1, 0, 0, 0, 13, 59, 1, 0, 0, 0, 15, 64, 1, 0, 0, 0, 17, 70, 1, 0, 0, 0, 19, 21, 7, 0, 0, 0, 20, 19, 1, 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 20, 1, 0, 0, 0, 22, 23, 1, 0, 0, 0, 23, 2, 1, 0, 0, 0, 24, 26, 7, 1, 0, 0, 25, 24, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 25, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 4, 1, 0, 0, 0, 29, 30, 5, 35, 0, 0, 30, 6, 1, 0, 0, 0, 31, 33, 8, 2, 0, 0, 32, 31, 1, 0, 0, 0, 33, 34, 1, 0, 0, 0, 34, 32, 1, 0, 0, 0, 34, 35, 1, 0, 0, 0, 35, 8, 1, 0, 0, 0, 36, 38, 5, 34, 0, 0, 37, 39, 3, 3, 1, 0, 38, 37, 1, 0, 0, 0, 38, 39, 1, 0, 0, 0, 39, 45, 1, 0, 0, 0, 40, 41, 3, 7, 3, 0, 41, 42, 3, 3, 1, 0, 42, 44, 1, 0, 0, 0, 43, 40, 1, 0, 0, 0, 44, 47, 1, 0, 0, 0, 45, 43, 1, 0, 0, 0, 45, 46, 1, 0, 0, 0, 46, 49, 1, 0, 0, 0, 47, 45, 1, 0, 0, 0, 48, 50, 3, 7, 3, 0, 49, 48, 1, 0, 0, 0, 49, 50, 1, 0, 0, 0, 50, 52, 1, 0, 0, 0, 51, 53, 5, 34, 0, 0, 52, 51, 1, 0, 0, 0, 52, 53, 1, 0, 0, 0, 53, 10, 1, 0, 0, 0, 54, 55, 7, 3, 0, 0, 55, 56, 7, 4, 0, 0, 56, 57, 7, 5, 0, 0, 57, 58, 7, 6, 0, 0, 58, 12, 1, 0, 0, 0, 59, 60, 7, 3, 0, 0, 60, 61, 7, 5, 0, 0, 61, 62, 7, 5, 0, 0, 62, 63, 7, 6, 0, 0, 63, 14, 1, 0, 0, 0, 64, 65, 7, 7, 0, 0, 65, 66, 7, 8, 0, 0, 66, 67, 7, 9, 0, 0, 67, 68, 7, 5, 0, 0, 68, 69, 7, 6, 0, 0, 69, 16, 1, 0, 0, 0, 70, 71, 7, 10, 0, 0, 71, 72, 7, 11, 0, 0, 72, 73, 7, 5, 0, 0, 73, 74, 7, 3, 0, 0, 74, 75, 7, 8, 0, 0, 75, 18, 1, 0, 0, 0, 8, 0, 22, 27, 34, 38, 45, 49, 52, 0] \ No newline at end of file diff --git a/server/handlers/fstab/ast/parser/FstabLexer.tokens b/server/handlers/fstab/ast/parser/FstabLexer.tokens new file mode 100644 index 0000000..9584238 --- /dev/null +++ b/server/handlers/fstab/ast/parser/FstabLexer.tokens @@ -0,0 +1,10 @@ +DIGITS=1 +WHITESPACE=2 +HASH=3 +STRING=4 +QUOTED_STRING=5 +ADFS=6 +AFFS=7 +BTRFS=8 +EXFAT=9 +'#'=3 diff --git a/server/handlers/fstab/ast/parser/fstab_base_listener.go b/server/handlers/fstab/ast/parser/fstab_base_listener.go new file mode 100644 index 0000000..0578f75 --- /dev/null +++ b/server/handlers/fstab/ast/parser/fstab_base_listener.go @@ -0,0 +1,64 @@ +// Code generated from Fstab.g4 by ANTLR 4.13.0. DO NOT EDIT. + +package parser // Fstab + +import "github.com/antlr4-go/antlr/v4" + +// BaseFstabListener is a complete listener for a parse tree produced by FstabParser. +type BaseFstabListener struct{} + +var _ FstabListener = &BaseFstabListener{} + +// VisitTerminal is called when a terminal node is visited. +func (s *BaseFstabListener) VisitTerminal(node antlr.TerminalNode) {} + +// VisitErrorNode is called when an error node is visited. +func (s *BaseFstabListener) VisitErrorNode(node antlr.ErrorNode) {} + +// EnterEveryRule is called when any rule is entered. +func (s *BaseFstabListener) EnterEveryRule(ctx antlr.ParserRuleContext) {} + +// ExitEveryRule is called when any rule is exited. +func (s *BaseFstabListener) ExitEveryRule(ctx antlr.ParserRuleContext) {} + +// EnterEntry is called when production entry is entered. +func (s *BaseFstabListener) EnterEntry(ctx *EntryContext) {} + +// ExitEntry is called when production entry is exited. +func (s *BaseFstabListener) ExitEntry(ctx *EntryContext) {} + +// EnterSpec is called when production spec is entered. +func (s *BaseFstabListener) EnterSpec(ctx *SpecContext) {} + +// ExitSpec is called when production spec is exited. +func (s *BaseFstabListener) ExitSpec(ctx *SpecContext) {} + +// EnterMountPoint is called when production mountPoint is entered. +func (s *BaseFstabListener) EnterMountPoint(ctx *MountPointContext) {} + +// ExitMountPoint is called when production mountPoint is exited. +func (s *BaseFstabListener) ExitMountPoint(ctx *MountPointContext) {} + +// EnterFileSystem is called when production fileSystem is entered. +func (s *BaseFstabListener) EnterFileSystem(ctx *FileSystemContext) {} + +// ExitFileSystem is called when production fileSystem is exited. +func (s *BaseFstabListener) ExitFileSystem(ctx *FileSystemContext) {} + +// EnterMountOptions is called when production mountOptions is entered. +func (s *BaseFstabListener) EnterMountOptions(ctx *MountOptionsContext) {} + +// ExitMountOptions is called when production mountOptions is exited. +func (s *BaseFstabListener) ExitMountOptions(ctx *MountOptionsContext) {} + +// EnterFreq is called when production freq is entered. +func (s *BaseFstabListener) EnterFreq(ctx *FreqContext) {} + +// ExitFreq is called when production freq is exited. +func (s *BaseFstabListener) ExitFreq(ctx *FreqContext) {} + +// EnterPass is called when production pass is entered. +func (s *BaseFstabListener) EnterPass(ctx *PassContext) {} + +// ExitPass is called when production pass is exited. +func (s *BaseFstabListener) ExitPass(ctx *PassContext) {} diff --git a/server/handlers/fstab/ast/parser/fstab_lexer.go b/server/handlers/fstab/ast/parser/fstab_lexer.go new file mode 100644 index 0000000..abac890 --- /dev/null +++ b/server/handlers/fstab/ast/parser/fstab_lexer.go @@ -0,0 +1,143 @@ +// Code generated from Fstab.g4 by ANTLR 4.13.0. DO NOT EDIT. + +package parser + +import ( + "fmt" + "github.com/antlr4-go/antlr/v4" + "sync" + "unicode" +) + +// Suppress unused import error +var _ = fmt.Printf +var _ = sync.Once{} +var _ = unicode.IsLetter + +type FstabLexer struct { + *antlr.BaseLexer + channelNames []string + modeNames []string + // TODO: EOF string +} + +var FstabLexerLexerStaticData struct { + once sync.Once + serializedATN []int32 + ChannelNames []string + ModeNames []string + LiteralNames []string + SymbolicNames []string + RuleNames []string + PredictionContextCache *antlr.PredictionContextCache + atn *antlr.ATN + decisionToDFA []*antlr.DFA +} + +func fstablexerLexerInit() { + staticData := &FstabLexerLexerStaticData + staticData.ChannelNames = []string{ + "DEFAULT_TOKEN_CHANNEL", "HIDDEN", + } + staticData.ModeNames = []string{ + "DEFAULT_MODE", + } + staticData.LiteralNames = []string{ + "", "", "", "'#'", + } + staticData.SymbolicNames = []string{ + "", "DIGITS", "WHITESPACE", "HASH", "STRING", "QUOTED_STRING", "ADFS", + "AFFS", "BTRFS", "EXFAT", + } + staticData.RuleNames = []string{ + "DIGITS", "WHITESPACE", "HASH", "STRING", "QUOTED_STRING", "ADFS", "AFFS", + "BTRFS", "EXFAT", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 0, 9, 76, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, + 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 1, 0, 4, 0, 21, + 8, 0, 11, 0, 12, 0, 22, 1, 1, 4, 1, 26, 8, 1, 11, 1, 12, 1, 27, 1, 2, 1, + 2, 1, 3, 4, 3, 33, 8, 3, 11, 3, 12, 3, 34, 1, 4, 1, 4, 3, 4, 39, 8, 4, + 1, 4, 1, 4, 1, 4, 5, 4, 44, 8, 4, 10, 4, 12, 4, 47, 9, 4, 1, 4, 3, 4, 50, + 8, 4, 1, 4, 3, 4, 53, 8, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, + 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, + 8, 1, 8, 1, 8, 0, 0, 9, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, + 8, 17, 9, 1, 0, 12, 1, 0, 48, 57, 2, 0, 9, 9, 32, 32, 3, 0, 9, 9, 32, 32, + 35, 35, 2, 0, 65, 65, 97, 97, 2, 0, 68, 68, 100, 100, 2, 0, 70, 70, 102, + 102, 2, 0, 83, 83, 115, 115, 2, 0, 66, 66, 98, 98, 2, 0, 84, 84, 116, 116, + 2, 0, 82, 82, 114, 114, 2, 0, 69, 69, 101, 101, 2, 0, 88, 88, 120, 120, + 82, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, + 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, + 0, 0, 0, 17, 1, 0, 0, 0, 1, 20, 1, 0, 0, 0, 3, 25, 1, 0, 0, 0, 5, 29, 1, + 0, 0, 0, 7, 32, 1, 0, 0, 0, 9, 36, 1, 0, 0, 0, 11, 54, 1, 0, 0, 0, 13, + 59, 1, 0, 0, 0, 15, 64, 1, 0, 0, 0, 17, 70, 1, 0, 0, 0, 19, 21, 7, 0, 0, + 0, 20, 19, 1, 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 20, 1, 0, 0, 0, 22, 23, + 1, 0, 0, 0, 23, 2, 1, 0, 0, 0, 24, 26, 7, 1, 0, 0, 25, 24, 1, 0, 0, 0, + 26, 27, 1, 0, 0, 0, 27, 25, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 4, 1, 0, + 0, 0, 29, 30, 5, 35, 0, 0, 30, 6, 1, 0, 0, 0, 31, 33, 8, 2, 0, 0, 32, 31, + 1, 0, 0, 0, 33, 34, 1, 0, 0, 0, 34, 32, 1, 0, 0, 0, 34, 35, 1, 0, 0, 0, + 35, 8, 1, 0, 0, 0, 36, 38, 5, 34, 0, 0, 37, 39, 3, 3, 1, 0, 38, 37, 1, + 0, 0, 0, 38, 39, 1, 0, 0, 0, 39, 45, 1, 0, 0, 0, 40, 41, 3, 7, 3, 0, 41, + 42, 3, 3, 1, 0, 42, 44, 1, 0, 0, 0, 43, 40, 1, 0, 0, 0, 44, 47, 1, 0, 0, + 0, 45, 43, 1, 0, 0, 0, 45, 46, 1, 0, 0, 0, 46, 49, 1, 0, 0, 0, 47, 45, + 1, 0, 0, 0, 48, 50, 3, 7, 3, 0, 49, 48, 1, 0, 0, 0, 49, 50, 1, 0, 0, 0, + 50, 52, 1, 0, 0, 0, 51, 53, 5, 34, 0, 0, 52, 51, 1, 0, 0, 0, 52, 53, 1, + 0, 0, 0, 53, 10, 1, 0, 0, 0, 54, 55, 7, 3, 0, 0, 55, 56, 7, 4, 0, 0, 56, + 57, 7, 5, 0, 0, 57, 58, 7, 6, 0, 0, 58, 12, 1, 0, 0, 0, 59, 60, 7, 3, 0, + 0, 60, 61, 7, 5, 0, 0, 61, 62, 7, 5, 0, 0, 62, 63, 7, 6, 0, 0, 63, 14, + 1, 0, 0, 0, 64, 65, 7, 7, 0, 0, 65, 66, 7, 8, 0, 0, 66, 67, 7, 9, 0, 0, + 67, 68, 7, 5, 0, 0, 68, 69, 7, 6, 0, 0, 69, 16, 1, 0, 0, 0, 70, 71, 7, + 10, 0, 0, 71, 72, 7, 11, 0, 0, 72, 73, 7, 5, 0, 0, 73, 74, 7, 3, 0, 0, + 74, 75, 7, 8, 0, 0, 75, 18, 1, 0, 0, 0, 8, 0, 22, 27, 34, 38, 45, 49, 52, + 0, + } + deserializer := antlr.NewATNDeserializer(nil) + staticData.atn = deserializer.Deserialize(staticData.serializedATN) + atn := staticData.atn + staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState)) + decisionToDFA := staticData.decisionToDFA + for index, state := range atn.DecisionToState { + decisionToDFA[index] = antlr.NewDFA(state, index) + } +} + +// FstabLexerInit initializes any static state used to implement FstabLexer. By default the +// static state used to implement the lexer is lazily initialized during the first call to +// NewFstabLexer(). You can call this function if you wish to initialize the static state ahead +// of time. +func FstabLexerInit() { + staticData := &FstabLexerLexerStaticData + staticData.once.Do(fstablexerLexerInit) +} + +// NewFstabLexer produces a new lexer instance for the optional input antlr.CharStream. +func NewFstabLexer(input antlr.CharStream) *FstabLexer { + FstabLexerInit() + l := new(FstabLexer) + l.BaseLexer = antlr.NewBaseLexer(input) + staticData := &FstabLexerLexerStaticData + l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache) + l.channelNames = staticData.ChannelNames + l.modeNames = staticData.ModeNames + l.RuleNames = staticData.RuleNames + l.LiteralNames = staticData.LiteralNames + l.SymbolicNames = staticData.SymbolicNames + l.GrammarFileName = "Fstab.g4" + // TODO: l.EOF = antlr.TokenEOF + + return l +} + +// FstabLexer tokens. +const ( + FstabLexerDIGITS = 1 + FstabLexerWHITESPACE = 2 + FstabLexerHASH = 3 + FstabLexerSTRING = 4 + FstabLexerQUOTED_STRING = 5 + FstabLexerADFS = 6 + FstabLexerAFFS = 7 + FstabLexerBTRFS = 8 + FstabLexerEXFAT = 9 +) diff --git a/server/handlers/fstab/ast/parser/fstab_listener.go b/server/handlers/fstab/ast/parser/fstab_listener.go new file mode 100644 index 0000000..730daae --- /dev/null +++ b/server/handlers/fstab/ast/parser/fstab_listener.go @@ -0,0 +1,52 @@ +// Code generated from Fstab.g4 by ANTLR 4.13.0. DO NOT EDIT. + +package parser // Fstab + +import "github.com/antlr4-go/antlr/v4" + +// FstabListener is a complete listener for a parse tree produced by FstabParser. +type FstabListener interface { + antlr.ParseTreeListener + + // EnterEntry is called when entering the entry production. + EnterEntry(c *EntryContext) + + // EnterSpec is called when entering the spec production. + EnterSpec(c *SpecContext) + + // EnterMountPoint is called when entering the mountPoint production. + EnterMountPoint(c *MountPointContext) + + // EnterFileSystem is called when entering the fileSystem production. + EnterFileSystem(c *FileSystemContext) + + // EnterMountOptions is called when entering the mountOptions production. + EnterMountOptions(c *MountOptionsContext) + + // EnterFreq is called when entering the freq production. + EnterFreq(c *FreqContext) + + // EnterPass is called when entering the pass production. + EnterPass(c *PassContext) + + // ExitEntry is called when exiting the entry production. + ExitEntry(c *EntryContext) + + // ExitSpec is called when exiting the spec production. + ExitSpec(c *SpecContext) + + // ExitMountPoint is called when exiting the mountPoint production. + ExitMountPoint(c *MountPointContext) + + // ExitFileSystem is called when exiting the fileSystem production. + ExitFileSystem(c *FileSystemContext) + + // ExitMountOptions is called when exiting the mountOptions production. + ExitMountOptions(c *MountOptionsContext) + + // ExitFreq is called when exiting the freq production. + ExitFreq(c *FreqContext) + + // ExitPass is called when exiting the pass production. + ExitPass(c *PassContext) +} diff --git a/server/handlers/fstab/ast/parser/fstab_parser.go b/server/handlers/fstab/ast/parser/fstab_parser.go new file mode 100644 index 0000000..7cc9582 --- /dev/null +++ b/server/handlers/fstab/ast/parser/fstab_parser.go @@ -0,0 +1,1171 @@ +// Code generated from Fstab.g4 by ANTLR 4.13.0. DO NOT EDIT. + +package parser // Fstab + +import ( + "fmt" + "strconv" + "sync" + + "github.com/antlr4-go/antlr/v4" +) + +// Suppress unused import errors +var _ = fmt.Printf +var _ = strconv.Itoa +var _ = sync.Once{} + +type FstabParser struct { + *antlr.BaseParser +} + +var FstabParserStaticData struct { + once sync.Once + serializedATN []int32 + LiteralNames []string + SymbolicNames []string + RuleNames []string + PredictionContextCache *antlr.PredictionContextCache + atn *antlr.ATN + decisionToDFA []*antlr.DFA +} + +func fstabParserInit() { + staticData := &FstabParserStaticData + staticData.LiteralNames = []string{ + "", "", "", "'#'", + } + staticData.SymbolicNames = []string{ + "", "DIGITS", "WHITESPACE", "HASH", "STRING", "QUOTED_STRING", "ADFS", + "AFFS", "BTRFS", "EXFAT", + } + staticData.RuleNames = []string{ + "entry", "spec", "mountPoint", "fileSystem", "mountOptions", "freq", + "pass", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 1, 9, 68, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, + 2, 5, 7, 5, 2, 6, 7, 6, 1, 0, 3, 0, 16, 8, 0, 1, 0, 3, 0, 19, 8, 0, 1, + 0, 3, 0, 22, 8, 0, 1, 0, 3, 0, 25, 8, 0, 1, 0, 3, 0, 28, 8, 0, 1, 0, 3, + 0, 31, 8, 0, 1, 0, 3, 0, 34, 8, 0, 1, 0, 3, 0, 37, 8, 0, 1, 0, 3, 0, 40, + 8, 0, 1, 0, 3, 0, 43, 8, 0, 1, 0, 3, 0, 46, 8, 0, 1, 0, 3, 0, 49, 8, 0, + 1, 0, 3, 0, 52, 8, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, + 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 0, 0, 7, 0, 2, 4, 6, 8, 10, 12, + 0, 2, 1, 0, 4, 5, 1, 0, 4, 9, 73, 0, 15, 1, 0, 0, 0, 2, 55, 1, 0, 0, 0, + 4, 57, 1, 0, 0, 0, 6, 59, 1, 0, 0, 0, 8, 61, 1, 0, 0, 0, 10, 63, 1, 0, + 0, 0, 12, 65, 1, 0, 0, 0, 14, 16, 5, 2, 0, 0, 15, 14, 1, 0, 0, 0, 15, 16, + 1, 0, 0, 0, 16, 18, 1, 0, 0, 0, 17, 19, 3, 2, 1, 0, 18, 17, 1, 0, 0, 0, + 18, 19, 1, 0, 0, 0, 19, 21, 1, 0, 0, 0, 20, 22, 5, 2, 0, 0, 21, 20, 1, + 0, 0, 0, 21, 22, 1, 0, 0, 0, 22, 24, 1, 0, 0, 0, 23, 25, 3, 4, 2, 0, 24, + 23, 1, 0, 0, 0, 24, 25, 1, 0, 0, 0, 25, 27, 1, 0, 0, 0, 26, 28, 5, 2, 0, + 0, 27, 26, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 30, 1, 0, 0, 0, 29, 31, + 3, 6, 3, 0, 30, 29, 1, 0, 0, 0, 30, 31, 1, 0, 0, 0, 31, 33, 1, 0, 0, 0, + 32, 34, 5, 2, 0, 0, 33, 32, 1, 0, 0, 0, 33, 34, 1, 0, 0, 0, 34, 36, 1, + 0, 0, 0, 35, 37, 3, 8, 4, 0, 36, 35, 1, 0, 0, 0, 36, 37, 1, 0, 0, 0, 37, + 39, 1, 0, 0, 0, 38, 40, 5, 2, 0, 0, 39, 38, 1, 0, 0, 0, 39, 40, 1, 0, 0, + 0, 40, 42, 1, 0, 0, 0, 41, 43, 3, 10, 5, 0, 42, 41, 1, 0, 0, 0, 42, 43, + 1, 0, 0, 0, 43, 45, 1, 0, 0, 0, 44, 46, 5, 2, 0, 0, 45, 44, 1, 0, 0, 0, + 45, 46, 1, 0, 0, 0, 46, 48, 1, 0, 0, 0, 47, 49, 3, 12, 6, 0, 48, 47, 1, + 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 51, 1, 0, 0, 0, 50, 52, 5, 2, 0, 0, 51, + 50, 1, 0, 0, 0, 51, 52, 1, 0, 0, 0, 52, 53, 1, 0, 0, 0, 53, 54, 5, 0, 0, + 1, 54, 1, 1, 0, 0, 0, 55, 56, 7, 0, 0, 0, 56, 3, 1, 0, 0, 0, 57, 58, 7, + 0, 0, 0, 58, 5, 1, 0, 0, 0, 59, 60, 7, 1, 0, 0, 60, 7, 1, 0, 0, 0, 61, + 62, 7, 0, 0, 0, 62, 9, 1, 0, 0, 0, 63, 64, 5, 1, 0, 0, 64, 11, 1, 0, 0, + 0, 65, 66, 5, 1, 0, 0, 66, 13, 1, 0, 0, 0, 13, 15, 18, 21, 24, 27, 30, + 33, 36, 39, 42, 45, 48, 51, + } + deserializer := antlr.NewATNDeserializer(nil) + staticData.atn = deserializer.Deserialize(staticData.serializedATN) + atn := staticData.atn + staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState)) + decisionToDFA := staticData.decisionToDFA + for index, state := range atn.DecisionToState { + decisionToDFA[index] = antlr.NewDFA(state, index) + } +} + +// FstabParserInit initializes any static state used to implement FstabParser. By default the +// static state used to implement the parser is lazily initialized during the first call to +// NewFstabParser(). You can call this function if you wish to initialize the static state ahead +// of time. +func FstabParserInit() { + staticData := &FstabParserStaticData + staticData.once.Do(fstabParserInit) +} + +// NewFstabParser produces a new parser instance for the optional input antlr.TokenStream. +func NewFstabParser(input antlr.TokenStream) *FstabParser { + FstabParserInit() + this := new(FstabParser) + this.BaseParser = antlr.NewBaseParser(input) + staticData := &FstabParserStaticData + this.Interpreter = antlr.NewParserATNSimulator(this, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache) + this.RuleNames = staticData.RuleNames + this.LiteralNames = staticData.LiteralNames + this.SymbolicNames = staticData.SymbolicNames + this.GrammarFileName = "Fstab.g4" + + return this +} + +// FstabParser tokens. +const ( + FstabParserEOF = antlr.TokenEOF + FstabParserDIGITS = 1 + FstabParserWHITESPACE = 2 + FstabParserHASH = 3 + FstabParserSTRING = 4 + FstabParserQUOTED_STRING = 5 + FstabParserADFS = 6 + FstabParserAFFS = 7 + FstabParserBTRFS = 8 + FstabParserEXFAT = 9 +) + +// FstabParser rules. +const ( + FstabParserRULE_entry = 0 + FstabParserRULE_spec = 1 + FstabParserRULE_mountPoint = 2 + FstabParserRULE_fileSystem = 3 + FstabParserRULE_mountOptions = 4 + FstabParserRULE_freq = 5 + FstabParserRULE_pass = 6 +) + +// IEntryContext is an interface to support dynamic dispatch. +type IEntryContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + EOF() antlr.TerminalNode + AllWHITESPACE() []antlr.TerminalNode + WHITESPACE(i int) antlr.TerminalNode + Spec() ISpecContext + MountPoint() IMountPointContext + FileSystem() IFileSystemContext + MountOptions() IMountOptionsContext + Freq() IFreqContext + Pass() IPassContext + + // IsEntryContext differentiates from other interfaces. + IsEntryContext() +} + +type EntryContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyEntryContext() *EntryContext { + var p = new(EntryContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_entry + return p +} + +func InitEmptyEntryContext(p *EntryContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_entry +} + +func (*EntryContext) IsEntryContext() {} + +func NewEntryContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *EntryContext { + var p = new(EntryContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = FstabParserRULE_entry + + return p +} + +func (s *EntryContext) GetParser() antlr.Parser { return s.parser } + +func (s *EntryContext) EOF() antlr.TerminalNode { + return s.GetToken(FstabParserEOF, 0) +} + +func (s *EntryContext) AllWHITESPACE() []antlr.TerminalNode { + return s.GetTokens(FstabParserWHITESPACE) +} + +func (s *EntryContext) WHITESPACE(i int) antlr.TerminalNode { + return s.GetToken(FstabParserWHITESPACE, i) +} + +func (s *EntryContext) Spec() ISpecContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ISpecContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(ISpecContext) +} + +func (s *EntryContext) MountPoint() IMountPointContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IMountPointContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IMountPointContext) +} + +func (s *EntryContext) FileSystem() IFileSystemContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFileSystemContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFileSystemContext) +} + +func (s *EntryContext) MountOptions() IMountOptionsContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IMountOptionsContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IMountOptionsContext) +} + +func (s *EntryContext) Freq() IFreqContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFreqContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFreqContext) +} + +func (s *EntryContext) Pass() IPassContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IPassContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IPassContext) +} + +func (s *EntryContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EntryContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *EntryContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.EnterEntry(s) + } +} + +func (s *EntryContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.ExitEntry(s) + } +} + +func (p *FstabParser) Entry() (localctx IEntryContext) { + localctx = NewEntryContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 0, FstabParserRULE_entry) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(15) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 0, p.GetParserRuleContext()) == 1 { + { + p.SetState(14) + p.Match(FstabParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(18) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 1, p.GetParserRuleContext()) == 1 { + { + p.SetState(17) + p.Spec() + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(21) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 2, p.GetParserRuleContext()) == 1 { + { + p.SetState(20) + p.Match(FstabParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(24) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 3, p.GetParserRuleContext()) == 1 { + { + p.SetState(23) + p.MountPoint() + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(27) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 4, p.GetParserRuleContext()) == 1 { + { + p.SetState(26) + p.Match(FstabParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(30) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 5, p.GetParserRuleContext()) == 1 { + { + p.SetState(29) + p.FileSystem() + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(33) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 6, p.GetParserRuleContext()) == 1 { + { + p.SetState(32) + p.Match(FstabParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(36) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == FstabParserSTRING || _la == FstabParserQUOTED_STRING { + { + p.SetState(35) + p.MountOptions() + } + + } + p.SetState(39) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 8, p.GetParserRuleContext()) == 1 { + { + p.SetState(38) + p.Match(FstabParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(42) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 9, p.GetParserRuleContext()) == 1 { + { + p.SetState(41) + p.Freq() + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(45) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 10, p.GetParserRuleContext()) == 1 { + { + p.SetState(44) + p.Match(FstabParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + p.SetState(48) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == FstabParserDIGITS { + { + p.SetState(47) + p.Pass() + } + + } + p.SetState(51) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == FstabParserWHITESPACE { + { + p.SetState(50) + p.Match(FstabParserWHITESPACE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(53) + p.Match(FstabParserEOF) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ISpecContext is an interface to support dynamic dispatch. +type ISpecContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + QUOTED_STRING() antlr.TerminalNode + STRING() antlr.TerminalNode + + // IsSpecContext differentiates from other interfaces. + IsSpecContext() +} + +type SpecContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptySpecContext() *SpecContext { + var p = new(SpecContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_spec + return p +} + +func InitEmptySpecContext(p *SpecContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_spec +} + +func (*SpecContext) IsSpecContext() {} + +func NewSpecContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *SpecContext { + var p = new(SpecContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = FstabParserRULE_spec + + return p +} + +func (s *SpecContext) GetParser() antlr.Parser { return s.parser } + +func (s *SpecContext) QUOTED_STRING() antlr.TerminalNode { + return s.GetToken(FstabParserQUOTED_STRING, 0) +} + +func (s *SpecContext) STRING() antlr.TerminalNode { + return s.GetToken(FstabParserSTRING, 0) +} + +func (s *SpecContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *SpecContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *SpecContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.EnterSpec(s) + } +} + +func (s *SpecContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.ExitSpec(s) + } +} + +func (p *FstabParser) Spec() (localctx ISpecContext) { + localctx = NewSpecContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 2, FstabParserRULE_spec) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(55) + _la = p.GetTokenStream().LA(1) + + if !(_la == FstabParserSTRING || _la == FstabParserQUOTED_STRING) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IMountPointContext is an interface to support dynamic dispatch. +type IMountPointContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + QUOTED_STRING() antlr.TerminalNode + STRING() antlr.TerminalNode + + // IsMountPointContext differentiates from other interfaces. + IsMountPointContext() +} + +type MountPointContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyMountPointContext() *MountPointContext { + var p = new(MountPointContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_mountPoint + return p +} + +func InitEmptyMountPointContext(p *MountPointContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_mountPoint +} + +func (*MountPointContext) IsMountPointContext() {} + +func NewMountPointContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *MountPointContext { + var p = new(MountPointContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = FstabParserRULE_mountPoint + + return p +} + +func (s *MountPointContext) GetParser() antlr.Parser { return s.parser } + +func (s *MountPointContext) QUOTED_STRING() antlr.TerminalNode { + return s.GetToken(FstabParserQUOTED_STRING, 0) +} + +func (s *MountPointContext) STRING() antlr.TerminalNode { + return s.GetToken(FstabParserSTRING, 0) +} + +func (s *MountPointContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *MountPointContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *MountPointContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.EnterMountPoint(s) + } +} + +func (s *MountPointContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.ExitMountPoint(s) + } +} + +func (p *FstabParser) MountPoint() (localctx IMountPointContext) { + localctx = NewMountPointContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 4, FstabParserRULE_mountPoint) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(57) + _la = p.GetTokenStream().LA(1) + + if !(_la == FstabParserSTRING || _la == FstabParserQUOTED_STRING) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IFileSystemContext is an interface to support dynamic dispatch. +type IFileSystemContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ADFS() antlr.TerminalNode + AFFS() antlr.TerminalNode + BTRFS() antlr.TerminalNode + EXFAT() antlr.TerminalNode + STRING() antlr.TerminalNode + QUOTED_STRING() antlr.TerminalNode + + // IsFileSystemContext differentiates from other interfaces. + IsFileSystemContext() +} + +type FileSystemContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFileSystemContext() *FileSystemContext { + var p = new(FileSystemContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_fileSystem + return p +} + +func InitEmptyFileSystemContext(p *FileSystemContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_fileSystem +} + +func (*FileSystemContext) IsFileSystemContext() {} + +func NewFileSystemContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FileSystemContext { + var p = new(FileSystemContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = FstabParserRULE_fileSystem + + return p +} + +func (s *FileSystemContext) GetParser() antlr.Parser { return s.parser } + +func (s *FileSystemContext) ADFS() antlr.TerminalNode { + return s.GetToken(FstabParserADFS, 0) +} + +func (s *FileSystemContext) AFFS() antlr.TerminalNode { + return s.GetToken(FstabParserAFFS, 0) +} + +func (s *FileSystemContext) BTRFS() antlr.TerminalNode { + return s.GetToken(FstabParserBTRFS, 0) +} + +func (s *FileSystemContext) EXFAT() antlr.TerminalNode { + return s.GetToken(FstabParserEXFAT, 0) +} + +func (s *FileSystemContext) STRING() antlr.TerminalNode { + return s.GetToken(FstabParserSTRING, 0) +} + +func (s *FileSystemContext) QUOTED_STRING() antlr.TerminalNode { + return s.GetToken(FstabParserQUOTED_STRING, 0) +} + +func (s *FileSystemContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FileSystemContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *FileSystemContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.EnterFileSystem(s) + } +} + +func (s *FileSystemContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.ExitFileSystem(s) + } +} + +func (p *FstabParser) FileSystem() (localctx IFileSystemContext) { + localctx = NewFileSystemContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 6, FstabParserRULE_fileSystem) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(59) + _la = p.GetTokenStream().LA(1) + + if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&1008) != 0) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IMountOptionsContext is an interface to support dynamic dispatch. +type IMountOptionsContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + QUOTED_STRING() antlr.TerminalNode + STRING() antlr.TerminalNode + + // IsMountOptionsContext differentiates from other interfaces. + IsMountOptionsContext() +} + +type MountOptionsContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyMountOptionsContext() *MountOptionsContext { + var p = new(MountOptionsContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_mountOptions + return p +} + +func InitEmptyMountOptionsContext(p *MountOptionsContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_mountOptions +} + +func (*MountOptionsContext) IsMountOptionsContext() {} + +func NewMountOptionsContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *MountOptionsContext { + var p = new(MountOptionsContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = FstabParserRULE_mountOptions + + return p +} + +func (s *MountOptionsContext) GetParser() antlr.Parser { return s.parser } + +func (s *MountOptionsContext) QUOTED_STRING() antlr.TerminalNode { + return s.GetToken(FstabParserQUOTED_STRING, 0) +} + +func (s *MountOptionsContext) STRING() antlr.TerminalNode { + return s.GetToken(FstabParserSTRING, 0) +} + +func (s *MountOptionsContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *MountOptionsContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *MountOptionsContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.EnterMountOptions(s) + } +} + +func (s *MountOptionsContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.ExitMountOptions(s) + } +} + +func (p *FstabParser) MountOptions() (localctx IMountOptionsContext) { + localctx = NewMountOptionsContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 8, FstabParserRULE_mountOptions) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(61) + _la = p.GetTokenStream().LA(1) + + if !(_la == FstabParserSTRING || _la == FstabParserQUOTED_STRING) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IFreqContext is an interface to support dynamic dispatch. +type IFreqContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + DIGITS() antlr.TerminalNode + + // IsFreqContext differentiates from other interfaces. + IsFreqContext() +} + +type FreqContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFreqContext() *FreqContext { + var p = new(FreqContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_freq + return p +} + +func InitEmptyFreqContext(p *FreqContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_freq +} + +func (*FreqContext) IsFreqContext() {} + +func NewFreqContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FreqContext { + var p = new(FreqContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = FstabParserRULE_freq + + return p +} + +func (s *FreqContext) GetParser() antlr.Parser { return s.parser } + +func (s *FreqContext) DIGITS() antlr.TerminalNode { + return s.GetToken(FstabParserDIGITS, 0) +} + +func (s *FreqContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FreqContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *FreqContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.EnterFreq(s) + } +} + +func (s *FreqContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.ExitFreq(s) + } +} + +func (p *FstabParser) Freq() (localctx IFreqContext) { + localctx = NewFreqContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 10, FstabParserRULE_freq) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(63) + p.Match(FstabParserDIGITS) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IPassContext is an interface to support dynamic dispatch. +type IPassContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + DIGITS() antlr.TerminalNode + + // IsPassContext differentiates from other interfaces. + IsPassContext() +} + +type PassContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyPassContext() *PassContext { + var p = new(PassContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_pass + return p +} + +func InitEmptyPassContext(p *PassContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = FstabParserRULE_pass +} + +func (*PassContext) IsPassContext() {} + +func NewPassContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *PassContext { + var p = new(PassContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = FstabParserRULE_pass + + return p +} + +func (s *PassContext) GetParser() antlr.Parser { return s.parser } + +func (s *PassContext) DIGITS() antlr.TerminalNode { + return s.GetToken(FstabParserDIGITS, 0) +} + +func (s *PassContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *PassContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *PassContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.EnterPass(s) + } +} + +func (s *PassContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(FstabListener); ok { + listenerT.ExitPass(s) + } +} + +func (p *FstabParser) Pass() (localctx IPassContext) { + localctx = NewPassContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 12, FstabParserRULE_pass) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(65) + p.Match(FstabParserDIGITS) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} diff --git a/server/handlers/fstab/ast/parser_test.go b/server/handlers/fstab/ast/parser_test.go index 6e3a2c7..ac37136 100644 --- a/server/handlers/fstab/ast/parser_test.go +++ b/server/handlers/fstab/ast/parser_test.go @@ -11,6 +11,7 @@ func TestExample1( ) { input := utils.Dedent(` LABEL=test /mnt/test ext4 defaults 0 0 +LABEL=example /mnt/example fat32 defaults 0 2 `) c := NewFstabConfig() @@ -20,8 +21,8 @@ LABEL=test /mnt/test ext4 defaults 0 0 t.Fatalf("Expected no errors, got %v", errors) } - if c.Entries.Size() != 1 { - t.Fatalf("Expected 1 entry, got %d", c.Entries.Size()) + if c.Entries.Size() != 2 { + t.Fatalf("Expected 2 entry, got %d", c.Entries.Size()) } rawFirstEntry, _ := c.Entries.Get(uint32(0)) @@ -93,6 +94,12 @@ LABEL=test /mnt/test ext4 defaults 0 0 if !(field == FstabFieldFreq) { t.Errorf("Expected field to be freq, got %v", field) } + + rawSecondEntry, _ := c.Entries.Get(uint32(1)) + secondEntry := rawSecondEntry.(*FstabEntry) + if !(secondEntry.Fields.Start.Line == 1) { + t.Errorf("Expected start line to be 1, got %d", secondEntry.Fields.Start.Line) + } } func TestIncompleteExample( diff --git a/server/handlers/fstab/fields/spec.go b/server/handlers/fstab/fields/spec.go index ae465ee..df43f6d 100644 --- a/server/handlers/fstab/fields/spec.go +++ b/server/handlers/fstab/fields/spec.go @@ -15,7 +15,7 @@ var LabelField = docvalues.RegexValue{ var SpecField = docvalues.OrValue{ Values: []docvalues.DeprecatedValue{ docvalues.PathValue{ - RequiredType: docvalues.PathTypeFile, + RequiredType: docvalues.PathTypeExistenceOptional, }, docvalues.KeyEnumAssignmentValue{ Separator: "=", diff --git a/server/handlers/fstab/fstab_test.go b/server/handlers/fstab/fstab_test.go index f6b21c1..e36c5f3 100644 --- a/server/handlers/fstab/fstab_test.go +++ b/server/handlers/fstab/fstab_test.go @@ -101,6 +101,36 @@ LABEL=test /mnt/test btrfs subvol=backup,fat=32 0 0 } } +func TestIncompleteExample(t *testing.T) { + input := utils.Dedent(` +LABEL=test /mnt/test defaults 0 0 +`) + p := ast.NewFstabConfig() + + errors := p.Parse(input) + + if len(errors) > 0 { + t.Fatal("ParseFromContent returned error", errors) + } + + rawFirstEntry, _ := p.Entries.Get(uint32(0)) + firstEntry := rawFirstEntry.(*ast.FstabEntry) + name := firstEntry.GetFieldAtPosition(common.CursorPosition(0)) + if !(name == ast.FstabFieldSpec) { + t.Errorf("GetFieldAtPosition failed to return correct field name. Got: %v but expected: %v", name, ast.FstabFieldSpec) + } + + name = firstEntry.GetFieldAtPosition(common.CursorPosition(9)) + if !(name == ast.FstabFieldSpec) { + t.Errorf("GetFieldAtPosition failed to return correct field name. Got: %v but expected: %v", name, ast.FstabFieldSpec) + } + + name = firstEntry.GetFieldAtPosition(common.CursorPosition(21)) + if !(name == ast.FstabFieldFileSystemType) { + t.Errorf("GetFieldAtPosition failed to return correct field name. Got: %v but expected: %v", name, ast.FstabFieldFileSystemType) + } +} + // func TestExample1(t *testing.T) { // input := utils.Dedent(` // /dev/disk/by-uuid/19ae4e13-1d6d-4833-965b-a20197aebf27 /mnt/RetroGames auto nosuid,nodev,nofail,x-gvfs-show 0 0 diff --git a/server/handlers/fstab/handlers/completions.go b/server/handlers/fstab/handlers/completions.go index 9d10d90..c2d00f5 100644 --- a/server/handlers/fstab/handlers/completions.go +++ b/server/handlers/fstab/handlers/completions.go @@ -42,7 +42,6 @@ func GetCompletion( fileSystemType := entry.Fields.FilesystemType.Value.Value completions := make([]protocol.CompletionItem, 0, 50) - println("fetching field options now", line, cursor) for _, completion := range fields.DefaultMountOptionsField.DeprecatedFetchCompletions(line, cursor) { var documentation string @@ -108,5 +107,9 @@ func getFieldSafely(field *ast.FstabField, cursor common.CursorPosition) (string return "", 0 } + if uint32(cursor) < field.Start.Character { + return "", 0 + } + return field.Value.Raw, common.CursorToCharacterIndex(uint32(cursor) - field.Start.Character) } diff --git a/server/handlers/ssh_config/ast/listener.go b/server/handlers/ssh_config/ast/listener.go index ae56143..1522513 100644 --- a/server/handlers/ssh_config/ast/listener.go +++ b/server/handlers/ssh_config/ast/listener.go @@ -26,6 +26,13 @@ func createListenerContext() *sshListenerContext { return context } +type sshParserListener struct { + *parser.BaseConfigListener + Config *SSHConfig + Errors []common.LSPError + sshContext *sshListenerContext +} + func createListener( config *SSHConfig, context *sshListenerContext, @@ -37,13 +44,6 @@ func createListener( } } -type sshParserListener struct { - *parser.BaseConfigListener - Config *SSHConfig - Errors []common.LSPError - sshContext *sshListenerContext -} - func (s *sshParserListener) EnterEntry(ctx *parser.EntryContext) { location := common.CharacterRangeFromCtx(ctx.BaseParserRuleContext) location.ChangeBothLines(s.sshContext.line) diff --git a/server/update_antlr_parser.sh b/server/update_antlr_parser.sh index e2e6005..9534937 100755 --- a/server/update_antlr_parser.sh +++ b/server/update_antlr_parser.sh @@ -5,6 +5,9 @@ ROOT=$(git rev-parse --show-toplevel)/server # aliases cd $ROOT/handlers/aliases && antlr4 -Dlanguage=Go -o ast/parser Aliases.g4 +# fstab +cd $ROOT/hanlders/fstab && antlr4 -Dlanguage=Go -o ast/parser Fstab.g4 + # sshd_config cd $ROOT/handlers/sshd_config && antlr4 -Dlanguage=Go -o ast/parser Config.g4 cd $ROOT/handlers/sshd_config/match-parser && antlr4 -Dlanguage=Go -o parser Match.g4 From 9f927b8685c57798ea679295011b2529c17ffc1a Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Wed, 9 Oct 2024 22:04:29 +0200 Subject: [PATCH 11/12] fix(fstab): Make options, freq and spec optional --- server/handlers/fstab/analyzer/fields.go | 42 ++---------------------- server/handlers/fstab/analyzer/values.go | 18 +++++++--- 2 files changed, 15 insertions(+), 45 deletions(-) diff --git a/server/handlers/fstab/analyzer/fields.go b/server/handlers/fstab/analyzer/fields.go index 1f8b054..371e243 100644 --- a/server/handlers/fstab/analyzer/fields.go +++ b/server/handlers/fstab/analyzer/fields.go @@ -83,46 +83,8 @@ func analyzeFieldAreFilled( 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, + Message: `The options field is empty. The usual convention is to use at least "defaults" keyword there.`, + Severity: &common.SeverityWarning, }) continue diff --git a/server/handlers/fstab/analyzer/values.go b/server/handlers/fstab/analyzer/values.go index fd6d731..bd7d8ba 100644 --- a/server/handlers/fstab/analyzer/values.go +++ b/server/handlers/fstab/analyzer/values.go @@ -16,18 +16,26 @@ func analyzeValuesAreValid( for it.Next() { entry := it.Value().(*ast.FstabEntry) - mountOptions := entry.FetchMountOptionsField(true) checkField(ctx, entry.Fields.Spec, fields.SpecField) checkField(ctx, entry.Fields.MountPoint, fields.MountPointField) checkField(ctx, entry.Fields.FilesystemType, fields.FileSystemTypeField) - if mountOptions != nil { - checkField(ctx, entry.Fields.Options, mountOptions) + if entry.Fields.Options != nil { + mountOptions := entry.FetchMountOptionsField(true) + + if mountOptions != nil { + checkField(ctx, entry.Fields.Options, mountOptions) + } } - checkField(ctx, entry.Fields.Freq, fields.FreqField) - checkField(ctx, entry.Fields.Pass, fields.PassField) + if entry.Fields.Freq != nil { + checkField(ctx, entry.Fields.Freq, fields.FreqField) + } + + if entry.Fields.Pass != nil { + checkField(ctx, entry.Fields.Pass, fields.PassField) + } } } From 948e3503de29dc23ca79bb0a2ce92844fc46afb6 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Wed, 9 Oct 2024 22:09:17 +0200 Subject: [PATCH 12/12] feat(fstab): Handle leading comments --- server/handlers/fstab/ast/parser.go | 11 +++++++++-- server/handlers/fstab/fstab_test.go | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/server/handlers/fstab/ast/parser.go b/server/handlers/fstab/ast/parser.go index ac63d6f..f24a6fa 100644 --- a/server/handlers/fstab/ast/parser.go +++ b/server/handlers/fstab/ast/parser.go @@ -6,6 +6,7 @@ import ( "config-lsp/handlers/fstab/ast/parser" "config-lsp/utils" "regexp" + "strings" "github.com/antlr4-go/antlr/v4" "github.com/emirpasic/gods/maps/treemap" @@ -27,13 +28,15 @@ func (c *FstabConfig) Clear() { var commentPattern = regexp.MustCompile(`^\s*#`) var emptyPattern = regexp.MustCompile(`^\s*$`) +var leadingCommentPattern = regexp.MustCompile(`^(.+?)#`) func (c *FstabConfig) Parse(input string) []common.LSPError { errors := make([]common.LSPError, 0) lines := utils.SplitIntoLines(input) context := createListenerContext() - for rawLineNumber, line := range lines { + for rawLineNumber, rawLine := range lines { + line := rawLine lineNumber := uint32(rawLineNumber) context.line = lineNumber @@ -46,6 +49,11 @@ func (c *FstabConfig) Parse(input string) []common.LSPError { continue } + if strings.Contains(line, "#") { + matches := leadingCommentPattern.FindStringSubmatch(line) + line = matches[1] + } + errors = append( errors, c.parseStatement(context, line)..., @@ -55,7 +63,6 @@ func (c *FstabConfig) Parse(input string) []common.LSPError { return errors } -// TODO: Handle leading comments func (c *FstabConfig) parseStatement( context *fstabListenerContext, input string, diff --git a/server/handlers/fstab/fstab_test.go b/server/handlers/fstab/fstab_test.go index e36c5f3..9d47ecf 100644 --- a/server/handlers/fstab/fstab_test.go +++ b/server/handlers/fstab/fstab_test.go @@ -166,6 +166,24 @@ UUID=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2 } +func TestArchExample1WithComments(t *testing.T) { + input := utils.Dedent(` +# Hello there! +UUID=0a3407de-014b-458b-b5c1-848e92a327a3 / ext4 defaults 0 1 +# How are you? +UUID=f9fe0b69-a280-415d-a03a-a32752370dee none swap defaults 0 0 # And I am trailing! +UUID=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2 # I am tailing too! +`) + p := ast.NewFstabConfig() + + errors := p.Parse(input) + + if len(errors) > 0 { + t.Fatalf("ParseFromContent failed with error %v", errors) + } + +} + func TestArchExample2(t *testing.T) { input := utils.Dedent(` /dev/sda1 /boot vfat defaults 0 2