From 2c3eb3308b1793083c39ee3ea35bb80077c53345 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 25 Oct 2026 13:23:09 +0000 Subject: [PATCH] Extract import declaration parsing helpers --- docs/roadmap/implementation.md | 10 ++- internal/engine/import_spec.go | 138 +++++++++++++++++++++------------ 2 files changed, 95 insertions(+), 53 deletions(-) diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index ac15dea..6d47c0c 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,6 +1,6 @@ # Consolidation Implementation Plan -Status: in progress. Stages 1 through 10 are complete; follow Stage 11 through Stage 20 in order. +Status: in progress. Stages 1 through 11 are complete; follow Stage 12 through Stage 20 in order. The target executor is a Luna coding agent working through one end-to-end sprint. ## Authority, Baseline, And Execution @@ -482,7 +482,7 @@ Progress notes (2026-10-25): moved request acquisition and traversal admission t projection to `import_projection.go`. Shared limits, traversal data, and import diagnostics remain in `imports.go`. `./scripts/check.sh` passed. -## Stage 11 — Extract Import Declaration Parsing Operations +## Stage 11 — Extract Import Declaration Parsing Operations (Complete) **Owner:** engine `import_spec.go`. @@ -502,6 +502,12 @@ diagnostics remain in `imports.go`. `./scripts/check.sh` passed. selector rules, default resolution, or diagnostics. Keep behavior tests as the owner; no helper-by-helper assertion inventory is needed. +Progress notes (2026-10-25): extracted boolean assignment, shared wildcard set +assignment, placement parsing, and selector-value parsing into private helpers. +The declaration parser retains its validation ordering, authored member sites, +and nil-versus-empty presence behavior. Import-focused tests and +`./scripts/check.sh` passed. + ## Stage 12 — Extract Directory Entry Admission And Descent **Owner:** engine `import_selection.go`, using existing directory/link helpers. diff --git a/internal/engine/import_spec.go b/internal/engine/import_spec.go index 9fb9005..ef78ab5 100644 --- a/internal/engine/import_spec.go +++ b/internal/engine/import_spec.go @@ -251,40 +251,17 @@ func parseImportDeclarationSpec(node *document.Node, declarationPath Path) (impo } switch entry.Key { case "required", "recursive", "follow", "hidden": - if entry.Value.Kind != document.Boolean { - return importDeclarationSpec{}, []Diagnostic{nodeImportDiagnostic(CodeImportInvalid, fieldPath, entry.Key+" must be a boolean", entry.Value)} - } - value := entry.Value.Text == "true" - switch entry.Key { - case "required": - spec.required = &value - case "recursive": - spec.recursive = &value - case "follow": - spec.follow = &value - case "hidden": - spec.hidden = &value + if diagnostic := parseImportBooleanAssignment(&spec, entry.Key, entry.Value, fieldPath); diagnostic != nil { + return importDeclarationSpec{}, []Diagnostic{*diagnostic} } case "pattern": - values, origins, ok, badIndex := parseStringSet(entry.Value) - if !ok { - return importDeclarationSpec{}, []Diagnostic{stringSetDiagnostic(entry.Value, fieldPath, "pattern", badIndex)} + if diagnostic := parseImportWildcardAssignment(&spec, entry.Key, entry.Value, fieldPath); diagnostic != nil { + return importDeclarationSpec{}, []Diagnostic{*diagnostic} } - if badIndex, ok := invalidWildcard(values); ok { - badPath, badNode := listMemberSite(entry.Value, fieldPath, origins[badIndex]) - return importDeclarationSpec{}, []Diagnostic{nodeImportDiagnostic(CodeImportInvalid, badPath, "pattern contains an invalid wildcard expression", badNode)} - } - spec.pattern, spec.patternSet = values, true case "exclude": - values, origins, ok, badIndex := parseStringSet(entry.Value) - if !ok { - return importDeclarationSpec{}, []Diagnostic{stringSetDiagnostic(entry.Value, fieldPath, "exclude", badIndex)} + if diagnostic := parseImportWildcardAssignment(&spec, entry.Key, entry.Value, fieldPath); diagnostic != nil { + return importDeclarationSpec{}, []Diagnostic{*diagnostic} } - if badIndex, ok := invalidWildcard(values); ok { - badPath, badNode := listMemberSite(entry.Value, fieldPath, origins[badIndex]) - return importDeclarationSpec{}, []Diagnostic{nodeImportDiagnostic(CodeImportInvalid, badPath, "exclude contains an invalid wildcard expression", badNode)} - } - spec.exclude, spec.excludeSet = values, true case "depth": depth, ok := parseImportDepth(entry.Value) if !ok { @@ -298,17 +275,11 @@ func parseImportDeclarationSpec(node *document.Node, declarationPath Path) (impo } spec.destination = destination case "placement": - if entry.Value.Kind != document.String { - return importDeclarationSpec{}, []Diagnostic{nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", entry.Value)} - } - switch entry.Value.Text { - case "mapping": - spec.placement = placementModeMapping - case "sequence-item": - spec.placement = placementModeSequenceItem - default: - return importDeclarationSpec{}, []Diagnostic{nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", entry.Value)} + placement, diagnostic := parseImportPlacement(entry.Value, fieldPath) + if diagnostic != nil { + return importDeclarationSpec{}, []Diagnostic{*diagnostic} } + spec.placement = placement default: return importDeclarationSpec{}, []Diagnostic{nodeImportDiagnostic(CodeImportInvalid, fieldPath, "import declaration field is not recognized", entry.Value)} } @@ -316,33 +287,98 @@ func parseImportDeclarationSpec(node *document.Node, declarationPath Path) (impo selectorPath := declarationPath.Key(selectorEntry.Key) spec.fieldSites[selectorEntry.Key] = importFieldSite{path: selectorPath.clone(), location: publicLocation(selectorEntry.Value.Location)} + if diagnostic := parseImportSelectorValue(&spec, selectorEntry.Key, selectorEntry.Value, selectorPath); diagnostic != nil { + return importDeclarationSpec{}, []Diagnostic{*diagnostic} + } + return spec, nil +} + +func parseImportBooleanAssignment(spec *importDeclarationSpec, field string, node *document.Node, fieldPath Path) *Diagnostic { + if node.Kind != document.Boolean { + diagnostic := nodeImportDiagnostic(CodeImportInvalid, fieldPath, field+" must be a boolean", node) + return &diagnostic + } + value := node.Text == "true" + switch field { + case "required": + spec.required = &value + case "recursive": + spec.recursive = &value + case "follow": + spec.follow = &value + case "hidden": + spec.hidden = &value + } + return nil +} + +func parseImportWildcardAssignment(spec *importDeclarationSpec, field string, node *document.Node, fieldPath Path) *Diagnostic { + values, origins, ok, badIndex := parseStringSet(node) + if !ok { + diagnostic := stringSetDiagnostic(node, fieldPath, field, badIndex) + return &diagnostic + } + if badIndex, ok := invalidWildcard(values); ok { + badPath, badNode := listMemberSite(node, fieldPath, origins[badIndex]) + diagnostic := nodeImportDiagnostic(CodeImportInvalid, badPath, field+" contains an invalid wildcard expression", badNode) + return &diagnostic + } + switch field { + case "pattern": + spec.pattern, spec.patternSet = values, true + case "exclude": + spec.exclude, spec.excludeSet = values, true + } + return nil +} + +func parseImportPlacement(node *document.Node, fieldPath Path) (placementMode, *Diagnostic) { + if node.Kind != document.String { + diagnostic := nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", node) + return placementModeMapping, &diagnostic + } + switch node.Text { + case "mapping": + return placementModeMapping, nil + case "sequence-item": + return placementModeSequenceItem, nil + default: + diagnostic := nodeImportDiagnostic(CodeImportInvalid, fieldPath, "placement must be mapping or sequence-item", node) + return placementModeMapping, &diagnostic + } +} + +func parseImportSelectorValue(spec *importDeclarationSpec, field string, node *document.Node, selectorPath Path) *Diagnostic { switch spec.selector { case importSelectorPath, importSelectorDirectory: - values, origins, ok, badIndex := parseStringSet(selectorEntry.Value) + values, origins, ok, badIndex := parseStringSet(node) if !ok { - name := selectorEntry.Key - return importDeclarationSpec{}, []Diagnostic{stringSetDiagnostic(selectorEntry.Value, selectorPath, name, badIndex)} + diagnostic := stringSetDiagnostic(node, selectorPath, field, badIndex) + return &diagnostic } for index, value := range values { if !validImportPath(value) { - badPath, badNode := listMemberSite(selectorEntry.Value, selectorPath, origins[index]) - return importDeclarationSpec{}, []Diagnostic{nodeImportDiagnostic(CodeImportInvalid, badPath, selectorEntry.Key+" contains an invalid source path", badNode)} + badPath, badNode := listMemberSite(node, selectorPath, origins[index]) + diagnostic := nodeImportDiagnostic(CodeImportInvalid, badPath, field+" contains an invalid source path", badNode) + return &diagnostic } - badPath, badNode := listMemberSite(selectorEntry.Value, selectorPath, origins[index]) + badPath, badNode := listMemberSite(node, selectorPath, origins[index]) spec.selectorSites = append(spec.selectorSites, importFieldSite{path: badPath.clone(), location: publicLocation(badNode.Location)}) } spec.paths = values case importSelectorGlob: - if selectorEntry.Value.Kind != document.String || !validImportPath(selectorEntry.Value.Text) { - return importDeclarationSpec{}, []Diagnostic{nodeImportDiagnostic(CodeImportInvalid, selectorPath, "glob must be a valid nonempty path pattern string", selectorEntry.Value)} + if node.Kind != document.String || !validImportPath(node.Text) { + diagnostic := nodeImportDiagnostic(CodeImportInvalid, selectorPath, "glob must be a valid nonempty path pattern string", node) + return &diagnostic } - segments, ok := validGlobPattern(selectorEntry.Value.Text) + segments, ok := validGlobPattern(node.Text) if !ok { - return importDeclarationSpec{}, []Diagnostic{nodeImportDiagnostic(CodeImportInvalid, selectorPath, "glob pattern is invalid", selectorEntry.Value)} + diagnostic := nodeImportDiagnostic(CodeImportInvalid, selectorPath, "glob pattern is invalid", node) + return &diagnostic } - spec.glob, spec.globSegments = selectorEntry.Value.Text, segments + spec.glob, spec.globSegments = node.Text, segments } - return spec, nil + return nil } func selectorForField(field string) importSelector {