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: true

Running 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 — clean if: + merged exists: through LoadYAMLWithTags (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_JobIncludedParseOffline of a DrupalCI-style merge-key rule, then rules.Evaluate; job included when if: is true, excluded when false.
  • TestMergeKeyRuleEndToEnd_ExistsExcludesJob — proves the merged exists: 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.

Merge request reports

Loading
Loading