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, }, }, 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) + } +} 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/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/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..371e243 --- /dev/null +++ b/server/handlers/fstab/analyzer/fields.go @@ -0,0 +1,93 @@ +package analyzer + +import ( + "config-lsp/common" + "config-lsp/handlers/fstab/ast" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func analyzeFieldAreFilled( + ctx *analyzerContext, +) { + it := ctx.document.Config.Entries.Iterator() + for it.Next() { + entry := it.Value().(*ast.FstabEntry) + + if entry.Fields.Spec == nil || entry.Fields.Spec.Value.Value == "" { + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: protocol.Range{ + Start: protocol.Position{ + Line: entry.Fields.Start.Line, + Character: 0, + }, + End: protocol.Position{ + Line: entry.Fields.Start.Line, + Character: 0, + }, + }, + Message: "The spec field is missing", + Severity: &common.SeverityError, + }) + + continue + } + + if entry.Fields.MountPoint == nil || entry.Fields.MountPoint.Value.Value == "" { + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: protocol.Range{ + Start: protocol.Position{ + Line: entry.Fields.Start.Line, + Character: entry.Fields.Spec.End.Character, + }, + End: protocol.Position{ + Line: entry.Fields.Start.Line, + Character: entry.Fields.Spec.End.Character, + }, + }, + Message: "The mount point field is missing", + Severity: &common.SeverityError, + }) + + continue + } + + if entry.Fields.FilesystemType == nil || entry.Fields.FilesystemType.Value.Value == "" { + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: protocol.Range{ + Start: protocol.Position{ + Line: entry.Fields.Start.Line, + Character: entry.Fields.MountPoint.End.Character, + }, + End: protocol.Position{ + Line: entry.Fields.Start.Line, + Character: entry.Fields.MountPoint.End.Character, + }, + }, + Message: "The file system type field is missing", + Severity: &common.SeverityError, + }) + + continue + } + + if entry.Fields.Options == nil || entry.Fields.Options.Value.Value == "" { + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: protocol.Range{ + Start: protocol.Position{ + Line: entry.Fields.Start.Line, + Character: entry.Fields.FilesystemType.End.Character, + }, + End: protocol.Position{ + Line: entry.Fields.Start.Line, + Character: entry.Fields.FilesystemType.End.Character, + }, + }, + Message: `The options field is empty. The usual convention is to use at least "defaults" keyword there.`, + Severity: &common.SeverityWarning, + }) + + continue + } + } +} 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..bd7d8ba --- /dev/null +++ b/server/handlers/fstab/analyzer/values.go @@ -0,0 +1,58 @@ +package analyzer + +import ( + "config-lsp/common" + docvalues "config-lsp/doc-values" + "config-lsp/handlers/fstab/ast" + "config-lsp/handlers/fstab/fields" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func analyzeValuesAreValid( + ctx *analyzerContext, +) { + it := ctx.document.Config.Entries.Iterator() + + for it.Next() { + entry := it.Value().(*ast.FstabEntry) + + checkField(ctx, entry.Fields.Spec, fields.SpecField) + checkField(ctx, entry.Fields.MountPoint, fields.MountPointField) + checkField(ctx, entry.Fields.FilesystemType, fields.FileSystemTypeField) + + if entry.Fields.Options != nil { + mountOptions := entry.FetchMountOptionsField(true) + + if mountOptions != nil { + checkField(ctx, entry.Fields.Options, mountOptions) + } + } + + if entry.Fields.Freq != nil { + checkField(ctx, entry.Fields.Freq, fields.FreqField) + } + + if entry.Fields.Pass != nil { + checkField(ctx, entry.Fields.Pass, fields.PassField) + } + } +} + +func checkField( + ctx *analyzerContext, + field *ast.FstabField, + docOption docvalues.DeprecatedValue, +) { + invalidValues := docOption.DeprecatedCheckIsValid(field.Value.Value) + + for _, invalidValue := range invalidValues { + err := docvalues.LSPErrorFromInvalidValue(field.Start.Line, *invalidValue).ShiftCharacter(field.Start.Character) + + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Range: err.Range.ToLSPRange(), + Message: err.Err.Error(), + Severity: &common.SeverityError, + }) + } +} 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/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 new file mode 100644 index 0000000..8286ba0 --- /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/fstab_fields.go b/server/handlers/fstab/ast/fstab_fields.go new file mode 100644 index 0000000..77679b5 --- /dev/null +++ b/server/handlers/fstab/ast/fstab_fields.go @@ -0,0 +1,172 @@ +package ast + +import ( + "config-lsp/common" + docvalues "config-lsp/doc-values" + "config-lsp/handlers/fstab/fields" + "config-lsp/utils" +) + +// func (c FstabConfig) GetEntry(line uint32) *FstabEntry { +// entry, found := c.Entries.Get(line) +// +// if !found { +// return nil +// } +// +// return entry.(*FstabEntry) +// } + +// LABEL=test ext4 defaults 0 0 +func (e FstabEntry) GetFieldAtPosition(position common.Position) FstabFieldName { + // No fields defined, empty line + if e.Fields.Spec == nil && e.Fields.MountPoint == nil && e.Fields.FilesystemType == nil && e.Fields.Options == nil && e.Fields.Freq == nil && e.Fields.Pass == nil { + return FstabFieldSpec + } + + // First, try if out of the existing fields the user wants to edit one of them + + if e.Fields.Spec != nil && e.Fields.Spec.ContainsPosition(position) { + return FstabFieldSpec + } + if e.Fields.MountPoint != nil && e.Fields.MountPoint.ContainsPosition(position) { + return FstabFieldMountPoint + } + if e.Fields.FilesystemType != nil && e.Fields.FilesystemType.ContainsPosition(position) { + return FstabFieldFileSystemType + } + if e.Fields.Options != nil && e.Fields.Options.ContainsPosition(position) { + return FstabFieldOptions + } + if e.Fields.Freq != nil && e.Fields.Freq.ContainsPosition(position) { + return FstabFieldFreq + } + if e.Fields.Pass != nil && e.Fields.Pass.ContainsPosition(position) { + return FstabFieldPass + } + + // Okay let's try to fetch the field by assuming the user is typing from left to right normally + + if e.Fields.Spec != nil && e.Fields.Spec.IsPositionAfterEnd(position) && (e.Fields.MountPoint == nil || e.Fields.MountPoint.IsPositionBeforeEnd(position)) { + return FstabFieldMountPoint + } + + if e.Fields.MountPoint != nil && e.Fields.MountPoint.IsPositionAfterEnd(position) && (e.Fields.FilesystemType == nil || e.Fields.FilesystemType.IsPositionBeforeEnd(position)) { + return FstabFieldFileSystemType + } + + if e.Fields.FilesystemType != nil && e.Fields.FilesystemType.IsPositionAfterEnd(position) && (e.Fields.Options == nil || e.Fields.Options.IsPositionBeforeEnd(position)) { + return FstabFieldOptions + } + + if e.Fields.Options != nil && e.Fields.Options.IsPositionAfterEnd(position) && (e.Fields.Freq == nil || e.Fields.Freq.IsPositionBeforeEnd(position)) { + return FstabFieldFreq + } + + if e.Fields.Freq != nil && e.Fields.Freq.IsPositionAfterEnd(position) && (e.Fields.Pass == nil || e.Fields.Pass.IsPositionBeforeEnd(position)) { + return FstabFieldPass + } + + // Okay shit no idea, let's just give whatever is missing + + if e.Fields.Spec == nil { + return FstabFieldSpec + } + + if e.Fields.MountPoint == nil { + return FstabFieldMountPoint + } + + if e.Fields.FilesystemType == nil { + return FstabFieldFileSystemType + } + + if e.Fields.Options == nil { + return FstabFieldOptions + } + + if e.Fields.Freq == nil { + return FstabFieldFreq + } + + return FstabFieldPass +} + +// LABEL=test /mnt/test btrfs subvol=backup,fat=32 [0] [0] +func (e FstabEntry) getCursorIndex() uint8 { + definedAmount := e.getDefinedFieldsAmount() + + switch definedAmount { + case 5: + + } + + return 0 +} + +func (e FstabEntry) getDefinedFieldsAmount() uint8 { + var definedAmount uint8 = 0 + + if e.Fields.Spec != nil { + definedAmount++ + } + if e.Fields.MountPoint != nil { + definedAmount++ + } + if e.Fields.FilesystemType != nil { + definedAmount++ + } + if e.Fields.Options != nil { + definedAmount++ + } + if e.Fields.Freq != nil { + definedAmount++ + } + if e.Fields.Pass != nil { + definedAmount++ + } + + return definedAmount +} + +// Create a mount options field for the entry +func (e FstabEntry) FetchMountOptionsField(includeDefaults bool) docvalues.DeprecatedValue { + if e.Fields.FilesystemType == nil { + return nil + } + + option, found := fields.MountOptionsMapField[e.Fields.FilesystemType.Value.Value] + + if !found { + return nil + } + + var enums []docvalues.EnumString + var assignable map[docvalues.EnumString]docvalues.DeprecatedValue + + if includeDefaults { + enums = append(option.Enums, fields.DefaultOptions...) + assignable = utils.MergeMaps(option.Assignable, fields.DefaultAssignOptions) + } else { + enums = option.Enums + assignable = option.Assignable + } + + return &docvalues.ArrayValue{ + Separator: ",", + DuplicatesExtractor: &fields.MountOptionsExtractor, + SubValue: docvalues.OrValue{ + Values: []docvalues.DeprecatedValue{ + docvalues.KeyEnumAssignmentValue{ + Values: assignable, + ValueIsOptional: false, + Separator: "=", + }, + docvalues.EnumValue{ + EnforceValues: true, + Values: enums, + }, + }, + }, + } +} 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 new file mode 100644 index 0000000..f24a6fa --- /dev/null +++ b/server/handlers/fstab/ast/parser.go @@ -0,0 +1,201 @@ +package ast + +import ( + "config-lsp/common" + commonparser "config-lsp/common/parser" + "config-lsp/handlers/fstab/ast/parser" + "config-lsp/utils" + "regexp" + "strings" + + "github.com/antlr4-go/antlr/v4" + "github.com/emirpasic/gods/maps/treemap" + + gods "github.com/emirpasic/gods/utils" +) + +func NewFstabConfig() *FstabConfig { + config := &FstabConfig{} + config.Clear() + + return config +} + +func (c *FstabConfig) Clear() { + c.Entries = treemap.NewWith(gods.UInt32Comparator) + c.CommentLines = map[uint32]struct{}{} +} + +var commentPattern = regexp.MustCompile(`^\s*#`) +var emptyPattern = regexp.MustCompile(`^\s*$`) +var leadingCommentPattern = regexp.MustCompile(`^(.+?)#`) + +func (c *FstabConfig) Parse(input string) []common.LSPError { + errors := make([]common.LSPError, 0) + lines := utils.SplitIntoLines(input) + context := createListenerContext() + + for rawLineNumber, rawLine := range lines { + line := rawLine + lineNumber := uint32(rawLineNumber) + context.line = lineNumber + + if emptyPattern.MatchString(line) { + continue + } + + if commentPattern.MatchString(line) { + c.CommentLines[lineNumber] = struct{}{} + continue + } + + if strings.Contains(line, "#") { + matches := leadingCommentPattern.FindStringSubmatch(line) + line = matches[1] + } + + errors = append( + errors, + c.parseStatement(context, line)..., + ) + } + + return errors +} + +func (c *FstabConfig) parseStatement( + context *fstabListenerContext, + input string, +) []common.LSPError { + stream := antlr.NewInputStream(input) + + lexerErrorListener := createErrorListener(context.line) + lexer := parser.NewFstabLexer(stream) + lexer.RemoveErrorListeners() + lexer.AddErrorListener(&lexerErrorListener) + + tokenStream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel) + + parserErrorListener := createErrorListener(context.line) + antlrParser := parser.NewFstabParser(tokenStream) + antlrParser.RemoveErrorListeners() + antlrParser.AddErrorListener(&parserErrorListener) + + listener := createListener(c, context) + antlr.ParseTreeWalkerDefault.Walk( + &listener, + antlrParser.Entry(), + ) + + errors := lexerErrorListener.Errors + errors = append(errors, parserErrorListener.Errors...) + errors = append(errors, listener.Errors...) + + return errors +} + +// func (c *FstabConfig) parseStatement( +// line uint32, +// input string, +// ) []common.LSPError { +// fields := whitespacePattern.FindAllStringIndex(input, -1) +// +// if len(fields) == 0 { +// return []common.LSPError{ +// { +// Range: common.LocationRange{ +// Start: common.Location{ +// Line: line, +// Character: 0, +// }, +// End: common.Location{ +// Line: line, +// Character: 0, +// }, +// }, +// }, +// } +// } +// +// var spec *FstabField +// var mountPoint *FstabField +// var filesystemType *FstabField +// var options *FstabField +// var freq *FstabField +// var pass *FstabField +// +// switch len(fields) { +// case 6: +// pass = parseField(line, input, fields[5]) +// fallthrough +// case 5: +// freq = parseField(line, input, fields[4]) +// fallthrough +// case 4: +// options = parseField(line, input, fields[3]) +// fallthrough +// case 3: +// filesystemType = parseField(line, input, fields[2]) +// fallthrough +// case 2: +// mountPoint = parseField(line, input, fields[1]) +// fallthrough +// case 1: +// spec = parseField(line, input, fields[0]) +// } +// +// fstabLine := &FstabEntry{ +// Fields: FstabFields{ +// LocationRange: common.LocationRange{ +// Start: common.Location{ +// Line: line, +// Character: 0, +// }, +// End: common.Location{ +// Line: line, +// Character: uint32(len(input)), +// }, +// }, +// Spec: spec, +// MountPoint: mountPoint, +// FilesystemType: filesystemType, +// Options: options, +// Freq: freq, +// Pass: pass, +// }, +// } +// +// c.Entries.Put(line, fstabLine) +// +// return nil +// } + +func parseField( + line uint32, + input string, + field []int, +) *FstabField { + start := uint32(field[0]) + end := uint32(field[1]) + value := input[start:end] + + return &FstabField{ + LocationRange: common.LocationRange{ + Start: common.Location{ + Line: line, + Character: start, + }, + End: common.Location{ + Line: line, + Character: end, + }, + }, + Value: commonparser.ParseRawString(value, commonparser.ParseFeatures{ + ParseEscapedCharacters: true, + ParseDoubleQuotes: true, + Replacements: &map[string]string{ + `\\040`: " ", + }, + }), + } +} 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 new file mode 100644 index 0000000..ac37136 --- /dev/null +++ b/server/handlers/fstab/ast/parser_test.go @@ -0,0 +1,145 @@ +package ast + +import ( + "config-lsp/common" + "config-lsp/utils" + "testing" +) + +func TestExample1( + t *testing.T, +) { + input := utils.Dedent(` +LABEL=test /mnt/test ext4 defaults 0 0 +LABEL=example /mnt/example fat32 defaults 0 2 +`) + c := NewFstabConfig() + + errors := c.Parse(input) + + if len(errors) > 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + if c.Entries.Size() != 2 { + t.Fatalf("Expected 2 entry, got %d", c.Entries.Size()) + } + + rawFirstEntry, _ := c.Entries.Get(uint32(0)) + firstEntry := rawFirstEntry.(*FstabEntry) + if !(firstEntry.Fields.Spec.Value.Value == "LABEL=test" && firstEntry.Fields.MountPoint.Value.Value == "/mnt/test" && firstEntry.Fields.FilesystemType.Value.Value == "ext4" && firstEntry.Fields.Options.Value.Value == "defaults" && firstEntry.Fields.Freq.Value.Value == "0" && firstEntry.Fields.Pass.Value.Value == "0") { + t.Fatalf("Expected entry to be LABEL=test /mnt/test ext4 defaults 0 0, got %v", firstEntry) + } + + if !(firstEntry.Fields.Spec.LocationRange.Start.Line == 0 && firstEntry.Fields.Spec.LocationRange.Start.Character == 0) { + t.Errorf("Expected spec start to be 0:0, got %v", firstEntry.Fields.Spec.LocationRange.Start) + } + + if !(firstEntry.Fields.Spec.LocationRange.End.Line == 0 && firstEntry.Fields.Spec.LocationRange.End.Character == 10) { + t.Errorf("Expected spec end to be 0:10, got %v", firstEntry.Fields.Spec.LocationRange.End) + } + + if !(firstEntry.Fields.MountPoint.LocationRange.Start.Line == 0 && firstEntry.Fields.MountPoint.LocationRange.Start.Character == 11) { + t.Errorf("Expected mountpoint start to be 0:11, got %v", firstEntry.Fields.MountPoint.LocationRange.Start) + } + + if !(firstEntry.Fields.MountPoint.LocationRange.End.Line == 0 && firstEntry.Fields.MountPoint.LocationRange.End.Character == 20) { + t.Errorf("Expected mountpoint end to be 0:20, got %v", firstEntry.Fields.MountPoint.LocationRange.End) + } + + if !(firstEntry.Fields.FilesystemType.LocationRange.Start.Line == 0 && firstEntry.Fields.FilesystemType.LocationRange.Start.Character == 21) { + t.Errorf("Expected filesystemtype start to be 0:21, got %v", firstEntry.Fields.FilesystemType.LocationRange.Start) + } + + if !(firstEntry.Fields.FilesystemType.LocationRange.End.Line == 0 && firstEntry.Fields.FilesystemType.LocationRange.End.Character == 25) { + t.Errorf("Expected filesystemtype end to be 0:25, got %v", firstEntry.Fields.FilesystemType.LocationRange.End) + } + + if !(firstEntry.Fields.Options.LocationRange.Start.Line == 0 && firstEntry.Fields.Options.LocationRange.Start.Character == 26) { + t.Errorf("Expected options start to be 0:26, got %v", firstEntry.Fields.Options.LocationRange.Start) + } + + if !(firstEntry.Fields.Options.LocationRange.End.Line == 0 && firstEntry.Fields.Options.LocationRange.End.Character == 34) { + t.Errorf("Expected options end to be 0:34, got %v", firstEntry.Fields.Options.LocationRange.End) + } + + if !(firstEntry.Fields.Freq.LocationRange.Start.Line == 0 && firstEntry.Fields.Freq.LocationRange.Start.Character == 35) { + t.Errorf("Expected freq start to be 0:35, got %v", firstEntry.Fields.Freq.LocationRange.Start) + } + + if !(firstEntry.Fields.Freq.LocationRange.End.Line == 0 && firstEntry.Fields.Freq.LocationRange.End.Character == 36) { + t.Errorf("Expected freq end to be 0:36, got %v", firstEntry.Fields.Freq.LocationRange.End) + } + + if !(firstEntry.Fields.Pass.LocationRange.Start.Line == 0 && firstEntry.Fields.Pass.LocationRange.Start.Character == 37) { + t.Errorf("Expected pass start to be 0:37, got %v", firstEntry.Fields.Pass.LocationRange.Start) + } + + field := firstEntry.GetFieldAtPosition(common.IndexPosition(0)) + if !(field == FstabFieldSpec) { + t.Errorf("Expected field to be spec, got %v", field) + } + + field = firstEntry.GetFieldAtPosition(common.IndexPosition(11)) + if !(field == FstabFieldMountPoint) { + t.Errorf("Expected field to be mountpoint, got %v", field) + } + + field = firstEntry.GetFieldAtPosition(common.IndexPosition(33)) + if !(field == FstabFieldOptions) { + t.Errorf("Expected field to be spec, got %v", field) + } + + field = firstEntry.GetFieldAtPosition(common.IndexPosition(35)) + if !(field == FstabFieldFreq) { + t.Errorf("Expected field to be freq, got %v", field) + } + + rawSecondEntry, _ := c.Entries.Get(uint32(1)) + secondEntry := rawSecondEntry.(*FstabEntry) + if !(secondEntry.Fields.Start.Line == 1) { + t.Errorf("Expected start line to be 1, got %d", secondEntry.Fields.Start.Line) + } +} + +func TestIncompleteExample( + t *testing.T, +) { + input := utils.Dedent(` +LABEL=test /mnt/test ext4 defaults +`) + c := NewFstabConfig() + + errors := c.Parse(input) + + if len(errors) != 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + rawFirstEntry, _ := c.Entries.Get(uint32(0)) + firstEntry := rawFirstEntry.(*FstabEntry) + + if !(firstEntry.Fields.Spec.Value.Raw == "LABEL=test" && firstEntry.Fields.MountPoint.Value.Raw == "/mnt/test" && firstEntry.Fields.FilesystemType.Value.Raw == "ext4" && firstEntry.Fields.Options.Value.Raw == "defaults") { + t.Fatalf("Expected entry to be LABEL=test /mnt/test ext4 defaults, got %v", firstEntry) + } + + field := firstEntry.GetFieldAtPosition(common.IndexPosition(0)) + if !(field == FstabFieldSpec) { + t.Errorf("Expected field to be spec, got %v", field) + } + + field = firstEntry.GetFieldAtPosition(common.IndexPosition(11)) + if !(field == FstabFieldMountPoint) { + t.Errorf("Expected field to be mountpoint, got %v", field) + } + + field = firstEntry.GetFieldAtPosition(common.IndexPosition(33)) + if !(field == FstabFieldOptions) { + t.Errorf("Expected field to be spec, got %v", field) + } + + field = firstEntry.GetFieldAtPosition(common.IndexPosition(35)) + if !(field == FstabFieldFreq) { + t.Errorf("Expected field to be freq, got %v", field) + } +} diff --git a/server/handlers/fstab/documentation/documentation-freq.go b/server/handlers/fstab/fields/freq.go similarity index 85% rename from server/handlers/fstab/documentation/documentation-freq.go rename to server/handlers/fstab/fields/freq.go index 5b42b3e..70a98e4 100644 --- a/server/handlers/fstab/documentation/documentation-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/documentation/documentation-mount-point.go b/server/handlers/fstab/fields/mount-point.go similarity index 94% rename from server/handlers/fstab/documentation/documentation-mount-point.go rename to server/handlers/fstab/fields/mount-point.go index 9de4c6d..b11daa6 100644 --- a/server/handlers/fstab/documentation/documentation-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/documentation/documentation-mountoptions.go b/server/handlers/fstab/fields/mountoptions.go similarity index 66% rename from server/handlers/fstab/documentation/documentation-mountoptions.go rename to server/handlers/fstab/fields/mountoptions.go index 191bd3d..2ebf666 100644 --- a/server/handlers/fstab/documentation/documentation-mountoptions.go +++ b/server/handlers/fstab/fields/mountoptions.go @@ -1,4 +1,4 @@ -package fstabdocumentation +package fields import ( commondocumentation "config-lsp/common-documentation" @@ -6,7 +6,7 @@ import ( "strings" ) -var mountOptionsExtractor = func(value string) string { +var MountOptionsExtractor = func(value string) string { separatorIndex := strings.Index(value, "=") if separatorIndex == -1 { @@ -17,7 +17,7 @@ var mountOptionsExtractor = func(value string) string { } // From https://www.man7.org/linux/man-pages/man8/mount.8.html -var defaultOptions = []docvalues.EnumString{ +var DefaultOptions = []docvalues.EnumString{ // Default options docvalues.CreateEnumStringWithDoc( "async", @@ -39,22 +39,6 @@ var defaultOptions = []docvalues.EnumString{ "noauto", "Can only be mounted explicitly (i.e., the -a option will not cause the filesystem to be mounted).", ), - docvalues.CreateEnumStringWithDoc( - "context", - "The context= option is useful when mounting filesystems that do not support extended attributes, such as a floppy or hard disk formatted with VFAT, or systems that are not normally running under SELinux, such as an ext3 or ext4 formatted disk from a non-SELinux workstation. You can also use context= on filesystems you do not trust, such as a floppy. It also helps in compatibility with xattr-supporting filesystems on earlier 2.4. 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.", @@ -193,13 +177,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 +195,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 +203,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 +211,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.`, ), @@ -244,7 +228,7 @@ type assignOption struct { Handler func(context docvalues.KeyValueAssignmentContext) docvalues.DeprecatedValue } -var defaultAssignOptions = map[docvalues.EnumString]docvalues.DeprecatedValue{ +var DefaultAssignOptions = map[docvalues.EnumString]docvalues.DeprecatedValue{ docvalues.CreateEnumStringWithDoc( "context", "The context= option is useful when mounting filesystems that do not support extended attributes, such as a floppy or hard disk formatted with VFAT, or systems that are not normally running under SELinux, such as an ext3 or ext4 formatted disk from a non-SELinux workstation. You can also use context= on filesystems you do not trust, such as a floppy. It also helps in compatibility with xattr-supporting filesystems on earlier 2.4. kernel versions. Even where xattrs are supported, you can save time not having to label every file by assigning the entire disk one security context. A commonly used option for removable media is context=\"system_u:object_r:removable_t\".", @@ -299,19 +283,19 @@ Added in version 245.`, ): docvalues.StringValue{}, docvalues.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 +307,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 +321,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. @@ -345,130 +329,149 @@ See TimeoutSec= below for details. Added in version 233.`, ): docvalues.StringValue{}, + docvalues.CreateEnumStringWithDoc( + "fscontext", + "The fscontext= option works for all filesystems, regardless of their xattr support. The fscontext option sets the overarching filesystem label to a specific security context. This filesystem label is separate from the individual labels on the files. It represents the entire filesystem for certain kinds of permission checks, such as during mount or file creation. Individual file labels are still obtained from the xattrs on the files themselves. The context option actually sets the aggregate context that fscontext provides, in addition to supplying the same label for individual files.", + ): docvalues.StringValue{}, + docvalues.CreateEnumStringWithDoc( + "defcontext", + "You can set the default security context for unlabeled files using defcontext= option. This overrides the value set for unlabeled files in the policy and requires a filesystem that supports xattr labeling.", + ): docvalues.StringValue{}, } func createMountOptionField( options []docvalues.EnumString, assignOption map[docvalues.EnumString]docvalues.DeprecatedValue, ) docvalues.DeprecatedValue { - dynamicOptions := docvalues.MergeKeyEnumAssignmentMaps(defaultAssignOptions, assignOption) + // dynamicOptions := docvalues.MergeKeyEnumAssignmentMaps(defaultAssignOptions, assignOption) return docvalues.ArrayValue{ Separator: ",", - DuplicatesExtractor: &mountOptionsExtractor, + DuplicatesExtractor: &MountOptionsExtractor, SubValue: docvalues.OrValue{ Values: []docvalues.DeprecatedValue{ docvalues.KeyEnumAssignmentValue{ - Values: dynamicOptions, + Values: assignOption, ValueIsOptional: false, Separator: "=", }, docvalues.EnumValue{ EnforceValues: true, - Values: append(defaultOptions, options...), + Values: options, }, }, }, } } -var DefaultMountOptionsField = createMountOptionField([]docvalues.EnumString{}, map[docvalues.EnumString]docvalues.DeprecatedValue{}) - -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/documentation/documentation-pass.go b/server/handlers/fstab/fields/pass.go similarity index 95% rename from server/handlers/fstab/documentation/documentation-pass.go rename to server/handlers/fstab/fields/pass.go index 6163c3b..5306a11 100644 --- a/server/handlers/fstab/documentation/documentation-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/documentation/documentation-spec.go b/server/handlers/fstab/fields/spec.go similarity index 87% rename from server/handlers/fstab/documentation/documentation-spec.go rename to server/handlers/fstab/fields/spec.go index 2cf7db2..df43f6d 100644 --- a/server/handlers/fstab/documentation/documentation-spec.go +++ b/server/handlers/fstab/fields/spec.go @@ -1,4 +1,4 @@ -package fstabdocumentation +package fields import ( docvalues "config-lsp/doc-values" @@ -15,7 +15,7 @@ var LabelField = docvalues.RegexValue{ var SpecField = docvalues.OrValue{ Values: []docvalues.DeprecatedValue{ docvalues.PathValue{ - RequiredType: docvalues.PathTypeFile & docvalues.PathTypeExistenceOptional, + RequiredType: docvalues.PathTypeExistenceOptional, }, docvalues.KeyEnumAssignmentValue{ Separator: "=", diff --git a/server/handlers/fstab/documentation/documentation-type.go b/server/handlers/fstab/fields/type.go similarity index 94% rename from server/handlers/fstab/documentation/documentation-type.go rename to server/handlers/fstab/fields/type.go index 8a6f8f8..b4d0b05 100644 --- a/server/handlers/fstab/documentation/documentation-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 629c690..9d47ecf 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/documentation" + "config-lsp/common" + "config-lsp/handlers/fstab/ast" + fields "config-lsp/handlers/fstab/fields" handlers "config-lsp/handlers/fstab/handlers" - "config-lsp/handlers/fstab/parser" "config-lsp/utils" "testing" ) @@ -12,22 +13,21 @@ func TestValidBasicExample(t *testing.T) { input := utils.Dedent(` LABEL=test /mnt/test ext4 defaults 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { - t.Fatal("ParseFromContent failed with error", errors) + t.Fatal("Parse failed with error", errors) } // Get hover for first field rawEntry, _ := p.Entries.Get(uint32(0)) - entry := rawEntry.(parser.FstabEntry) + entry := rawEntry.(*ast.FstabEntry) println("Getting hover info") { - hover, err := handlers.GetHoverInfo(&entry, uint32(0)) + hover, err := handlers.GetHoverInfo(uint32(0), common.IndexPosition(0), entry) if err != nil { t.Fatal("getHoverInfo failed with error", err) @@ -38,7 +38,7 @@ LABEL=test /mnt/test ext4 defaults 0 0 } // Get hover for second field - hover, err = handlers.GetHoverInfo(&entry, uint32(11)) + hover, err = handlers.GetHoverInfo(uint32(0), common.IndexPosition(11), entry) if err != nil { t.Fatal("getHoverInfo failed with error", err) } @@ -47,20 +47,16 @@ LABEL=test /mnt/test ext4 defaults 0 0 t.Fatal("getHoverInfo failed to return correct hover content. Got:", hover.Contents, "but expected:", handlers.MountPointHoverField.Contents) } - hover, err = handlers.GetHoverInfo(&entry, uint32(20)) + hover, err = handlers.GetHoverInfo(uint32(0), common.IndexPosition(20), entry) if err != nil { t.Fatal("getHoverInfo failed with error", err) } - - if hover.Contents != handlers.MountPointHoverField.Contents { - t.Fatal("getHoverInfo failed to return correct hover content. Got:", hover.Contents, "but expected:", handlers.MountPointHoverField.Contents) - } } println("Getting completions") { - completions, err := handlers.GetCompletion(entry.Line, uint32(0)) + completions, err := handlers.GetCompletion(entry, common.CursorPosition(0)) if err != nil { t.Fatal("getCompletion failed with error", err) @@ -79,49 +75,59 @@ LABEL=test /mnt/test ext4 defaults 0 0 } { - completions, err := handlers.GetCompletion(entry.Line, uint32(21)) + completions, err := handlers.GetCompletion(entry, common.CursorPosition(23)) if err != nil { t.Fatal("getCompletion failed with error", err) } - expectedLength := len(utils.KeysOfMap(fstabdocumentation.MountOptionsMapField)) + expectedLength := len(utils.KeysOfMap(fields.MountOptionsMapField)) if len(completions) != expectedLength { t.Fatal("getCompletion failed to return correct number of completions. Got:", len(completions), "but expected:", expectedLength) } } - - println("Checking values") - { - diagnostics := p.AnalyzeValues() - - if len(diagnostics) > 0 { - t.Fatal("AnalyzeValues failed with error", diagnostics) - } - } } func TestInvalidOptionsExample(t *testing.T) { input := utils.Dedent(` LABEL=test /mnt/test btrfs subvol=backup,fat=32 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) + + if len(errors) > 0 { + t.Fatal("ParseFromContent returned error", errors) + } +} + +func TestIncompleteExample(t *testing.T) { + input := utils.Dedent(` +LABEL=test /mnt/test defaults 0 0 +`) + p := ast.NewFstabConfig() + + errors := p.Parse(input) if len(errors) > 0 { t.Fatal("ParseFromContent returned error", errors) } - // Get hover for first field - println("Checking values") - { - diagnostics := p.AnalyzeValues() + 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) + } - if len(diagnostics) == 0 { - t.Fatal("AnalyzeValues should have returned error") - } + name = firstEntry.GetFieldAtPosition(common.CursorPosition(9)) + if !(name == ast.FstabFieldSpec) { + t.Errorf("GetFieldAtPosition failed to return correct field name. Got: %v but expected: %v", name, ast.FstabFieldSpec) + } + + name = firstEntry.GetFieldAtPosition(common.CursorPosition(21)) + if !(name == ast.FstabFieldFileSystemType) { + t.Errorf("GetFieldAtPosition failed to return correct field name. Got: %v but expected: %v", name, ast.FstabFieldFileSystemType) } } @@ -150,14 +156,32 @@ UUID=0a3407de-014b-458b-b5c1-848e92a327a3 / ext4 defaults 0 1 UUID=f9fe0b69-a280-415d-a03a-a32752370dee none swap defaults 0 0 UUID=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) } + +} + +func TestArchExample1WithComments(t *testing.T) { + input := utils.Dedent(` +# Hello there! +UUID=0a3407de-014b-458b-b5c1-848e92a327a3 / ext4 defaults 0 1 +# How are you? +UUID=f9fe0b69-a280-415d-a03a-a32752370dee none swap defaults 0 0 # And I am trailing! +UUID=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2 # I am tailing too! +`) + p := ast.NewFstabConfig() + + errors := p.Parse(input) + + if len(errors) > 0 { + t.Fatalf("ParseFromContent failed with error %v", errors) + } + } func TestArchExample2(t *testing.T) { @@ -167,10 +191,9 @@ func TestArchExample2(t *testing.T) { /dev/sda3 /home ext4 defaults 0 2 /dev/sda4 none swap defaults 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) @@ -184,14 +207,14 @@ LABEL=System / ext4 defaults 0 1 LABEL=Data /home ext4 defaults 0 2 LABEL=Swap none swap defaults 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) } + } func TestArchExample4(t *testing.T) { @@ -201,10 +224,9 @@ UUID=0a3407de-014b-458b-b5c1-848e92a327a3 / ext4 defaults 0 1 UUID=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2 UUID=f9fe0b69-a280-415d-a03a-a32752370dee none swap defaults 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) @@ -218,10 +240,9 @@ PARTLABEL=GNU/Linux / ext4 defaults 0 1 PARTLABEL=Home /home ext4 defaults 0 2 PARTLABEL=Swap none swap defaults 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) @@ -235,13 +256,74 @@ PARTUUID=98a81274-10f7-40db-872a-03df048df366 / ext4 defaults 0 1 PARTUUID=7280201c-fc5d-40f2-a9b2-466611d3d49e /home ext4 defaults 0 2 PARTUUID=039b6c1c-7553-4455-9537-1befbc9fbc5b none swap defaults 0 0 `) - p := parser.FstabParser{} - p.Clear() + p := ast.NewFstabConfig() - errors := p.ParseFromContent(input) + errors := p.Parse(input) if len(errors) > 0 { t.Fatalf("ParseFromContent failed with error %v", errors) } } +func TestLinuxConfigExample(t *testing.T) { + input := utils.Dedent(` +UUID=80b496fa-ce2d-4dcf-9afc-bcaa731a67f1 /mnt/example ext4 defaults 0 2 +`) + p := ast.NewFstabConfig() + + errors := p.Parse(input) + + if len(errors) > 0 { + t.Fatalf("ParseFromContent failed with error %v", errors) + } +} + +func Test1(t *testing.T) { + input := utils.Dedent(` +PARTLABEL="rootfs" / ext4 noatime,lazytime,rw 0 0 +`) + p := ast.NewFstabConfig() + + errors := p.Parse(input) + + if len(errors) > 0 { + t.Fatalf("ParseFromContent failed with error %v", errors) + } +} + +func Test2(t *testing.T) { + input := utils.Dedent(` +/dev/sda /home1 xfs defaults 1 2 +/dev/sdb /homeB xfs noauto,nobarrier,rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0 +/dev/sdc /homeC xfs noauto,defaults 0 0 +/dev/sdd /homeD xfs noauto,rw,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0 +/dev/sde /homeE xfs defaults 0 0 +`) + p := ast.NewFstabConfig() + + errors := p.Parse(input) + + if len(errors) > 0 { + t.Fatalf("ParseFromContent failed with error %v", errors) + } +} + +func Test3(t *testing.T) { + input := utils.Dedent(` +/dev/disk/by-label/dsp /dsp auto ro +/dev/disk/by-partlabel/modem_a /firmware auto ro +/dev/disk/by-partlabel/persist /persist auto ro,discard,nosuid,nodev,noexec +/dev/disk/by-partlabel/userdata /data auto discard,noatime,nodiratime,nosuid,nodev,nofail 0 0 +/dev/disk/by-partlabel/cache /cache ext4 relatime,data=ordered,noauto_da_alloc,discard,noexec,nodev,nosuid,x-systemd.makefs 0 0 +/dev/nvme0n1 /data/media auto discard,nosuid,nodev,nofail,x-systemd.device-timeout=5s 0 0 +tmpfs /var tmpfs rw,nosuid,nodev,size=128M,mode=755 0 0 +tmpfs /tmp tmpfs rw,nosuid,nodev,size=150M,mode=1777 0 0 +`) + p := ast.NewFstabConfig() + + errors := p.Parse(input) + + if len(errors) > 0 { + t.Fatalf("ParseFromContent failed with error %v", errors) + } +} diff --git a/server/handlers/fstab/handlers/completions.go b/server/handlers/fstab/handlers/completions.go index 35b79e2..c2d00f5 100644 --- a/server/handlers/fstab/handlers/completions.go +++ b/server/handlers/fstab/handlers/completions.go @@ -1,70 +1,93 @@ package handlers import ( - "config-lsp/doc-values" - "config-lsp/handlers/fstab/documentation" - "config-lsp/handlers/fstab/parser" + "config-lsp/common" + "config-lsp/handlers/fstab/ast" + "config-lsp/handlers/fstab/fields" + "fmt" + "github.com/tliron/glsp/protocol_3_16" ) func GetCompletion( - line parser.FstabLine, - cursor uint32, + entry *ast.FstabEntry, + cursor common.CursorPosition, ) ([]protocol.CompletionItem, error) { - targetField := line.GetFieldAtPosition(cursor) + targetField := entry.GetFieldAtPosition(cursor) switch targetField { - case parser.FstabFieldSpec: - value, cursor := GetFieldSafely(line.Fields.Spec, cursor) + case ast.FstabFieldSpec: + value, cursor := getFieldSafely(entry.Fields.Spec, cursor) - return fstabdocumentation.SpecField.DeprecatedFetchCompletions( + return fields.SpecField.DeprecatedFetchCompletions( value, cursor, ), nil - case parser.FstabFieldMountPoint: - value, cursor := GetFieldSafely(line.Fields.MountPoint, cursor) + case ast.FstabFieldMountPoint: + value, cursor := getFieldSafely(entry.Fields.MountPoint, cursor) - return fstabdocumentation.MountPointField.DeprecatedFetchCompletions( + return fields.MountPointField.DeprecatedFetchCompletions( value, cursor, ), nil - case parser.FstabFieldFileSystemType: - value, cursor := GetFieldSafely(line.Fields.FilesystemType, cursor) + case ast.FstabFieldFileSystemType: + value, cursor := getFieldSafely(entry.Fields.FilesystemType, cursor) - return fstabdocumentation.FileSystemTypeField.DeprecatedFetchCompletions( + return fields.FileSystemTypeField.DeprecatedFetchCompletions( value, cursor, ), nil - case parser.FstabFieldOptions: - fileSystemType := line.Fields.FilesystemType.Value + case ast.FstabFieldOptions: + line, cursor := getFieldSafely(entry.Fields.Options, cursor) + fileSystemType := entry.Fields.FilesystemType.Value.Value + completions := make([]protocol.CompletionItem, 0, 50) - var optionsField docvalues.DeprecatedValue + for _, completion := range fields.DefaultMountOptionsField.DeprecatedFetchCompletions(line, cursor) { + var documentation string - if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found { - optionsField = foundField - } else { - optionsField = fstabdocumentation.DefaultMountOptionsField + switch completion.Documentation.(type) { + case string: + documentation = completion.Documentation.(string) + case *string: + documentation = *completion.Documentation.(*string) + } + + completion.Documentation = protocol.MarkupContent{ + Kind: protocol.MarkupKindMarkdown, + Value: documentation + "\n\n" + "From: _Default Mount Options_", + } + completions = append(completions, completion) } - value, cursor := GetFieldSafely(line.Fields.Options, cursor) + for _, completion := range entry.FetchMountOptionsField(false).DeprecatedFetchCompletions(line, cursor) { + var documentation string - completions := optionsField.DeprecatedFetchCompletions( - 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 parser.FstabFieldFreq: - value, cursor := GetFieldSafely(line.Fields.Freq, cursor) + case ast.FstabFieldFreq: + value, cursor := getFieldSafely(entry.Fields.Freq, cursor) - return fstabdocumentation.FreqField.DeprecatedFetchCompletions( + return fields.FreqField.DeprecatedFetchCompletions( value, cursor, ), nil - case parser.FstabFieldPass: - value, cursor := GetFieldSafely(line.Fields.Pass, cursor) + case ast.FstabFieldPass: + value, cursor := getFieldSafely(entry.Fields.Pass, cursor) - return fstabdocumentation.PassField.DeprecatedFetchCompletions( + return fields.PassField.DeprecatedFetchCompletions( value, cursor, ), nil @@ -75,14 +98,18 @@ func GetCompletion( // Safely get value and new cursor position // If field is nil, return empty string and 0 -func GetFieldSafely(field *parser.Field, character uint32) (string, uint32) { +func getFieldSafely(field *ast.FstabField, cursor common.CursorPosition) (string, uint32) { if field == nil { return "", 0 } - if field.Value == "" { + if field.Value.Value == "" { return "", 0 } - return field.Value, character - field.Start + if uint32(cursor) < field.Start.Character { + return "", 0 + } + + return field.Value.Raw, common.CursorToCharacterIndex(uint32(cursor) - field.Start.Character) } diff --git a/server/handlers/fstab/handlers/hover.go b/server/handlers/fstab/handlers/hover.go index 33fd213..0216a62 100644 --- a/server/handlers/fstab/handlers/hover.go +++ b/server/handlers/fstab/handlers/hover.go @@ -1,36 +1,36 @@ package handlers import ( - "config-lsp/doc-values" - "config-lsp/handlers/fstab/documentation" - "config-lsp/handlers/fstab/parser" - "github.com/tliron/glsp/protocol_3_16" + "config-lsp/common" + "config-lsp/handlers/fstab/ast" "strings" + + "github.com/tliron/glsp/protocol_3_16" ) -func GetHoverInfo(entry *parser.FstabEntry, cursor uint32) (*protocol.Hover, error) { - line := entry.Line - targetField := line.GetFieldAtPosition(cursor) +func GetHoverInfo( + line uint32, + index common.IndexPosition, + entry *ast.FstabEntry, +) (*protocol.Hover, error) { + targetField := entry.GetFieldAtPosition(index) switch targetField { - case parser.FstabFieldSpec: + case ast.FstabFieldSpec: return &SpecHoverField, nil - case parser.FstabFieldMountPoint: + case ast.FstabFieldMountPoint: return &MountPointHoverField, nil - case parser.FstabFieldFileSystemType: + case ast.FstabFieldFileSystemType: return &FileSystemTypeField, nil - case parser.FstabFieldOptions: - fileSystemType := line.Fields.FilesystemType.Value - var optionsField docvalues.DeprecatedValue + case ast.FstabFieldOptions: + optionsField := entry.FetchMountOptionsField(true) - if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found { - optionsField = foundField - } else { - optionsField = fstabdocumentation.DefaultMountOptionsField + if optionsField == nil { + return nil, nil } - relativeCursor := cursor - line.Fields.Options.Start - fieldInfo := optionsField.DeprecatedFetchHoverInfo(line.Fields.Options.Value, relativeCursor) + relativeCursor := uint32(index) - entry.Fields.Options.Start.Character + fieldInfo := optionsField.DeprecatedFetchHoverInfo(entry.Fields.Options.Value.Value, relativeCursor) hover := protocol.Hover{ Contents: protocol.MarkupContent{ @@ -40,9 +40,9 @@ func GetHoverInfo(entry *parser.FstabEntry, cursor uint32) (*protocol.Hover, err } return &hover, nil - case parser.FstabFieldFreq: + case ast.FstabFieldFreq: return &FreqHoverField, nil - case parser.FstabFieldPass: + case ast.FstabFieldPass: return &PassHoverField, nil } diff --git a/server/handlers/fstab/lsp/text-document-completion.go b/server/handlers/fstab/lsp/text-document-completion.go index 375fbd2..f2fc9cc 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,10 @@ import ( ) func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) { - p := shared.DocumentParserMap[params.TextDocument.URI] + d := shared.DocumentParserMap[params.TextDocument.URI] + cursor := common.LSPCharacterAsCursorPosition(params.Position.Character) - entry, found := p.GetEntry(params.Position.Line) + rawEntry, found := d.Config.Entries.Get(params.Position.Line) if !found { // Empty line, return spec completions @@ -24,12 +25,7 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa ), nil } - if entry.Type == parser.FstabEntryTypeComment { - return nil, nil - } + entry := rawEntry.(*ast.FstabEntry) - cursor := common.CursorToCharacterIndex(params.Position.Character) - line := entry.Line - - return handlers.GetCompletion(line, cursor) + return handlers.GetCompletion(entry, cursor) } diff --git a/server/handlers/fstab/lsp/text-document-did-change.go b/server/handlers/fstab/lsp/text-document-did-change.go index 2864e19..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,21 +17,21 @@ func TextDocumentDidChange( content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text common.ClearDiagnostics(context, params.TextDocument.URI) - p := shared.DocumentParserMap[params.TextDocument.URI] - p.Clear() + d := shared.DocumentParserMap[params.TextDocument.URI] + d.Config.Clear() diagnostics := make([]protocol.Diagnostic, 0) - errors := p.ParseFromContent(content) + errors := d.Config.Parse(content) if len(errors) > 0 { diagnostics = append(diagnostics, utils.Map( errors, - func(err common.ParseError) protocol.Diagnostic { + func(err common.LSPError) protocol.Diagnostic { return err.ToDiagnostic() }, )...) } else { - diagnostics = append(diagnostics, p.AnalyzeValues()...) + diagnostics = append(diagnostics, analyzer.Analyze(d)...) } if len(diagnostics) > 0 { diff --git a/server/handlers/fstab/lsp/text-document-did-open.go b/server/handlers/fstab/lsp/text-document-did-open.go index 6658b21..1bbe1ae 100644 --- a/server/handlers/fstab/lsp/text-document-did-open.go +++ b/server/handlers/fstab/lsp/text-document-did-open.go @@ -2,9 +2,11 @@ package lsp import ( "config-lsp/common" - "config-lsp/handlers/fstab/parser" + "config-lsp/handlers/fstab/analyzer" + "config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/shared" "config-lsp/utils" + "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) @@ -15,24 +17,26 @@ func TextDocumentDidOpen( ) error { common.ClearDiagnostics(context, params.TextDocument.URI) - p := parser.FstabParser{} - p.Clear() - shared.DocumentParserMap[params.TextDocument.URI] = &p + config := ast.NewFstabConfig() + d := &shared.FstabDocument{ + Config: config, + } + shared.DocumentParserMap[params.TextDocument.URI] = d content := params.TextDocument.Text diagnostics := make([]protocol.Diagnostic, 0) - errors := p.ParseFromContent(content) + errors := d.Config.Parse(content) if len(errors) > 0 { diagnostics = append(diagnostics, utils.Map( errors, - func(err common.ParseError) protocol.Diagnostic { + func(err common.LSPError) protocol.Diagnostic { return err.ToDiagnostic() }, )...) } else { - diagnostics = append(diagnostics, p.AnalyzeValues()...) + diagnostics = append(diagnostics, analyzer.Analyze(d)...) } if len(diagnostics) > 0 { diff --git a/server/handlers/fstab/lsp/text-document-hover.go b/server/handlers/fstab/lsp/text-document-hover.go index 310706e..62c6d6b 100644 --- a/server/handlers/fstab/lsp/text-document-hover.go +++ b/server/handlers/fstab/lsp/text-document-hover.go @@ -1,29 +1,33 @@ package lsp import ( + "config-lsp/common" + "config-lsp/handlers/fstab/ast" "config-lsp/handlers/fstab/handlers" - "config-lsp/handlers/fstab/parser" "config-lsp/handlers/fstab/shared" + "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) func TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) { - cursor := params.Position.Character + line := params.Position.Line + index := common.LSPCharacterAsIndexPosition(params.Position.Character) - p := shared.DocumentParserMap[params.TextDocument.URI] + d := shared.DocumentParserMap[params.TextDocument.URI] - entry, found := p.GetEntry(params.Position.Line) + rawEntry, found := d.Config.Entries.Get(params.Position.Line) // Empty line if !found { return nil, nil } - // Comment line - if entry.Type == parser.FstabEntryTypeComment { - return nil, nil - } + entry := rawEntry.(*ast.FstabEntry) - return handlers.GetHoverInfo(entry, cursor) + return handlers.GetHoverInfo( + line, + index, + entry, + ) } diff --git a/server/handlers/fstab/parser/parser.go b/server/handlers/fstab/parser/parser.go deleted file mode 100644 index a57165a..0000000 --- a/server/handlers/fstab/parser/parser.go +++ /dev/null @@ -1,387 +0,0 @@ -package parser - -import ( - "config-lsp/common" - docvalues "config-lsp/doc-values" - fstabdocumentation "config-lsp/handlers/fstab/documentation" - "fmt" - "regexp" - "strings" - - "github.com/emirpasic/gods/maps/treemap" - protocol "github.com/tliron/glsp/protocol_3_16" - - gods "github.com/emirpasic/gods/utils" -) - -var commentPattern = regexp.MustCompile(`^\s*#`) -var ignoreLinePattern = regexp.MustCompile(`^\s*$`) -var whitespacePattern = regexp.MustCompile(`\S+`) - -type MalformedLineError struct{} - -func (e MalformedLineError) Error() string { - return "Malformed line" -} - -type Field struct { - Value string - Start uint32 - End uint32 -} - -func (f Field) String() string { - return fmt.Sprintf("%v [%v-%v]", f.Value, f.Start, f.End) -} - -func (f *Field) CreateRange(fieldLine uint32) protocol.Range { - return protocol.Range{ - Start: protocol.Position{ - Line: fieldLine, - Character: f.Start, - }, - End: protocol.Position{ - Line: fieldLine, - Character: f.End, - }, - } -} - -type FstabField string - -const ( - FstabFieldSpec FstabField = "spec" - FstabFieldMountPoint FstabField = "mountpoint" - FstabFieldFileSystemType FstabField = "filesystemtype" - FstabFieldOptions FstabField = "options" - FstabFieldFreq FstabField = "freq" - FstabFieldPass FstabField = "pass" -) - -type FstabFields struct { - Spec *Field - MountPoint *Field - FilesystemType *Field - Options *Field - Freq *Field - Pass *Field -} - -func (f FstabFields) String() string { - return fmt.Sprintf( - "Spec: %s, MountPoint: %s, FilesystemType: %s, Options: %s, Freq: %s, Pass: %s", - f.Spec, - f.MountPoint, - f.FilesystemType, - f.Options, - f.Freq, - f.Pass, - ) -} - -type FstabLine struct { - Line uint32 - Fields FstabFields -} - -func (e *FstabLine) CheckIsValid() []protocol.Diagnostic { - diagnostics := make([]protocol.Diagnostic, 0) - - if e.Fields.Spec != nil { - errors := fstabdocumentation.SpecField.DeprecatedCheckIsValid(e.Fields.Spec.Value) - - if len(errors) > 0 { - diagnostics = append( - diagnostics, - docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.Spec.Start, errors)..., - ) - } - } - - if e.Fields.MountPoint != nil { - errors := fstabdocumentation.MountPointField.DeprecatedCheckIsValid(e.Fields.MountPoint.Value) - - if len(errors) > 0 { - diagnostics = append( - diagnostics, - docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.MountPoint.Start, errors)..., - ) - } - } - - var fileSystemType string = "" - - if e.Fields.FilesystemType != nil { - errors := fstabdocumentation.FileSystemTypeField.DeprecatedCheckIsValid(e.Fields.FilesystemType.Value) - - if len(errors) > 0 { - diagnostics = append( - diagnostics, - docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.FilesystemType.Start, errors)..., - ) - } else { - fileSystemType = e.Fields.FilesystemType.Value - } - } - - if e.Fields.Options != nil && fileSystemType != "" { - var optionsField docvalues.DeprecatedValue - - if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found { - optionsField = foundField - } else { - optionsField = fstabdocumentation.DefaultMountOptionsField - } - - errors := optionsField.DeprecatedCheckIsValid(e.Fields.Options.Value) - - if len(errors) > 0 { - diagnostics = append( - diagnostics, - docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.Options.Start, errors)..., - ) - } - } - - if e.Fields.Freq != nil { - errors := fstabdocumentation.FreqField.DeprecatedCheckIsValid(e.Fields.Freq.Value) - - if len(errors) > 0 { - diagnostics = append( - diagnostics, - docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.Freq.Start, errors)..., - ) - } - } - - if e.Fields.Pass != nil { - errors := fstabdocumentation.PassField.DeprecatedCheckIsValid(e.Fields.Pass.Value) - - if len(errors) > 0 { - diagnostics = append( - diagnostics, - docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.Pass.Start, errors)..., - ) - } - } - - return diagnostics -} - -func (e FstabLine) GetFieldAtPosition(cursor uint32) FstabField { - if e.Fields.Spec == nil || (cursor >= e.Fields.Spec.Start && cursor <= e.Fields.Spec.End) { - return FstabFieldSpec - } - - if e.Fields.MountPoint == nil || (cursor >= e.Fields.MountPoint.Start && cursor <= e.Fields.MountPoint.End) { - return FstabFieldMountPoint - } - - if e.Fields.FilesystemType == nil || (cursor >= e.Fields.FilesystemType.Start && cursor <= e.Fields.FilesystemType.End) { - return FstabFieldFileSystemType - } - - if e.Fields.Options == nil || (cursor >= e.Fields.Options.Start && cursor <= e.Fields.Options.End) { - return FstabFieldOptions - } - - if e.Fields.Freq == nil || (cursor >= e.Fields.Freq.Start && cursor <= e.Fields.Freq.End) { - return FstabFieldFreq - } - - return FstabFieldPass -} - -type FstabEntryType string - -const ( - FstabEntryTypeLine FstabEntryType = "line" - FstabEntryTypeComment FstabEntryType = "comment" -) - -type FstabEntry struct { - Type FstabEntryType - Line FstabLine -} - -type FstabParser struct { - // [uint32]FstabEntry - line number to entry mapping - Entries *treemap.Map -} - -func (p *FstabParser) AddLine(line string, lineNumber int) error { - fields := whitespacePattern.FindAllStringIndex(line, -1) - - if len(fields) == 0 { - return MalformedLineError{} - } - - var spec *Field - var mountPoint *Field - var filesystemType *Field - var options *Field - var freq *Field - var pass *Field - - switch len(fields) { - case 6: - field := fields[5] - start := uint32(field[0]) - end := uint32(field[1]) - - pass = &Field{ - Value: line[start:end], - Start: start, - End: end, - } - fallthrough - case 5: - field := fields[4] - start := uint32(field[0]) - end := uint32(field[1]) - - freq = &Field{ - Value: line[start:end], - Start: start, - End: end, - } - fallthrough - case 4: - field := fields[3] - start := uint32(field[0]) - end := uint32(field[1]) - - options = &Field{ - Value: line[start:end], - Start: start, - End: end, - } - fallthrough - case 3: - field := fields[2] - start := uint32(field[0]) - end := uint32(field[1]) - - filesystemType = &Field{ - Value: line[start:end], - Start: start, - End: end, - } - fallthrough - case 2: - field := fields[1] - start := uint32(field[0]) - end := uint32(field[1]) - - mountPoint = &Field{ - Value: line[start:end], - Start: start, - End: end, - } - fallthrough - case 1: - field := fields[0] - start := uint32(field[0]) - end := uint32(field[1]) - - spec = &Field{ - Value: line[start:end], - Start: start, - End: end, - } - } - - entry := FstabEntry{ - Type: FstabEntryTypeLine, - Line: FstabLine{ - Line: uint32(lineNumber), - Fields: FstabFields{ - Spec: spec, - MountPoint: mountPoint, - FilesystemType: filesystemType, - Options: options, - Freq: freq, - Pass: pass, - }, - }, - } - p.Entries.Put(entry.Line.Line, entry) - - return nil -} - -func (p *FstabParser) AddCommentLine(line string, lineNumber int) { - entry := FstabLine{ - Line: uint32(lineNumber), - } - p.Entries.Put(entry.Line, FstabEntry{ - Type: FstabEntryTypeComment, - Line: entry, - }) -} - -func (p *FstabParser) ParseFromContent(content string) []common.ParseError { - errors := []common.ParseError{} - lines := strings.Split(content, "\n") - - for index, line := range lines { - if ignoreLinePattern.MatchString(line) { - continue - } - - if commentPattern.MatchString(line) { - p.AddCommentLine(line, index) - continue - } - - err := p.AddLine(line, index) - - if err != nil { - errors = append(errors, common.ParseError{ - Line: uint32(index), - Err: err, - }) - } - } - - return errors -} - -func (p *FstabParser) GetEntry(line uint32) (*FstabEntry, bool) { - rawEntry, found := p.Entries.Get(line) - - if !found { - return nil, false - } - - entry := rawEntry.(FstabEntry) - - return &entry, true -} - -func (p *FstabParser) Clear() { - p.Entries = treemap.NewWith(gods.UInt32Comparator) -} - -func (p *FstabParser) AnalyzeValues() []protocol.Diagnostic { - diagnostics := []protocol.Diagnostic{} - - it := p.Entries.Iterator() - - for it.Next() { - entry := it.Value().(FstabEntry) - - switch entry.Type { - case FstabEntryTypeLine: - newDiagnostics := entry.Line.CheckIsValid() - - if len(newDiagnostics) > 0 { - diagnostics = append(diagnostics, newDiagnostics...) - } - case FstabEntryTypeComment: - // Do nothing - } - } - - return diagnostics -} diff --git a/server/handlers/fstab/shared/document.go b/server/handlers/fstab/shared/document.go index e25de3b..aeaf132 100644 --- a/server/handlers/fstab/shared/document.go +++ b/server/handlers/fstab/shared/document.go @@ -1,8 +1,13 @@ package shared import ( - "config-lsp/handlers/fstab/parser" + "config-lsp/handlers/fstab/ast" + protocol "github.com/tliron/glsp/protocol_3_16" ) -var DocumentParserMap = map[protocol.DocumentUri]*parser.FstabParser{} +type FstabDocument struct { + Config *ast.FstabConfig +} + +var DocumentParserMap = map[protocol.DocumentUri]*FstabDocument{} 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, + } +} 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 == "" { 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) } } 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