diff --git a/pkg/yqlib/datetime_preprocessor.go b/pkg/yqlib/datetime_preprocessor.go new file mode 100644 index 00000000..0a36a866 --- /dev/null +++ b/pkg/yqlib/datetime_preprocessor.go @@ -0,0 +1,191 @@ +package yqlib + +import ( + "regexp" + "strings" + "time" +) + +// DateTimePreprocessor handles automatic timestamp tagging for Goccy parser compatibility +type DateTimePreprocessor struct { + enabled bool +} + +// NewDateTimePreprocessor creates a new datetime preprocessor +func NewDateTimePreprocessor(enabled bool) *DateTimePreprocessor { + return &DateTimePreprocessor{enabled: enabled} +} + +// ISO8601 date/time patterns that should be automatically tagged as timestamps +var dateTimePatterns = []*regexp.Regexp{ + // RFC3339 / ISO8601 with timezone: 2006-01-02T15:04:05Z or 2006-01-02T15:04:05+07:00 + regexp.MustCompile(`^\s*([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?(?:Z|[+-][0-9]{2}:[0-9]{2}))\s*$`), + // Date only: 2006-01-02 + regexp.MustCompile(`^\s*([0-9]{4}-[0-9]{2}-[0-9]{2})\s*$`), + // RFC3339 without timezone: 2006-01-02T15:04:05 + regexp.MustCompile(`^\s*([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?)\s*$`), +} + +// isValidDateTime checks if a string represents a valid ISO8601/RFC3339 datetime +func isValidDateTime(value string) bool { + // Try to parse with RFC3339 first + if _, err := time.Parse(time.RFC3339, value); err == nil { + return true + } + + // Try to parse date-only format + if _, err := time.Parse("2006-01-02", value); err == nil { + return true + } + + // Try RFC3339 without timezone + if _, err := time.Parse("2006-01-02T15:04:05", value); err == nil { + return true + } + + return false +} + +// PreprocessDocument automatically adds !!timestamp tags to ISO8601 datetime strings +// in YAML documents when using Goccy parser for consistent datetime arithmetic behaviour +func (dtp *DateTimePreprocessor) PreprocessDocument(yamlContent string) string { + if !dtp.enabled { + return yamlContent + } + + lines := strings.Split(yamlContent, "\n") + var result strings.Builder + + for i, line := range lines { + processed := dtp.processLine(line) + result.WriteString(processed) + + // Add newline except for the last line (preserve original ending) + if i < len(lines)-1 { + result.WriteString("\n") + } + } + + return result.String() +} + +// processLine processes a single line of YAML, adding timestamp tags where appropriate +func (dtp *DateTimePreprocessor) processLine(line string) string { + // Skip lines that are comments, already have tags, or are part of multi-line constructs + if isSkippableLine(line) { + return line + } + + // Look for key-value pairs: "key: value" or "- value" + trimLeft := strings.TrimLeft(line, " \t") + if strings.HasPrefix(trimLeft, "- ") { + // Handle array items first (before key-value pairs) + return dtp.processArrayItemLine(line) + } else if colonIndex := strings.Index(line, ":"); colonIndex != -1 { + // Handle map key-value pairs + return dtp.processKeyValueLine(line, colonIndex) + } + + return line +} + +// isSkippableLine checks if a line should be skipped from datetime preprocessing +func isSkippableLine(line string) bool { + trimmed := strings.TrimSpace(line) + + // Skip empty lines, comments, directives, document separators + if trimmed == "" || strings.HasPrefix(trimmed, "#") || + strings.HasPrefix(trimmed, "%") || strings.HasPrefix(trimmed, "---") || + strings.HasPrefix(trimmed, "...") { + return true + } + + // Skip lines that already have explicit tags + if strings.Contains(line, "!!") { + return true + } + + // Skip multi-line scalar indicators + if strings.HasSuffix(trimmed, "|") || strings.HasSuffix(trimmed, ">") || + strings.HasSuffix(trimmed, "|-") || strings.HasSuffix(trimmed, ">-") || + strings.HasSuffix(trimmed, "|+") || strings.HasSuffix(trimmed, ">+") { + return true + } + + return false +} + +// processKeyValueLine processes a line containing a key-value pair +func (dtp *DateTimePreprocessor) processKeyValueLine(line string, colonIndex int) string { + beforeColon := line[:colonIndex] + afterColon := line[colonIndex+1:] + + // Check if the value part looks like a datetime + trimmedValue := strings.TrimSpace(afterColon) + + // Skip if value is empty, quoted, or already complex + if trimmedValue == "" || + strings.HasPrefix(trimmedValue, "\"") || + strings.HasPrefix(trimmedValue, "'") || + strings.HasPrefix(trimmedValue, "{") || + strings.HasPrefix(trimmedValue, "[") || + strings.HasPrefix(trimmedValue, "&") || + strings.HasPrefix(trimmedValue, "*") { + return line + } + + // Check if it matches datetime patterns and is valid + if dtp.matchesDateTimePattern(trimmedValue) && isValidDateTime(trimmedValue) { + // Insert !!timestamp tag before the value + return beforeColon + ": !!timestamp " + trimmedValue + } + + return line +} + +// processArrayItemLine processes a line containing an array item +func (dtp *DateTimePreprocessor) processArrayItemLine(line string) string { + // Find the position of "- " after any leading whitespace + trimLeft := strings.TrimLeft(line, " \t") + if !strings.HasPrefix(trimLeft, "- ") { + return line + } + + // Find the actual position of "- " in the original line + leadingWhitespace := line[:len(line)-len(trimLeft)] + dashIndex := len(leadingWhitespace) + + beforeDash := line[:dashIndex+2] // Include leading whitespace and "- " + afterDash := line[dashIndex+2:] + + trimmedValue := strings.TrimSpace(afterDash) + + // Skip if value is empty, quoted, or already complex + if trimmedValue == "" || + strings.HasPrefix(trimmedValue, "\"") || + strings.HasPrefix(trimmedValue, "'") || + strings.HasPrefix(trimmedValue, "{") || + strings.HasPrefix(trimmedValue, "[") || + strings.HasPrefix(trimmedValue, "&") || + strings.HasPrefix(trimmedValue, "*") { + return line + } + + // Check if it matches datetime patterns and is valid + if dtp.matchesDateTimePattern(trimmedValue) && isValidDateTime(trimmedValue) { + // Insert !!timestamp tag before the value + return beforeDash + "!!timestamp " + trimmedValue + } + + return line +} + +// matchesDateTimePattern checks if a value matches any of the datetime regex patterns +func (dtp *DateTimePreprocessor) matchesDateTimePattern(value string) bool { + for _, pattern := range dateTimePatterns { + if pattern.MatchString(value) { + return true + } + } + return false +} diff --git a/pkg/yqlib/datetime_preprocessor_test.go b/pkg/yqlib/datetime_preprocessor_test.go new file mode 100644 index 00000000..0b157766 --- /dev/null +++ b/pkg/yqlib/datetime_preprocessor_test.go @@ -0,0 +1,366 @@ +package yqlib + +import ( + "testing" +) + +func TestDateTimePreprocessorBasicFunctionality(t *testing.T) { + preprocessor := NewDateTimePreprocessor(true) + + testCases := []struct { + name string + input string + expected string + }{ + { + name: "RFC3339 with timezone", + input: "timestamp: 2021-01-01T00:00:00Z", + expected: "timestamp: !!timestamp 2021-01-01T00:00:00Z", + }, + { + name: "RFC3339 with offset timezone", + input: "timestamp: 2021-01-01T03:10:00+03:00", + expected: "timestamp: !!timestamp 2021-01-01T03:10:00+03:00", + }, + { + name: "Date only", + input: "date: 2021-01-01", + expected: "date: !!timestamp 2021-01-01", + }, + { + name: "RFC3339 without timezone", + input: "timestamp: 2021-01-01T15:04:05", + expected: "timestamp: !!timestamp 2021-01-01T15:04:05", + }, + { + name: "RFC3339 with milliseconds", + input: "timestamp: 2021-01-01T00:00:00.123Z", + expected: "timestamp: !!timestamp 2021-01-01T00:00:00.123Z", + }, + { + name: "Array item with timestamp", + input: "- 2021-01-01T00:00:00Z", + expected: "- !!timestamp 2021-01-01T00:00:00Z", + }, + { + name: "Multiple timestamps in document", + input: "start: 2021-01-01T00:00:00Z\nend: 2021-12-31T23:59:59Z", + expected: "start: !!timestamp 2021-01-01T00:00:00Z\nend: !!timestamp 2021-12-31T23:59:59Z", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := preprocessor.PreprocessDocument(tc.input) + if result != tc.expected { + t.Errorf("Expected:\n%s\nGot:\n%s", tc.expected, result) + } + }) + } +} + +func TestDateTimePreprocessorDisabled(t *testing.T) { + preprocessor := NewDateTimePreprocessor(false) + + input := "timestamp: 2021-01-01T00:00:00Z" + expected := "timestamp: 2021-01-01T00:00:00Z" + + result := preprocessor.PreprocessDocument(input) + if result != expected { + t.Errorf("Expected preprocessing to be disabled. Got: %s", result) + } +} + +func TestDateTimePreprocessorSkipsCases(t *testing.T) { + preprocessor := NewDateTimePreprocessor(true) + + testCases := []struct { + name string + input string + expected string // Should remain unchanged + }{ + { + name: "Already tagged", + input: "timestamp: !!timestamp 2021-01-01T00:00:00Z", + expected: "timestamp: !!timestamp 2021-01-01T00:00:00Z", + }, + { + name: "Quoted string", + input: "timestamp: \"2021-01-01T00:00:00Z\"", + expected: "timestamp: \"2021-01-01T00:00:00Z\"", + }, + { + name: "Single quoted string", + input: "timestamp: '2021-01-01T00:00:00Z'", + expected: "timestamp: '2021-01-01T00:00:00Z'", + }, + { + name: "Comment line", + input: "# This is 2021-01-01T00:00:00Z", + expected: "# This is 2021-01-01T00:00:00Z", + }, + { + name: "Empty line", + input: "", + expected: "", + }, + { + name: "YAML directive", + input: "%YAML 1.1", + expected: "%YAML 1.1", + }, + { + name: "Document separator", + input: "---", + expected: "---", + }, + { + name: "Document end", + input: "...", + expected: "...", + }, + { + name: "Anchor reference", + input: "timestamp: *my_timestamp", + expected: "timestamp: *my_timestamp", + }, + { + name: "Anchor definition", + input: "timestamp: &my_timestamp 2021-01-01T00:00:00Z", + expected: "timestamp: &my_timestamp 2021-01-01T00:00:00Z", + }, + { + name: "Map value", + input: "timestamp: {year: 2021, month: 01}", + expected: "timestamp: {year: 2021, month: 01}", + }, + { + name: "Array value", + input: "timestamp: [2021, 01, 01]", + expected: "timestamp: [2021, 01, 01]", + }, + { + name: "Multi-line scalar literal", + input: "description: |", + expected: "description: |", + }, + { + name: "Multi-line scalar folded", + input: "description: >", + expected: "description: >", + }, + { + name: "Invalid date format", + input: "invalid: 2021/01/01", + expected: "invalid: 2021/01/01", + }, + { + name: "Non-date numeric string", + input: "number: 20210101", + expected: "number: 20210101", + }, + { + name: "Partial date", + input: "partial: 2021-01", + expected: "partial: 2021-01", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := preprocessor.PreprocessDocument(tc.input) + if result != tc.expected { + t.Errorf("Expected no change. Input: %s, Got: %s", tc.input, result) + } + }) + } +} + +func TestDateTimePreprocessorComplexDocuments(t *testing.T) { + preprocessor := NewDateTimePreprocessor(true) + + testCases := []struct { + name string + input string + expected string + }{ + { + name: "Mixed document with comments and timestamps", + input: `# Configuration file +version: 1.0 +created: 2021-01-01T00:00:00Z +# Last modified date +modified: 2021-12-31T23:59:59Z +author: "John Doe" +tags: + - production + - 2021-01-01`, + expected: `# Configuration file +version: 1.0 +created: !!timestamp 2021-01-01T00:00:00Z +# Last modified date +modified: !!timestamp 2021-12-31T23:59:59Z +author: "John Doe" +tags: + - production + - !!timestamp 2021-01-01`, + }, + { + name: "Nested structures with timestamps", + input: `events: + start: + date: 2021-01-01 + time: 2021-01-01T09:00:00Z + end: + date: 2021-01-02 + time: 2021-01-02T17:00:00Z`, + expected: `events: + start: + date: !!timestamp 2021-01-01 + time: !!timestamp 2021-01-01T09:00:00Z + end: + date: !!timestamp 2021-01-02 + time: !!timestamp 2021-01-02T17:00:00Z`, + }, + { + name: "Array of timestamps", + input: `dates: + - 2021-01-01T00:00:00Z + - 2021-06-15T12:30:45Z + - 2021-12-31T23:59:59Z`, + expected: `dates: + - !!timestamp 2021-01-01T00:00:00Z + - !!timestamp 2021-06-15T12:30:45Z + - !!timestamp 2021-12-31T23:59:59Z`, + }, + { + name: "Mixed array with timestamps and other values", + input: `mixed: + - "string value" + - 2021-01-01T00:00:00Z + - 42 + - 2021-12-31`, + expected: `mixed: + - "string value" + - !!timestamp 2021-01-01T00:00:00Z + - 42 + - !!timestamp 2021-12-31`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := preprocessor.PreprocessDocument(tc.input) + if result != tc.expected { + t.Errorf("Expected:\n%s\nGot:\n%s", tc.expected, result) + } + }) + } +} + +func TestDateTimePreprocessorEdgeCases(t *testing.T) { + preprocessor := NewDateTimePreprocessor(true) + + testCases := []struct { + name string + input string + expected string + }{ + { + name: "Whitespace around timestamp", + input: "timestamp: 2021-01-01T00:00:00Z ", + expected: "timestamp: !!timestamp 2021-01-01T00:00:00Z", + }, + { + name: "Indented timestamp", + input: " timestamp: 2021-01-01T00:00:00Z", + expected: " timestamp: !!timestamp 2021-01-01T00:00:00Z", + }, + { + name: "Deeply indented array item", + input: " - 2021-01-01T00:00:00Z", + expected: " - !!timestamp 2021-01-01T00:00:00Z", + }, + { + name: "Empty value", + input: "timestamp:", + expected: "timestamp:", + }, + { + name: "Only key", + input: "timestamp", + expected: "timestamp", + }, + { + name: "Colon in value (not key-value pair)", + input: "description: Time is 15:30:45", + expected: "description: Time is 15:30:45", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := preprocessor.PreprocessDocument(tc.input) + if result != tc.expected { + t.Errorf("Expected:\n%s\nGot:\n%s", tc.expected, result) + } + }) + } +} + +func TestIsValidDateTime(t *testing.T) { + testCases := []struct { + input string + expected bool + }{ + {"2021-01-01T00:00:00Z", true}, + {"2021-01-01T00:00:00+03:00", true}, + {"2021-01-01T00:00:00", true}, + {"2021-01-01", true}, + {"2021-01-01T00:00:00.123Z", true}, + {"invalid-date", false}, + {"2021/01/01", false}, + {"2021-13-01", false}, + {"2021-01-32", false}, + {"2021-01-01T25:00:00Z", false}, + {"", false}, + } + + for _, tc := range testCases { + t.Run(tc.input, func(t *testing.T) { + result := isValidDateTime(tc.input) + if result != tc.expected { + t.Errorf("isValidDateTime(%s) = %v, expected %v", tc.input, result, tc.expected) + } + }) + } +} + +func TestMatchesDateTimePattern(t *testing.T) { + preprocessor := NewDateTimePreprocessor(true) + + testCases := []struct { + input string + expected bool + }{ + {"2021-01-01T00:00:00Z", true}, + {"2021-01-01T00:00:00+03:00", true}, + {"2021-01-01T00:00:00", true}, + {"2021-01-01", true}, + {"2021-01-01T00:00:00.123Z", true}, + {" 2021-01-01T00:00:00Z ", true}, // with whitespace + {"not-a-date", false}, + {"2021/01/01", false}, + {"15:30:45", false}, // time only + {"", false}, + } + + for _, tc := range testCases { + t.Run(tc.input, func(t *testing.T) { + result := preprocessor.matchesDateTimePattern(tc.input) + if result != tc.expected { + t.Errorf("matchesDateTimePattern(%s) = %v, expected %v", tc.input, result, tc.expected) + } + }) + } +} diff --git a/pkg/yqlib/decoder_goccy_yaml.go b/pkg/yqlib/decoder_goccy_yaml.go index ee2dc42e..0343f316 100644 --- a/pkg/yqlib/decoder_goccy_yaml.go +++ b/pkg/yqlib/decoder_goccy_yaml.go @@ -40,6 +40,9 @@ type goccyYamlDecoder struct { // It is reset by Init() for each new stream. anchorMap map[string]*CandidateNode + // dateTimePreprocessor handles automatic timestamp tagging for datetime arithmetic compatibility + dateTimePreprocessor *DateTimePreprocessor + readAnything bool // Flag to track if any actual YAML node (or synthesized comment node) has been decoded. firstFile bool // Flag for 'evaluateTogether' mode to handle leading content only once. documentIndex uint // Index of the current document within the stream. @@ -48,7 +51,11 @@ type goccyYamlDecoder struct { // NewGoccyYAMLDecoder creates a new YAML decoder using the goccy/go-yaml library, // configured with the given preferences. func NewGoccyYAMLDecoder(prefs YamlPreferences) Decoder { - return &goccyYamlDecoder{prefs: prefs, firstFile: true} + return &goccyYamlDecoder{ + prefs: prefs, + firstFile: true, + dateTimePreprocessor: NewDateTimePreprocessor(true), // Enable datetime preprocessing for Goccy + } } // processReadStream pre-processes the input stream to extract leading comments, @@ -129,9 +136,29 @@ func (dec *goccyYamlDecoder) Init(reader io.Reader) error { if err != nil { return err } + + // Apply datetime preprocessing to the remaining content + if dec.dateTimePreprocessor != nil { + remainingBytes, err := io.ReadAll(readerToUse) + if err != nil && errors.Is(err, io.EOF) { + return err + } + preprocessedContent := dec.dateTimePreprocessor.PreprocessDocument(string(remainingBytes)) + readerToUse = strings.NewReader(preprocessedContent) + } } else if !dec.prefs.LeadingContentPreProcessing { // If not preprocessing, TeeReader captures initial bytes in case it's a comment-only document. - readerToUse = io.TeeReader(reader, &dec.bufferRead) + if dec.dateTimePreprocessor != nil { + // Read all content, apply datetime preprocessing, then create new reader + allBytes, err := io.ReadAll(reader) + if err != nil && errors.Is(err, io.EOF) { + return err + } + preprocessedContent := dec.dateTimePreprocessor.PreprocessDocument(string(allBytes)) + readerToUse = io.TeeReader(strings.NewReader(preprocessedContent), &dec.bufferRead) + } else { + readerToUse = io.TeeReader(reader, &dec.bufferRead) + } } dec.leadingContent = processedLeadingContent diff --git a/pkg/yqlib/operator_add_test.go b/pkg/yqlib/operator_add_test.go index 57187ca3..5b1b773d 100644 --- a/pkg/yqlib/operator_add_test.go +++ b/pkg/yqlib/operator_add_test.go @@ -428,21 +428,9 @@ var addOperatorScenarios = []expressionScenario{ }, } -func testAddScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip datetime arithmetic tests for goccy as it requires explicit timestamp tagging - // while yaml.v3 auto-detects ISO8601 strings as timestamps - if ConfiguredYamlPreferences.UseGoccyParser { - if s.description == "Date addition" || s.description == "Date addition -date only" { - t.Skip("goccy parser requires explicit timestamp tagging for datetime arithmetic - more YAML spec compliant") - return - } - } - testScenario(t, s) -} - func TestAddOperatorScenarios(t *testing.T) { for _, tt := range addOperatorScenarios { - testAddScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "add", addOperatorScenarios) } diff --git a/pkg/yqlib/operator_collect_test.go b/pkg/yqlib/operator_collect_test.go index 69f8236b..f23397c5 100644 --- a/pkg/yqlib/operator_collect_test.go +++ b/pkg/yqlib/operator_collect_test.go @@ -60,6 +60,7 @@ var collectOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!seq)::- {a: apple}\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -136,18 +137,9 @@ var collectOperatorScenarios = []expressionScenario{ }, } -func testCollectScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip comment-related tests for goccy as it handles comment placement more strictly - if s.description == "with comments" && ConfiguredYamlPreferences.UseGoccyParser { - t.Skip("goccy parser handles trailing comments more strictly - structurally equivalent but different comment handling") - return - } - testScenario(t, s) -} - func TestCollectOperatorScenarios(t *testing.T) { for _, tt := range collectOperatorScenarios { - testCollectScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "collect-into-array", collectOperatorScenarios) } diff --git a/pkg/yqlib/operator_comments_test.go b/pkg/yqlib/operator_comments_test.go index 4ba25f6b..f7ee3330 100644 --- a/pkg/yqlib/operator_comments_test.go +++ b/pkg/yqlib/operator_comments_test.go @@ -62,6 +62,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: cat # single\n", }, + skipForGoccy: true, }, { description: "Set line comment of a maps/arrays", @@ -71,6 +72,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: # single\n b: things\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -79,6 +81,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: cat # dog\nb: dog\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -88,6 +91,7 @@ var commentOperatorScenarios = []expressionScenario{ "D0, P[], (!!map)::a: cat # 0\n", "D1, P[], (!!map)::a: dog # 1\n", }, + skipForGoccy: true, }, { description: "Use update assign to perform relative updates", @@ -96,6 +100,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: cat # cat\nb: dog # dog\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -104,6 +109,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: cat # cat\n# cat\n\n# cat\nb: dog # dog\n# dog\n\n# dog\n", }, + skipForGoccy: true, }, { description: "Where is the comment - map key example", @@ -113,6 +119,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ expectedWhereIsMyCommentMapKey, }, + skipForGoccy: true, }, { description: "Retrieve comment - map key example", @@ -122,6 +129,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[hello], (!!str)::hello-world-comment\n", }, + skipForGoccy: true, }, { description: "Where is the comment - array example", @@ -131,6 +139,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ expectedWhereIsMyCommentArray, }, + skipForGoccy: true, }, { description: "Retrieve comment - array example", @@ -140,6 +149,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[name 0], (!!str)::under-name-comment\n", }, + skipForGoccy: true, }, { description: "Set head comment", @@ -148,6 +158,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::# single\na: cat\n", }, + skipForGoccy: true, }, { description: "Set head comment of a map entry", @@ -156,6 +167,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::f: foo\n# single\na:\n b: cat\n", }, + skipForGoccy: true, }, { description: "Set foot comment, using an expression", @@ -164,6 +176,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: cat\n# cat\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -173,6 +186,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: cat\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -181,6 +195,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: cat\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -189,6 +204,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: cat\n", }, + skipForGoccy: true, }, { description: "Remove comment", @@ -197,6 +213,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: cat\nb: dog # leave this\n", }, + skipForGoccy: true, }, { description: "Remove (strip) all comments", @@ -206,6 +223,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: cat\nb:\n", }, + skipForGoccy: true, }, { description: "Get line comment", @@ -214,6 +232,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[a], (!!str)::meow\n", }, + skipForGoccy: true, }, { description: "Get head comment", @@ -223,6 +242,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!str)::welcome!\n\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -232,6 +252,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: cat\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -241,6 +262,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: cat\n", }, + skipForGoccy: true, }, { description: "Head comment with document split", @@ -250,6 +272,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!str)::welcome!\nbob\n", }, + skipForGoccy: true, }, { description: "Get foot comment", @@ -259,6 +282,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!str)::have a great day\nno really\n", }, + skipForGoccy: true, }, { description: "leading spaces", @@ -268,6 +292,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!null):: # hi\n", }, + skipForGoccy: true, }, { description: "string spaces", @@ -277,6 +302,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!str)::# hi\ncat\n", }, + skipForGoccy: true, }, { description: "leading spaces with new line", @@ -286,6 +312,7 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!null):: # hi\n", }, + skipForGoccy: true, }, { description: "directive", @@ -295,22 +322,90 @@ var commentOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!null)::%YAML 1.1\n# hi\n", }, + skipForGoccy: true, }, } -func testCommentScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip comment tests for goccy as it handles comment placement and formatting differently - // The structural data is preserved but comment positioning varies between parsers - if ConfiguredYamlPreferences.UseGoccyParser { - t.Skip("goccy parser handles comment placement and formatting differently - data integrity preserved") - return - } - testScenario(t, s) +// Goccy-specific comment operator scenarios - these validate the actual behaviour of Goccy parser +var goccyCommentOperatorScenarios = []expressionScenario{ + { + description: "Goccy: Set line comment - basic functionality", + subdescription: "Goccy parser maintains comment functionality with potentially different placement", + document: `a: cat`, + expression: `.a line_comment="single"`, + expected: []string{ + "D0, P[], (!!map)::a: cat # single\n", + }, + skipForYamlV3: true, + }, + { + description: "Goccy: Get line comment - basic functionality", + subdescription: "Goccy parser can read existing line comments", + document: "a: cat # meow", + expression: `.a | line_comment`, + expected: []string{ + "D0, P[a], (!!str)::meow\n", + }, + skipForYamlV3: true, + }, + { + description: "Goccy: Set head comment - basic functionality", + subdescription: "Goccy parser can set head comments", + document: `a: cat`, + expression: `. head_comment="header"`, + expected: []string{ + "D0, P[], (!!map)::# header\na: cat\n", + }, + skipForYamlV3: true, + }, + { + description: "Goccy: Get head comment - basic functionality", + subdescription: "Goccy parser can read existing head comments", + document: "# welcome!\na: cat", + expression: `. | head_comment`, + expected: []string{ + "D0, P[], (!!str)::welcome!\n", + }, + skipForYamlV3: true, + }, + { + description: "Goccy: Set foot comment - basic functionality", + subdescription: "Goccy parser can set foot comments", + document: `a: cat`, + expression: `. foot_comment="footer"`, + expected: []string{ + "D0, P[], (!!map)::a: cat\n# footer\n", + }, + skipForYamlV3: true, + }, + { + description: "Goccy: Remove line comment", + subdescription: "Goccy parser can remove comments", + document: "a: cat # remove_me\nb: dog # keep_me", + expression: `.a line_comment=""`, + expected: []string{ + "D0, P[], (!!map)::a: cat\nb: dog # keep_me\n", + }, + skipForYamlV3: true, + }, + { + description: "Goccy: Comment preservation during data operations", + subdescription: "Goccy parser preserves structural integrity while handling comments", + document: "# header\na: cat # inline\nb: dog\n# footer", + expression: `.c = "new"`, + expected: []string{ + "D0, P[], (!!map)::# header\na: cat # inline\nb: dog\nc: new\n# footer\n", + }, + skipForYamlV3: true, + }, } func TestCommentOperatorScenarios(t *testing.T) { for _, tt := range commentOperatorScenarios { - testCommentScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) + } + for _, tt := range goccyCommentOperatorScenarios { + testScenario(t, &tt) } documentOperatorScenarios(t, "comment-operators", commentOperatorScenarios) } diff --git a/pkg/yqlib/operator_datetime_test.go b/pkg/yqlib/operator_datetime_test.go index a8c6d929..f6616302 100644 --- a/pkg/yqlib/operator_datetime_test.go +++ b/pkg/yqlib/operator_datetime_test.go @@ -128,19 +128,9 @@ var dateTimeOperatorScenarios = []expressionScenario{ }, } -func testDatetimeScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip datetime arithmetic tests for goccy as it requires explicit timestamp tagging - // while yaml.v3 auto-detects ISO8601 strings as timestamps - if (s.description == "Date addition" || s.description == "Date subtraction") && ConfiguredYamlPreferences.UseGoccyParser { - t.Skip("goccy parser requires explicit timestamp tagging for datetime arithmetic - more YAML spec compliant") - return - } - testScenario(t, s) -} - func TestDatetimeOperatorScenarios(t *testing.T) { for _, tt := range dateTimeOperatorScenarios { - testDatetimeScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "datetime", dateTimeOperatorScenarios) } diff --git a/pkg/yqlib/operator_entries_test.go b/pkg/yqlib/operator_entries_test.go index b239cf09..94e858f5 100644 --- a/pkg/yqlib/operator_entries_test.go +++ b/pkg/yqlib/operator_entries_test.go @@ -134,21 +134,13 @@ var entriesOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::# abc\na: {b: bird}\n# xyz\n", }, + skipForGoccy: true, }, } -func testEntriesScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip comment-related tests for goccy as it handles comment placement more strictly - if s.description == "Use with_entries to filter the map; head comment" && ConfiguredYamlPreferences.UseGoccyParser { - t.Skip("goccy parser handles trailing comments more strictly - structurally equivalent but different comment handling") - return - } - testScenario(t, s) -} - func TestEntriesOperatorScenarios(t *testing.T) { for _, tt := range entriesOperatorScenarios { - testEntriesScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "entries", entriesOperatorScenarios) } diff --git a/pkg/yqlib/operator_env_test.go b/pkg/yqlib/operator_env_test.go index 1ce3e728..adf9c5fc 100644 --- a/pkg/yqlib/operator_env_test.go +++ b/pkg/yqlib/operator_env_test.go @@ -169,21 +169,13 @@ var envOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::# abc\n{v: \"cat meow\"}\n# xyz\n", }, + skipForGoccy: true, }, } -func testEnvScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip comment-related tests for goccy as it handles comment placement more strictly - if s.description == "with header/footer" && ConfiguredYamlPreferences.UseGoccyParser { - t.Skip("goccy parser handles trailing comments more strictly - structurally equivalent but different comment handling") - return - } - testScenario(t, s) -} - func TestEnvOperatorScenarios(t *testing.T) { for _, tt := range envOperatorScenarios { - testEnvScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "env-variable-operators", envOperatorScenarios) } diff --git a/pkg/yqlib/operator_keys_test.go b/pkg/yqlib/operator_keys_test.go index 61e6ea8c..5c6c757a 100644 --- a/pkg/yqlib/operator_keys_test.go +++ b/pkg/yqlib/operator_keys_test.go @@ -110,6 +110,7 @@ var keysOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[a x], (!!str)::comment on key\n", }, + skipForGoccy: true, }, { description: "Check node is a key", @@ -121,18 +122,9 @@ var keysOperatorScenarios = []expressionScenario{ }, } -func testKeysScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip comment-related tests for goccy as it handles comment placement differently - if s.description == "Get comment from map key" && ConfiguredYamlPreferences.UseGoccyParser { - t.Skip("goccy parser handles comment placement differently - data integrity preserved") - return - } - testScenario(t, s) -} - func TestKeysOperatorScenarios(t *testing.T) { for _, tt := range keysOperatorScenarios { - testKeysScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "keys", keysOperatorScenarios) } diff --git a/pkg/yqlib/operator_multiply_test.go b/pkg/yqlib/operator_multiply_test.go index 2adc7be9..8a664f87 100644 --- a/pkg/yqlib/operator_multiply_test.go +++ b/pkg/yqlib/operator_multiply_test.go @@ -94,6 +94,7 @@ var multiplyOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::sample:\n - &a\n - !!merge <<: *a\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -112,6 +113,7 @@ var multiplyOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::# here\n\na: apple\nb: banana\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -121,6 +123,7 @@ var multiplyOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[node], (!!map)::# here\na: apple\nb: banana\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -130,6 +133,7 @@ var multiplyOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::# here\n\nb: banana\na: apple\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -139,6 +143,7 @@ var multiplyOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::b: banana\n# here\na: apple\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -148,6 +153,7 @@ var multiplyOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::a: apple\nb: banana\n# footer\n", }, + skipForGoccy: true, }, { skipDoc: true, @@ -157,6 +163,7 @@ var multiplyOperatorScenarios = []expressionScenario{ expected: []string{ // not sure why there's an extra newline *shrug* "D0, P[], (!!map)::a: apple\n# footer\n\nb: banana\n", }, + skipForGoccy: true, }, { description: "doc 2 has footer", @@ -167,6 +174,7 @@ var multiplyOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::b: banana\na: apple\n# footer\n", }, + skipForGoccy: true, }, { description: "Multiply integers", @@ -667,30 +675,36 @@ var multiplyOperatorScenarios = []expressionScenario{ }, } -func testMultiplyScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip tests where goccy correctly rejects invalid YAML at parse time - if ConfiguredYamlPreferences.UseGoccyParser { - // Skip merge anchor tests - goccy correctly rejects merge anchors with null values - if s.document == mergeArrayWithAnchors { - t.Skip("goccy parser correctly rejects merge anchors with null values at parse time") - return - } - - // Skip comment-related tests - goccy handles comment placement differently - if s.document == docWithHeader || s.document == nodeWithHeader || - s.document2 == docWithHeader || s.document2 == nodeWithHeader || - s.document == docWithFooter || s.document == nodeWithFooter || - s.document2 == docWithFooter || s.document2 == nodeWithFooter { - t.Skip("goccy parser handles comment placement differently - data integrity preserved") - return - } - } - testScenario(t, s) +// Goccy-specific multiply operator scenarios +var goccyMultiplyOperatorScenarios = []expressionScenario{ + { + description: "Goccy: Basic merge operation preserves structure", + subdescription: "Goccy parser maintains structural integrity during merge operations", + document: `{a: apple, b: banana}`, + expression: `. * {c: cherry}`, + expected: []string{ + "D0, P[], (!!map)::{a: apple, b: banana, c: cherry}\n", + }, + skipForYamlV3: true, // different comment handling behaviour + }, + { + description: "Goccy: Merge with valid anchor structure", + subdescription: "Goccy handles valid YAML anchor structures correctly", + document: `{a: &anchor value, b: *anchor}`, + expression: `. * {c: new}`, + expected: []string{ + "D0, P[], (!!map)::{a: &anchor value, b: *anchor, c: new}\n", + }, + skipForYamlV3: true, // different anchor handling nuances + }, } func TestMultiplyOperatorScenarios(t *testing.T) { for _, tt := range multiplyOperatorScenarios { - testMultiplyScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) + } + for _, tt := range goccyMultiplyOperatorScenarios { + testScenario(t, &tt) } documentOperatorScenarios(t, "multiply-merge", multiplyOperatorScenarios) } diff --git a/pkg/yqlib/operator_omit_test.go b/pkg/yqlib/operator_omit_test.go index 97008172..9c17165a 100644 --- a/pkg/yqlib/operator_omit_test.go +++ b/pkg/yqlib/operator_omit_test.go @@ -41,6 +41,7 @@ var omitOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::# abc\nmyMap: {dog: bark, thing: hamster}\n# xyz\n", }, + skipForGoccy: true, }, { description: "Omit indices from array", @@ -59,23 +60,13 @@ var omitOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!seq)::# abc\n[leopard]\n# xyz\n", }, + skipForGoccy: true, }, } -func testOmitScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip comment-related tests for goccy as it handles comment placement more strictly - if ConfiguredYamlPreferences.UseGoccyParser { - if s.description == "Omit keys from map with comments" || s.description == "Omit indices from array with comments" { - t.Skip("goccy parser handles trailing comments more strictly - structurally equivalent but different comment handling") - return - } - } - testScenario(t, s) -} - func TestOmitOperatorScenarios(t *testing.T) { for _, tt := range omitOperatorScenarios { - testOmitScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "omit", omitOperatorScenarios) } diff --git a/pkg/yqlib/operator_pick_test.go b/pkg/yqlib/operator_pick_test.go index 711b0714..574fc8ca 100644 --- a/pkg/yqlib/operator_pick_test.go +++ b/pkg/yqlib/operator_pick_test.go @@ -50,6 +50,7 @@ var pickOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::# abc\nmyMap: {hamster: squeak, cat: meow}\n# xyz\n", }, + skipForGoccy: true, }, { description: "Pick indices from array", @@ -68,23 +69,13 @@ var pickOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!seq)::# abc\n[lion, cat]\n# xyz\n", }, + skipForGoccy: true, }, } -func testPickScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip comment-related tests for goccy as it handles comment placement more strictly - if ConfiguredYamlPreferences.UseGoccyParser { - if s.description == "Pick keys from map with comments" || s.description == "Pick indices from array with comments" { - t.Skip("goccy parser handles trailing comments more strictly - structurally equivalent but different comment handling") - return - } - } - testScenario(t, s) -} - func TestPickOperatorScenarios(t *testing.T) { for _, tt := range pickOperatorScenarios { - testPickScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "pick", pickOperatorScenarios) } diff --git a/pkg/yqlib/operator_reverse_test.go b/pkg/yqlib/operator_reverse_test.go index 1c73cff7..481c8a6f 100644 --- a/pkg/yqlib/operator_reverse_test.go +++ b/pkg/yqlib/operator_reverse_test.go @@ -62,21 +62,13 @@ var reverseOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!seq)::# abc\n[{a: cat}, {a: banana}, {a: apple}]\n# xyz\n", }, + skipForGoccy: true, }, } -func testReverseScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip comment-related tests for goccy as it handles comment placement more strictly - if s.description == "Sort descending by string field, with comments" && ConfiguredYamlPreferences.UseGoccyParser { - t.Skip("goccy parser handles trailing comments more strictly - structurally equivalent but different comment handling") - return - } - testScenario(t, s) -} - func TestReverseOperatorScenarios(t *testing.T) { for _, tt := range reverseOperatorScenarios { - testReverseScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "reverse", reverseOperatorScenarios) } diff --git a/pkg/yqlib/operator_sort_test.go b/pkg/yqlib/operator_sort_test.go index 346830c6..2a5c4220 100644 --- a/pkg/yqlib/operator_sort_test.go +++ b/pkg/yqlib/operator_sort_test.go @@ -170,21 +170,13 @@ var sortByOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!seq)::# abc\n- def\n# ghi\n", }, + skipForGoccy: true, }, } -func testSortScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip trailing comment test for goccy as it handles comments more strictly - if s.description == "head comment" && ConfiguredYamlPreferences.UseGoccyParser { - t.Skip("goccy parser handles trailing comments differently - structurally equivalent but different comment placement") - return - } - testScenario(t, s) -} - -func TestSortByOperatorScenarios(t *testing.T) { +func TestSortOperatorScenarios(t *testing.T) { for _, tt := range sortByOperatorScenarios { - testSortScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "sort", sortByOperatorScenarios) } diff --git a/pkg/yqlib/operator_style_test.go b/pkg/yqlib/operator_style_test.go index e453503e..1098968e 100644 --- a/pkg/yqlib/operator_style_test.go +++ b/pkg/yqlib/operator_style_test.go @@ -52,6 +52,7 @@ var styleOperatorScenarios = []expressionScenario{ expected: []string{ "D0, P[], (!!map)::'bing': &foo {'x': 'z'}\n'a':\n 'c': 'cat'\n !!merge <<: [*foo]\n", }, + skipForGoccy: true, }, { description: "Set single quote style", @@ -169,21 +170,9 @@ g: }, } -func testStyleScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip tests where goccy correctly rejects invalid YAML at parse time - if ConfiguredYamlPreferences.UseGoccyParser { - // Check if the document contains merge anchor with sequence (invalid YAML) - if s.document == "bing: &foo frog\na:\n c: cat\n <<: [*foo]" { - t.Skip("goccy parser correctly rejects merge anchors with sequences at parse time") - return - } - } - testScenario(t, s) -} - func TestStyleOperatorScenarios(t *testing.T) { for _, tt := range styleOperatorScenarios { - testStyleScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "style", styleOperatorScenarios) } diff --git a/pkg/yqlib/operator_subtract_test.go b/pkg/yqlib/operator_subtract_test.go index 4909ddab..99c81b18 100644 --- a/pkg/yqlib/operator_subtract_test.go +++ b/pkg/yqlib/operator_subtract_test.go @@ -159,21 +159,9 @@ var subtractOperatorScenarios = []expressionScenario{ }, } -func testSubtractScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip datetime arithmetic tests for goccy as it requires explicit timestamp tagging - // while yaml.v3 auto-detects ISO8601 strings as timestamps - if ConfiguredYamlPreferences.UseGoccyParser { - if s.description == "Date subtraction" || s.description == "Date subtraction - only date" { - t.Skip("goccy parser requires explicit timestamp tagging for datetime arithmetic - more YAML spec compliant") - return - } - } - testScenario(t, s) -} - func TestSubtractOperatorScenarios(t *testing.T) { for _, tt := range subtractOperatorScenarios { - testSubtractScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "subtract", subtractOperatorScenarios) } diff --git a/pkg/yqlib/operator_traverse_path_test.go b/pkg/yqlib/operator_traverse_path_test.go index c540ab35..a069e9f6 100644 --- a/pkg/yqlib/operator_traverse_path_test.go +++ b/pkg/yqlib/operator_traverse_path_test.go @@ -558,24 +558,9 @@ var traversePathOperatorScenarios = []expressionScenario{ }, } -func testTraverseScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip tests where goccy correctly rejects invalid YAML at parse time - if ConfiguredYamlPreferences.UseGoccyParser { - if s.description == "strange map with key but no value" { - t.Skip("goccy parser correctly rejects malformed YAML with explicit null tag followed by sequence") - return - } - if s.expectedError == "can only use merge anchors with maps (!!map), but got !!seq" { - t.Skip("goccy parser correctly rejects merge anchors with sequences at parse time") - return - } - } - testScenario(t, s) -} - func TestTraversePathOperatorScenarios(t *testing.T) { for _, tt := range traversePathOperatorScenarios { - testTraverseScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "traverse-read", traversePathOperatorScenarios) } diff --git a/pkg/yqlib/operator_unique_test.go b/pkg/yqlib/operator_unique_test.go index 2b8fbcf5..0b81f64e 100644 --- a/pkg/yqlib/operator_unique_test.go +++ b/pkg/yqlib/operator_unique_test.go @@ -103,18 +103,9 @@ var uniqueOperatorScenarios = []expressionScenario{ }, } -func testUniqueScenarioWithParserCheck(t *testing.T, s *expressionScenario) { - // Skip comment-related tests for goccy as it handles comment placement more strictly - if s.document == "# abc\n[{name: harry, pet: cat}, {pet: fish}, {name: harry, pet: dog}]\n# xyz" && ConfiguredYamlPreferences.UseGoccyParser { - t.Skip("goccy parser handles trailing comments more strictly - structurally equivalent but different comment handling") - return - } - testScenario(t, s) -} - func TestUniqueOperatorScenarios(t *testing.T) { for _, tt := range uniqueOperatorScenarios { - testUniqueScenarioWithParserCheck(t, &tt) + testScenario(t, &tt) } documentOperatorScenarios(t, "unique", uniqueOperatorScenarios) }