diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index dd8778e..4e04407 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,6 +1,6 @@ # Import Discovery And Facade Simplification Implementation Plan -Status: not started. +Status: in progress. Baseline: `bb5540b62e382106d59678e2edb7655acfd5c960`. Audience: a Luna coding agent implementing the stages sequentially. @@ -76,7 +76,8 @@ acceptance criteria. ## Stage 1 — Extract Patterned Glob Entry Preparation Finding: **C03**. Owners: `internal/engine/glob.go`, existing glob selection, -link, and directory failure tests. Status: **not started**. +link, and directory failure tests. Status: **complete**. Validation passed: +`env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./internal/engine -run 'Glob|Directory|Link|Filesystem|Resource'` and `git diff --check`. 1. Add `prepareGlobEntry` beside the selector. Take the filesystem access, base path, parent `selectedImportCandidate`, `fs.DirEntry`, pattern segment, diff --git a/internal/engine/glob.go b/internal/engine/glob.go index 76a74dc..e7f3002 100644 --- a/internal/engine/glob.go +++ b/internal/engine/glob.go @@ -56,45 +56,13 @@ func selectGlobImportCandidates(access *filesystemAccess, basePath string, spec continue } for _, entry := range entries { - matched, err := path.Match(segment, entry.Name()) - if err != nil { - return importSelection{}, []Diagnostic{*importSelectionDiagnostic(site, CodeImportInvalid, "import glob pattern is invalid")} + candidate, selected, diagnostic := prepareGlobEntry(access, basePath, parent, entry, segment, last, policy) + if diagnostic != nil { + return importSelection{}, []Diagnostic{*diagnostic} } - if !matched || !policy.hidden && strings.HasPrefix(entry.Name(), ".") { - continue + if selected { + nextByPath[candidate.path] = candidate } - accessPath := access.join(parent.accessPath, entry.Name()) - lexicalPath := access.join(parent.path, entry.Name()) - if !access.contains(accessPath) || !access.contains(lexicalPath) { - return importSelection{}, []Diagnostic{*importSelectionDiagnostic(site, CodeImportOutsideRoot, "glob traversal is outside the configuration root")} - } - target, err := access.importEntryTarget(accessPath, entry, policy.follow) - if err != nil { - return importSelection{}, []Diagnostic{globCandidateDiagnostic(err, site.location, "glob match could not be inspected")} - } - if target.symlink && !policy.follow { - continue - } - if !last && !target.dir { - continue - } - physicalPath := target.identity - // Native source identity and relative imports use the namespace - // spelling; resolved link identities belong only to discovery. - if access.native != nil { - physicalPath = accessPath - } - if physicalPath == "" { - physicalPath, err = access.canonical(accessPath) - if err != nil { - return importSelection{}, []Diagnostic{globFilesystemDiagnostic(err, "glob match could not be canonicalized", site.location)} - } - } - candidate := selectedImportCandidate{ - path: normalizedImportCandidatePath(access, lexicalPath), accessPath: physicalPath, - label: relativeImportLabel(access, basePath, lexicalPath), site: site, - } - nextByPath[candidate.path] = candidate } } candidates = candidatesFromMap(nextByPath) @@ -123,6 +91,50 @@ func selectGlobImportCandidates(access *filesystemAccess, basePath string, spec return selection, nil } +func prepareGlobEntry(access *filesystemAccess, basePath string, parent selectedImportCandidate, entry fs.DirEntry, pattern string, last bool, policy resolvedImportPolicy) (selectedImportCandidate, bool, *Diagnostic) { + name := entry.Name() + matched, err := path.Match(pattern, name) + if err != nil { + return selectedImportCandidate{}, false, importSelectionDiagnostic(parent.site, CodeImportInvalid, "import glob pattern is invalid") + } + if !matched || !policy.hidden && strings.HasPrefix(name, ".") { + return selectedImportCandidate{}, false, nil + } + + accessPath := access.join(parent.accessPath, name) + lexicalPath := access.join(parent.path, name) + if !access.contains(accessPath) || !access.contains(lexicalPath) { + return selectedImportCandidate{}, false, importSelectionDiagnostic(parent.site, CodeImportOutsideRoot, "glob traversal is outside the configuration root") + } + target, err := access.importEntryTarget(accessPath, entry, policy.follow) + if err != nil { + diagnostic := globCandidateDiagnostic(err, parent.site.location, "glob match could not be inspected") + return selectedImportCandidate{}, false, &diagnostic + } + if target.symlink && !policy.follow || !last && !target.dir { + return selectedImportCandidate{}, false, nil + } + + physicalPath := target.identity + // Native source identity and relative imports use the namespace spelling; + // resolved link identities belong only to discovery. + if access.native != nil { + physicalPath = accessPath + } + if physicalPath == "" { + physicalPath, err = access.canonical(accessPath) + if err != nil { + diagnostic := globFilesystemDiagnostic(err, "glob match could not be canonicalized", parent.site.location) + return selectedImportCandidate{}, false, &diagnostic + } + } + + return selectedImportCandidate{ + path: normalizedImportCandidatePath(access, lexicalPath), accessPath: physicalPath, + label: relativeImportLabel(access, basePath, lexicalPath), site: parent.site, + }, true, nil +} + func explicitGlobCandidate(access *filesystemAccess, basePath, candidatePath, accessPath string, site importFieldSite, last bool) (selectedImportCandidate, bool, []Diagnostic) { if !access.contains(candidatePath) || !access.contains(accessPath) { return selectedImportCandidate{}, false, []Diagnostic{*importSelectionDiagnostic(site, CodeImportOutsideRoot, "glob traversal is outside the configuration root")}