Fix YAML merge keys (<<:) being folded into rule if: expressions
Closes #93 (closed)
Claude code was used here.
What
Fixes a YAML sanitizer bug where a merge key (<<: *anchor) on the line after a quoted multi-line if: scalar was folded into the scalar, losing the merge key and producing an unparseable if: expression. Affected jobs were then silently dropped.
Why it matters
The current DrupalCI gitlab_templates (the default-ref / 1.15.x line) use exactly this pattern throughout their rules::
phpcs:
rules:
- if: $_PHPCS_ALLOW_FAILURE == "1" || ($_ALL_VALIDATE_ALLOW_FAILURE == "1" && $_PHPCS_ALLOW_FAILURE != "0")
<<: *php-files-exist
allow_failure: trueRunning any Drupal contrib project through glci dropped phpcs, phpstan, stylelint, etc. with:
glci: warning: could not evaluate rule if: "... != \"0\") <<: *php-files-exist": unexpected trailing input: "<<: *php-files-exist"Root cause
sanitizeYAML quotes unquoted plain scalars containing && / ** so yaml.v3 doesn't misread the bare &/* as anchor/alias indicators. When such a scalar spans multiple lines it collects continuation lines until the next key. Its key-detector regex (yamlKeyLineRe) only matched keys starting with a letter, so the YAML merge key <<: was collected as a continuation of the if: scalar. Stock yaml.v3 resolves these merge keys correctly; only glci's pre-parse sanitizer mishandled them.
Fix
One alternative added to yamlKeyLineRe so <<: is recognized as a line-terminating key:
^[ \t]*(?:-[ \t]+)?(?:<<|[A-Za-z_.][A-Za-z0-9_.-]{0,200}):(?:[ \t]|$)After the fix the merge key terminates the scalar, the anchor's keys (exists:, changes:, …) merge into the rule, and the jobs evaluate correctly. Verified against a real Drupal contrib pipeline: the warnings disappear and the dropped validate-stage jobs reappear.
Tests
Unit (pkg/config/sanitize_test.go):
TestSanitizeYAML_MergeKeyAfterQuotedScalar— cleanif:+ mergedexists:throughLoadYAMLWithTags(the real loader path).TestSanitizeYAML_ContinuationTerminatedByMergeKey— string-in/string-out: the<<:line is preserved, incl. a bare<<:at end of line and tab indentation.TestSanitizeYAML_MergeKeySequenceAfterQuotedScalar— merge key referencing a sequence of anchors (<<: [*a, *b]).
End-to-end (pkg/rules/evaluator_test.go):
TestMergeKeyRuleEndToEnd_JobIncluded—ParseOfflineof a DrupalCI-style merge-key rule, thenrules.Evaluate; job included whenif:is true, excluded when false.TestMergeKeyRuleEndToEnd_ExistsExcludesJob— proves the mergedexists:is actually evaluated (not always-true).TestMergeKeyRuleEndToEnd_MergeKeyOnlyRule— rule whose only key is<<: *anchor.
go build ./..., go vet ./..., make test, and make test-e2e-parse all pass.