package engine import ( "errors" "io/fs" "path" "path/filepath" "sort" ) type importSelectionOutcome uint8 const ( importSelectionNone importSelectionOutcome = iota importSelectionHasCandidates ) type selectedImportCandidate struct { path string accessPath string site importFieldSite } // importSelection contains lexical candidates only; admission assigns source identity later. type importSelection struct { candidates []selectedImportCandidate outcome importSelectionOutcome } func selectNamedImportCandidates(access *filesystemAccess, basePath string, spec importDeclarationSpec, policy resolvedImportPolicy) (importSelection, []Diagnostic) { normalized := make([]selectedImportCandidate, 0, len(spec.paths)) seen := make(map[string]struct{}, len(spec.paths)) for index, relative := range spec.paths { accessPath := access.join(basePath, relative) candidatePath := accessPath if access.native == nil { candidatePath = filepath.ToSlash(filepath.Clean(accessPath)) } if _, exists := seen[candidatePath]; exists { continue } seen[candidatePath] = struct{}{} var site importFieldSite if index < len(spec.selectorSites) { site = spec.selectorSites[index] } else { site = spec.fieldSites["path"] } normalized = append(normalized, selectedImportCandidate{path: candidatePath, accessPath: accessPath, site: site}) } sort.Slice(normalized, func(left, right int) bool { return normalized[left].path < normalized[right].path }) candidates := make([]selectedImportCandidate, 0, len(normalized)) for _, candidate := range normalized { missing, _, diagnostic := validateNamedImportTarget(access, candidate, policy.required, false) if diagnostic != nil { return importSelection{}, []Diagnostic{*diagnostic} } if missing { continue } if excludedBasename(path.Base(candidate.path), policy.exclude) { continue } candidates = append(candidates, candidate) } selection := importSelection{candidates: candidates} if len(candidates) != 0 { selection.outcome = importSelectionHasCandidates } return selection, nil } func selectShallowDirectoryImportCandidates(access *filesystemAccess, basePath string, spec importDeclarationSpec, policy resolvedImportPolicy, limits importLimits, accounting *importAccounting) (importSelection, []Diagnostic) { roots := make([]selectedImportCandidate, 0, len(spec.paths)) seenRoots := make(map[string]struct{}, len(spec.paths)) for index, relative := range spec.paths { accessPath := access.join(basePath, relative) candidatePath := normalizedImportCandidatePath(access, accessPath) if _, exists := seenRoots[candidatePath]; exists { continue } seenRoots[candidatePath] = struct{}{} site := spec.fieldSites["dir"] if index < len(spec.selectorSites) { site = spec.selectorSites[index] } roots = append(roots, selectedImportCandidate{path: candidatePath, accessPath: accessPath, site: site}) } sort.Slice(roots, func(left, right int) bool { return roots[left].path < roots[right].path }) if accounting == nil { accounting = &importAccounting{} } discovered := make([]selectedImportCandidate, 0) seenCandidates := make(map[string]struct{}) for _, root := range roots { missing, isDirectory, diagnostic := validateNamedImportTarget(access, root, false, true) if diagnostic != nil { return importSelection{}, []Diagnostic{*diagnostic} } if missing { continue } if !isDirectory { return importSelection{}, []Diagnostic{*importSelectionDiagnostic(root.site, CodeImportInvalid, "directory search root is not a directory")} } entries := make([]fs.DirEntry, 0) var problem *Diagnostic err := access.readDirectory(root.accessPath, func(entry fs.DirEntry) bool { accounting.entries++ if accounting.entries > limits.entries { problem = importSelectionDiagnostic(root.site, CodeImportLimitExceeded, "directory search exceeds the directory-entry visit limit") return false } entries = append(entries, entry) return true }) if problem != nil { return importSelection{}, []Diagnostic{*problem} } if errors.Is(err, fs.ErrNotExist) { continue } if err != nil { return importSelection{}, []Diagnostic{*importSelectionDiagnostic(root.site, CodeImportInvalid, "directory search root could not be read")} } sort.Slice(entries, func(left, right int) bool { return entries[left].Name() < entries[right].Name() }) for _, entry := range entries { if entry.IsDir() || !matchesImportFilename(entry.Name(), policy.pattern) { continue } accessPath := access.join(root.accessPath, entry.Name()) candidate := selectedImportCandidate{ path: normalizedImportCandidatePath(access, accessPath), accessPath: accessPath, site: root.site, } if _, exists := seenCandidates[candidate.path]; exists { continue } seenCandidates[candidate.path] = struct{}{} discovered = append(discovered, candidate) } } sort.Slice(discovered, func(left, right int) bool { return discovered[left].path < discovered[right].path }) selected := make([]selectedImportCandidate, 0, len(discovered)) for _, candidate := range discovered { missing, isDirectory, diagnostic := validateNamedImportTarget(access, candidate, false, true) if diagnostic != nil { return importSelection{}, []Diagnostic{*diagnostic} } if missing || isDirectory || excludedBasename(path.Base(candidate.path), policy.exclude) { continue } selected = append(selected, candidate) } if policy.required && len(selected) == 0 { return importSelection{}, []Diagnostic{*importSelectionDiagnostic(spec.fieldSites["dir"], CodeImportMissing, "required directory import selected no files")} } selection := importSelection{candidates: selected} if len(selected) != 0 { selection.outcome = importSelectionHasCandidates } return selection, nil } func normalizedImportCandidatePath(access *filesystemAccess, accessPath string) string { if access.native != nil { return accessPath } return filepath.ToSlash(filepath.Clean(accessPath)) } func matchesImportFilename(name string, patterns []string) bool { if patterns == nil { return true } for _, pattern := range patterns { matched, err := path.Match(pattern, name) if err == nil && matched { return true } } return false } func validateNamedImportTarget(access *filesystemAccess, candidate selectedImportCandidate, required, allowDirectory bool) (bool, bool, *Diagnostic) { if !access.contains(candidate.accessPath) { return false, false, importSelectionDiagnostic(candidate.site, CodeImportOutsideRoot, "import target is outside the configuration root") } canonical, err := access.canonical(candidate.accessPath) if err != nil { if errors.Is(err, fs.ErrNotExist) { if required { return false, false, importSelectionDiagnostic(candidate.site, CodeImportMissing, "required import target does not exist") } return true, false, nil } if errors.Is(err, fs.ErrPermission) { return false, false, importSelectionDiagnostic(candidate.site, CodeImportInvalid, "import target cannot be accessed") } return false, false, importSelectionDiagnostic(candidate.site, CodeImportInvalid, "import target could not be canonicalized") } if !access.contains(canonical) { return false, false, importSelectionDiagnostic(candidate.site, CodeImportOutsideRoot, "import target resolves outside the configuration root") } info, err := access.stat(canonical) if err != nil { if errors.Is(err, fs.ErrNotExist) { if required { return false, false, importSelectionDiagnostic(candidate.site, CodeImportMissing, "required import target does not exist") } return true, false, nil } if errors.Is(err, fs.ErrPermission) { return false, false, importSelectionDiagnostic(candidate.site, CodeImportInvalid, "import target cannot be accessed") } return false, false, importSelectionDiagnostic(candidate.site, CodeImportInvalid, "import target could not be inspected") } if info.IsDir() && allowDirectory { return false, true, nil } if !info.Mode().IsRegular() { return false, false, importSelectionDiagnostic(candidate.site, CodeSourceNotRegular, "source is not a regular file") } return false, false, nil } func excludedBasename(name string, patterns []string) bool { for _, pattern := range patterns { matched, err := path.Match(pattern, name) if err == nil && matched { return true } } return false } func importSelectionDiagnostic(site importFieldSite, code Code, message string) *Diagnostic { location := site.location return &Diagnostic{ Code: code, Severity: SeverityError, Path: site.path.clone(), Message: message, Primary: &location, } }