diff --git a/internal/tsoptions/wildcarddirectories.go b/internal/tsoptions/wildcarddirectories.go index 33068745ac..a782b1123c 100644 --- a/internal/tsoptions/wildcarddirectories.go +++ b/internal/tsoptions/wildcarddirectories.go @@ -1,7 +1,6 @@ package tsoptions import ( - "regexp" "strings" "github.com/dlclark/regexp2" @@ -28,13 +27,13 @@ func getWildcardDirectories(include []string, exclude []string, comparePathsOpti } rawExcludeRegex := vfs.GetRegularExpressionForWildcard(exclude, comparePathsOptions.CurrentDirectory, "exclude") - var excludeRegex *regexp.Regexp + var excludeRegex *regexp2.Regexp if rawExcludeRegex != "" { - options := "" + flags := regexp2.ECMAScript if !comparePathsOptions.UseCaseSensitiveFileNames { - options = "(?i)" + flags |= regexp2.IgnoreCase } - excludeRegex = regexp.MustCompile(options + rawExcludeRegex) + excludeRegex = regexp2.MustCompile(rawExcludeRegex, regexp2.RegexOptions(flags)) } wildcardDirectories := make(map[string]bool) @@ -44,8 +43,10 @@ func getWildcardDirectories(include []string, exclude []string, comparePathsOpti for _, file := range include { spec := tspath.NormalizeSlashes(tspath.CombinePaths(comparePathsOptions.CurrentDirectory, file)) - if excludeRegex != nil && excludeRegex.MatchString(spec) { - continue + if excludeRegex != nil { + if matched, _ := excludeRegex.MatchString(spec); matched { + continue + } } match := getWildcardDirectoryFromSpec(spec, comparePathsOptions.UseCaseSensitiveFileNames) diff --git a/internal/tsoptions/wildcarddirectories_test.go b/internal/tsoptions/wildcarddirectories_test.go new file mode 100644 index 0000000000..65976295ab --- /dev/null +++ b/internal/tsoptions/wildcarddirectories_test.go @@ -0,0 +1,65 @@ +package tsoptions + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/tspath" +) + +func TestGetWildcardDirectories_NonASCIICharacters(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + include []string + exclude []string + currentDirectory string + useCaseSensitiveFileNames bool + }{ + { + name: "Norwegian character æ in path", + include: []string{"src/**/*.test.ts", "src/**/*.stories.ts", "src/**/*.mdx"}, + exclude: []string{"node_modules"}, + currentDirectory: "C:/Users/TobiasLægreid/dev/app/frontend/packages/react", + useCaseSensitiveFileNames: false, + }, + { + name: "Japanese characters in path", + include: []string{"src/**/*.ts"}, + exclude: []string{"テスト"}, + currentDirectory: "/Users/ユーザー/プロジェクト", + useCaseSensitiveFileNames: true, + }, + { + name: "Chinese characters in path", + include: []string{"源代码/**/*.js"}, + exclude: []string{"节点模块"}, + currentDirectory: "/home/用户/项目", + useCaseSensitiveFileNames: true, + }, + { + name: "Various Unicode characters", + include: []string{"src/**/*.ts"}, + exclude: []string{"node_modules"}, + currentDirectory: "/Users/Müller/café/naïve/résumé", + useCaseSensitiveFileNames: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + comparePathsOptions := tspath.ComparePathsOptions{ + CurrentDirectory: tt.currentDirectory, + UseCaseSensitiveFileNames: tt.useCaseSensitiveFileNames, + } + + result := getWildcardDirectories(tt.include, tt.exclude, comparePathsOptions) + + if result == nil { + t.Fatalf("expected non-nil result") + } + }) + } +} diff --git a/internal/vfs/utilities.go b/internal/vfs/utilities.go index d86a7f0d10..4b44664d64 100644 --- a/internal/vfs/utilities.go +++ b/internal/vfs/utilities.go @@ -81,11 +81,11 @@ func IsImplicitGlob(lastPathComponent string) bool { return !strings.ContainsAny(lastPathComponent, ".*?") } -// Reserved characters, forces escaping of any non-word (or digit), non-whitespace character. -// It may be inefficient (we could just match (/[-[\]{}()*+?.,\\^$|#\s]/g), but this is future -// proof. +// Reserved characters - only escape actual regex metacharacters. +// Go's regexp doesn't support \x escape sequences for arbitrary characters, +// so we only escape characters that have special meaning in regex. var ( - reservedCharacterPattern *regexp.Regexp = regexp.MustCompile(`[^\w\s/]`) + reservedCharacterPattern *regexp.Regexp = regexp.MustCompile(`[\\.\+*?()\[\]{}^$|#]`) wildcardCharCodes = []rune{'*', '?'} )