Skip to content

Commit cea0d4f

Browse files
committed
Add YAML anchor and alias support for GitHub Actions workflows
Implements two-pass YAML parsing to resolve anchors (&anchor) and aliases (*alias) before linting, matching GitHub Actions' native YAML support. - Add containsAnchorsOrAliases() to detect anchor usage - Add resolveYAMLAnchors() using unmarshal/marshal strategy - Modify Parse() to resolve anchors when present - Add comprehensive test suite covering various anchor scenarios - Preserve backward compatibility for workflows without anchors - Maintain error detection and line number reporting after resolution Fixes workflows that previously failed with "alias node but mapping node is expected" errors when using YAML anchors supported by GitHub Actions.
1 parent 2ab3a12 commit cea0d4f

File tree

6 files changed

+717
-62
lines changed

6 files changed

+717
-62
lines changed

parse.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1390,6 +1390,33 @@ func (p *parser) parse(n *yaml.Node) *Workflow {
13901390
// }
13911391
// }
13921392

1393+
// containsAnchorsOrAliases recursively checks if a YAML node tree contains any anchors or aliases
1394+
func containsAnchorsOrAliases(node *yaml.Node) bool {
1395+
if node.Anchor != "" || node.Alias != nil {
1396+
return true
1397+
}
1398+
1399+
for _, child := range node.Content {
1400+
if containsAnchorsOrAliases(child) {
1401+
return true
1402+
}
1403+
}
1404+
1405+
return false
1406+
}
1407+
1408+
// resolveYAMLAnchors resolves YAML anchors by using the two-pass strategy:
1409+
// 1. Parse into interface{} to let yaml.v3 resolve anchors automatically
1410+
// 2. Marshal back to YAML to get the resolved structure
1411+
func resolveYAMLAnchors(b []byte) ([]byte, error) {
1412+
var data interface{}
1413+
if err := yaml.Unmarshal(b, &data); err != nil {
1414+
return nil, err
1415+
}
1416+
1417+
return yaml.Marshal(data)
1418+
}
1419+
13931420
func handleYAMLError(err error) []*Error {
13941421
re := regexp.MustCompile(`\bline (\d+):`)
13951422

@@ -1417,12 +1444,32 @@ func handleYAMLError(err error) []*Error {
14171444
// detected while parsing the input. It means that detecting one error does not stop parsing. Even
14181445
// if one or more errors are detected, parser will try to continue parsing and finding more errors.
14191446
func Parse(b []byte) (*Workflow, []*Error) {
1447+
// First, check if the YAML contains anchors/aliases
14201448
var n yaml.Node
1421-
14221449
if err := yaml.Unmarshal(b, &n); err != nil {
14231450
return nil, handleYAMLError(err)
14241451
}
14251452

1453+
// If the YAML contains anchors or aliases, resolve them
1454+
if containsAnchorsOrAliases(&n) {
1455+
resolvedBytes, err := resolveYAMLAnchors(b)
1456+
if err != nil {
1457+
return nil, []*Error{{
1458+
Message: fmt.Sprintf("could not resolve YAML anchors: %s", err.Error()),
1459+
Filepath: "",
1460+
Line: 1,
1461+
Column: 1,
1462+
Kind: "syntax-check",
1463+
}}
1464+
}
1465+
b = resolvedBytes
1466+
1467+
// Re-parse the resolved YAML
1468+
if err := yaml.Unmarshal(b, &n); err != nil {
1469+
return nil, handleYAMLError(err)
1470+
}
1471+
}
1472+
14261473
// Uncomment for checking YAML tree
14271474
// dumpYAML(&n, 0)
14281475

0 commit comments

Comments
 (0)