From 053b39b9ceea03b7f24e49734137c1521fc85ab4 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Tue, 12 Aug 2025 12:11:54 +0100 Subject: [PATCH 1/2] perf(tspath): optimize `hasRelativePathSegment` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ HasRelativePathSegment/foo/bar/baz-12 29.10n ± 2% 14.79n ± 1% -49.16% (p=0.000 n=10) HasRelativePathSegment/./some/path-12 2.065n ± 0% 2.039n ± 2% -1.26% (p=0.022 n=10) HasRelativePathSegment//foo/./bar/../../.-12 7.030n ± 0% 2.025n ± 0% -71.20% (p=0.000 n=10) HasRelativePathSegment/foo/foo/foo/foo/foo/...etc-12 9.076n ± 1% 2.308n ± 1% -74.56% (p=0.000 n=10) geomean 7.869n 3.446n -56.21% --- internal/tspath/path.go | 67 ++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/internal/tspath/path.go b/internal/tspath/path.go index a0d722b233..8682fccffd 100644 --- a/internal/tspath/path.go +++ b/internal/tspath/path.go @@ -470,28 +470,73 @@ func simpleNormalizePath(path string) (string, bool) { return "", false } +// hasRelativePathSegment reports whether p contains ".", "..", "./", "../", "/.", "/..", "//", "/./", or "/../". func hasRelativePathSegment(p string) bool { - if p == "." || p == ".." { - return true + n := len(p) + if n == 0 { + return false } - if strings.HasPrefix(p, "./") || strings.HasPrefix(p, "../") { + if p == "." || p == ".." { return true } - if strings.HasSuffix(p, "/.") || strings.HasSuffix(p, "/..") { - return true + // Leading "./" OR "../" + if p[0] == '.' { + if n >= 2 && p[1] == '/' { + return true + } + // Leading "../" + if n >= 3 && p[1] == '.' && p[2] == '/' { + return true + } } - - if strings.Contains(p, "//") { - return true + // Trailing "/." OR "/.." + if p[n-1] == '.' { + if n >= 2 && p[n-2] == '/' { + return true + } + if n >= 3 && p[n-2] == '.' && p[n-3] == '/' { + return true + } } - if strings.Contains(p, "/./") || strings.Contains(p, "/../") { - return true + // Now look for any `//` or `/./` or `/../` + + prevSlash := true // treat start as if preceded by '/' + segLen := 0 // length of current segment since last slash + dotCount := 0 // consecutive dots at start of the current segment; -1 => not only dots + + for i := range n { + c := p[i] + if c == '/' { + // "//" + if prevSlash { + return true + } + // "/./" or "/../" + if (segLen == 1 && dotCount == 1) || (segLen == 2 && dotCount == 2) { + return true + } + prevSlash = true + segLen = 0 + dotCount = 0 + continue + } + + if c == '.' { + if dotCount >= 0 { + dotCount++ + } + } else { + dotCount = -1 + } + segLen++ + prevSlash = false } - return false + // Trailing "/." or "/.." + return (segLen == 1 && dotCount == 1) || (segLen == 2 && dotCount == 2) } func NormalizePath(path string) string { From 7c6c5a0d46d9c41b489f7feaca6069dd867d44c4 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Tue, 12 Aug 2025 18:50:15 +0100 Subject: [PATCH 2/2] fix case --- internal/tspath/path.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/tspath/path.go b/internal/tspath/path.go index 8682fccffd..7b9cb3dc06 100644 --- a/internal/tspath/path.go +++ b/internal/tspath/path.go @@ -503,9 +503,9 @@ func hasRelativePathSegment(p string) bool { // Now look for any `//` or `/./` or `/../` - prevSlash := true // treat start as if preceded by '/' - segLen := 0 // length of current segment since last slash - dotCount := 0 // consecutive dots at start of the current segment; -1 => not only dots + prevSlash := false + segLen := 0 // length of current segment since last slash + dotCount := 0 // consecutive dots at start of the current segment; -1 => not only dots for i := range n { c := p[i]