Files
kjol/tools/tsgo/internal/tsoptions/wildcarddirectories.go
2026-07-09 16:50:43 -04:00

135 lines
4.2 KiB
Go

package tsoptions
import (
"strings"
"github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/vfs/vfsmatch"
)
func getWildcardDirectories(include []string, exclude []string, comparePathsOptions tspath.ComparePathsOptions) map[string]bool {
// We watch a directory recursively if it contains a wildcard anywhere in a directory segment
// of the pattern:
//
// /a/b/**/d - Watch /a/b recursively to catch changes to any d in any subfolder recursively
// /a/b/*/d - Watch /a/b recursively to catch any d in any immediate subfolder, even if a new subfolder is added
// /a/b - Watch /a/b recursively to catch changes to anything in any recursive subfoler
//
// We watch a directory without recursion if it contains a wildcard in the file segment of
// the pattern:
//
// /a/b/* - Watch /a/b directly to catch any new file
// /a/b/a?z - Watch /a/b directly to catch any new file matching a?z
if len(include) == 0 {
return nil
}
excludeMatcher := vfsmatch.NewSpecMatcher(exclude, comparePathsOptions.CurrentDirectory, vfsmatch.UsageExclude, comparePathsOptions.UseCaseSensitiveFileNames)
wildcardDirectories := make(map[string]bool)
wildCardKeyToPath := make(map[string]string)
var recursiveKeys []string
for _, file := range include {
spec := tspath.NormalizePath(tspath.CombinePaths(comparePathsOptions.CurrentDirectory, file))
if excludeMatcher != nil && excludeMatcher.MatchString(spec) {
continue
}
match := getWildcardDirectoryFromSpec(spec, comparePathsOptions.UseCaseSensitiveFileNames)
if match != nil {
key := match.Key
path := match.Path
recursive := match.Recursive
existingPath, existsPath := wildCardKeyToPath[key]
var existingRecursive bool
if existsPath {
existingRecursive = wildcardDirectories[existingPath]
}
if !existsPath || (!existingRecursive && recursive) {
pathToUse := path
if existsPath {
pathToUse = existingPath
}
wildcardDirectories[pathToUse] = recursive
if !existsPath {
wildCardKeyToPath[key] = path
}
if recursive {
recursiveKeys = append(recursiveKeys, key)
}
}
}
// Remove any subpaths under an existing recursively watched directory
for path := range wildcardDirectories {
for _, recursiveKey := range recursiveKeys {
key := toCanonicalKey(path, comparePathsOptions.UseCaseSensitiveFileNames)
if key != recursiveKey && tspath.ContainsPath(recursiveKey, key, comparePathsOptions) {
delete(wildcardDirectories, path)
}
}
}
}
return wildcardDirectories
}
func toCanonicalKey(path string, useCaseSensitiveFileNames bool) string {
if useCaseSensitiveFileNames {
return path
}
return strings.ToLower(path)
}
// wildcardDirectoryMatch represents the result of a wildcard directory match
type wildcardDirectoryMatch struct {
Key string
Path string
Recursive bool
}
func getWildcardDirectoryFromSpec(spec string, useCaseSensitiveFileNames bool) *wildcardDirectoryMatch {
// Find the first occurrence of a wildcard character
firstWildcard := strings.IndexAny(spec, "*?")
if firstWildcard != -1 {
// Find the last directory separator before the wildcard
lastSepBeforeWildcard := strings.LastIndexByte(spec[:firstWildcard], tspath.DirectorySeparator)
if lastSepBeforeWildcard != -1 {
path := spec[:lastSepBeforeWildcard]
lastDirectorySeparatorIndex := strings.LastIndexByte(spec, tspath.DirectorySeparator)
// Determine if this should be watched recursively:
// recursive if the wildcard appears in a directory segment (not just the final file segment)
recursive := firstWildcard < lastDirectorySeparatorIndex
return &wildcardDirectoryMatch{
Key: toCanonicalKey(path, useCaseSensitiveFileNames),
Path: path,
Recursive: recursive,
}
}
}
if lastSepIndex := strings.LastIndexByte(spec, tspath.DirectorySeparator); lastSepIndex != -1 {
lastSegment := spec[lastSepIndex+1:]
if vfsmatch.IsImplicitGlob(lastSegment) {
path := tspath.RemoveTrailingDirectorySeparator(spec)
return &wildcardDirectoryMatch{
Key: toCanonicalKey(path, useCaseSensitiveFileNames),
Path: path,
Recursive: true,
}
}
}
return nil
}