From 425334948d2fd586985127d1c4d652a966acb3a7 Mon Sep 17 00:00:00 2001
From: Anton Bershanskyi <bershanskyi@gmail.com>
Date: Tue, 11 Feb 2025 13:01:27 -0800
Subject: [PATCH] [Presubmit] Standardize paths in PRESUBMIT.py on Windows

Windows uses back slash \ as a path separator while other OSes use
forward slash. CL 6236404 introduced depot_tools AffectedFile.UnixLocalPath()
which provides a UNIX-style platform-independent path on all systems.
This CL updates PRESUBMIT.py to take advantage of this new method.

Bug: None
Change-Id: Iccd71ccb5192ff56efbc822dc2ae52304b61f778
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6218309
Commit-Queue: Anton Bershanskyi <bershanskyi@gmail.com>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Dirk Pranke <dpranke@google.com>
Cr-Commit-Position: refs/heads/main@{#1418864}
---
 PRESUBMIT.py            | 56 ++++++++++++++++-------------------------
 PRESUBMIT_test_mocks.py | 10 ++++++--
 2 files changed, 29 insertions(+), 37 deletions(-)

diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 39a44c0d3622a..580addd1a191e 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -2802,7 +2802,7 @@ def CheckCrosApiNeedBrowserTest(input_api, output_api):
     has_new_crosapi = False
     has_browser_test = False
     for f in input_api.AffectedFiles():
-        path = f.LocalPath()
+        path = f.UnixLocalPath()
         if (path.startswith('chromeos/crosapi/mojom') and
             _IsMojomFile(input_api, path) and f.Action() == 'A'):
             has_new_crosapi = True
@@ -2885,10 +2885,7 @@ def CheckNoBannedFunctions(input_api, output_api):
         if not excluded_paths:
             return False
 
-        local_path = affected_file.LocalPath()
-        # Consistently use / as path separator to simplify the writing of regex
-        # expressions.
-        local_path = local_path.replace(input_api.os_path.sep, '/')
+        local_path = affected_file.UnixLocalPath()
         for item in excluded_paths:
             if input_api.re.match(item, local_path):
                 return True
@@ -3224,11 +3221,9 @@ def CheckNoInternalHeapIncludes(input_api, output_api):
     v8_wrapper_pattern = input_api.re.compile(
         r'^\s*#include\s*"third_party/blink/renderer/platform/heap/v8_wrapper/.*"'
     )
-    # Consistently use / as path separator to simplify the writing of regex
-    # expressions.
     file_filter = lambda f: not input_api.re.match(
         r"^third_party/blink/renderer/platform/heap/.*",
-        f.LocalPath().replace(input_api.os_path.sep, '/'))
+        f.UnixLocalPath())
     errors = []
 
     for f in input_api.AffectedFiles(file_filter=file_filter):
@@ -3651,11 +3646,9 @@ def CheckAddedDepsHaveTargetApprovals(input_api, output_api):
     # (via lines like "+foo/bar/baz").
     depended_on_paths = set()
 
-    # Consistently use / as path separator to simplify the writing of regex
-    # expressions.
     file_filter = lambda f: not input_api.re.match(
         r"^third_party/blink/.*",
-        f.LocalPath().replace(input_api.os_path.sep, '/'))
+        f.UnixLocalPath())
     for f in input_api.AffectedFiles(include_deletes=False,
                                      file_filter=file_filter):
         filename = input_api.os_path.basename(f.LocalPath())
@@ -4118,7 +4111,7 @@ def CheckParseErrors(input_api, output_api):
         action = get_action(affected_file)
         if not action:
             return False
-        path = affected_file.LocalPath()
+        path = affected_file.UnixLocalPath()
 
         if _MatchesFile(input_api,
                         _KNOWN_TEST_DATA_AND_INVALID_JSON_FILE_PATTERNS, path):
@@ -4136,7 +4129,7 @@ def CheckParseErrors(input_api, output_api):
         kwargs = {}
         if (action == _GetJSONParseError
                 and _MatchesFile(input_api, json_no_comments_patterns,
-                                 affected_file.LocalPath())):
+                                 affected_file.UnixLocalPath())):
             kwargs['eat_comments'] = False
         parse_error = action(input_api, affected_file.AbsoluteLocalPath(),
                              **kwargs)
@@ -4213,9 +4206,6 @@ def CheckPythonDevilInit(input_api, output_api):
 
 
 def _MatchesFile(input_api, patterns, path):
-    # Consistently use / as path separator to simplify the writing of regex
-    # expressions.
-    path = path.replace(input_api.os_path.sep, '/')
     for pattern in patterns:
         if input_api.re.search(pattern, path):
             return True
@@ -4662,7 +4652,7 @@ def CheckSetNoParent(input_api, output_api):
         # Check that every set noparent line has a corresponding file:// line
         # listed in build/OWNERS.setnoparent. An exception is made for top level
         # directories since src/OWNERS shouldn't review them.
-        linux_path = f.LocalPath().replace(input_api.os_path.sep, '/')
+        linux_path = f.UnixLocalPath()
         if (linux_path.count('/') != 1
                 and (not linux_path in _EXCLUDED_SET_NO_PARENT_PATHS)):
             for set_noparent_line in found_set_noparent_lines:
@@ -4697,12 +4687,12 @@ def CheckUselessForwardDeclarations(input_api, output_api):
     struct_pattern = input_api.re.compile(r'^struct\s+(\w+);$',
                                           input_api.re.MULTILINE)
     for f in input_api.AffectedFiles(include_deletes=False):
-        if (f.LocalPath().startswith('third_party')
-                and not f.LocalPath().startswith('third_party/blink')
-                and not f.LocalPath().startswith('third_party\\blink')):
+        local_path = f.UnixLocalPath()
+        if (local_path.startswith('third_party')
+                and not local_path.startswith('third_party/blink')):
             continue
 
-        if not f.LocalPath().endswith('.h'):
+        if not local_path.endswith('.h'):
             continue
 
         contents = input_api.ReadFile(f)
@@ -4951,10 +4941,9 @@ def _CheckAndroidTestAnnotationUsage(input_api, output_api):
 
 def _CheckAndroidNewMdpiAssetLocation(input_api, output_api):
     """Checks if MDPI assets are placed in a correct directory."""
-    file_filter = lambda f: (f.LocalPath().endswith(
-        '.png') and ('/res/drawable/'.replace('/', input_api.os_path.sep) in f.
-                     LocalPath() or '/res/drawable-ldrtl/'.replace(
-                         '/', input_api.os_path.sep) in f.LocalPath()))
+    file_filter = lambda f: (f.UnixLocalPath().endswith(
+        '.png') and ('/res/drawable/' in f.
+                     UnixLocalPath() or '/res/drawable-ldrtl/' in f.UnixLocalPath()))
     errors = []
     for f in input_api.AffectedFiles(include_deletes=False,
                                      file_filter=file_filter):
@@ -5364,9 +5353,8 @@ def CheckNoDeprecatedCss(input_api, output_api):
 def CheckForRelativeIncludes(input_api, output_api):
     bad_files = {}
     for f in input_api.AffectedFiles(include_deletes=False):
-        if (f.LocalPath().startswith('third_party')
-                and not f.LocalPath().startswith('third_party/blink')
-                and not f.LocalPath().startswith('third_party\\blink')):
+        if (f.UnixLocalPath().startswith('third_party')
+                and not f.LocalPath().startswith('third_party/blink')):
             continue
 
         if not _IsCPlusPlusFile(input_api, f.LocalPath()):
@@ -5409,9 +5397,8 @@ def CheckForCcIncludes(input_api, output_api):
     results = []
     for f in input_api.AffectedFiles(include_deletes=False):
         # We let third_party code do whatever it wants
-        if (f.LocalPath().startswith('third_party')
-                and not f.LocalPath().startswith('third_party/blink')
-                and not f.LocalPath().startswith('third_party\\blink')):
+        if (f.UnixLocalPath().startswith('third_party')
+                and not f.LocalPath().startswith('third_party/blink')):
             continue
 
         if not _IsCPlusPlusFile(input_api, f.LocalPath()):
@@ -6251,7 +6238,7 @@ def CheckForInvalidIfDefinedMacros(input_api, output_api):
     def affected_files_filter(f):
         # Normalize the local path to Linux-style path separators so that the
         # path comparisons work on Windows as well.
-        path = f.LocalPath().replace('\\', '/')
+        path = f.UnixLocalPath()
 
         for skipped_path in SKIPPED_PATHS:
             if path.startswith(skipped_path):
@@ -7288,7 +7275,7 @@ def CheckAssertAshOnlyCode(input_api, output_api):
 
 
 def _IsMiraclePtrDisallowed(input_api, affected_file):
-    path = affected_file.LocalPath()
+    path = affected_file.UnixLocalPath()
     if not _IsCPlusPlusFile(input_api, path):
         return False
 
@@ -7543,8 +7530,7 @@ def CheckLibcxxRevisionsMatch(input_api, output_api):
 
     DEPS_FILES = [ 'DEPS', 'buildtools/deps_revisions.gni' ]
 
-    file_filter = lambda f: f.LocalPath().replace(
-            input_api.os_path.sep, '/') in DEPS_FILES
+    file_filter = lambda f: f.UnixLocalPath() in DEPS_FILES
     changed_deps_files = input_api.AffectedFiles(file_filter=file_filter)
     if not changed_deps_files:
         return []
diff --git a/PRESUBMIT_test_mocks.py b/PRESUBMIT_test_mocks.py
index 5b3fccf003885..131a7777979c2 100644
--- a/PRESUBMIT_test_mocks.py
+++ b/PRESUBMIT_test_mocks.py
@@ -45,7 +45,7 @@ class MockCannedChecks(object):
             # Shelling out to the SCM to determine the changed region can be
             # quite expensive on Win32. Assuming that most files will be kept
             # problem-free, we can skip the SCM operations most of the time.
-            extension = str(f.LocalPath()).rsplit('.', 1)[-1]
+            extension = str(f.UnixLocalPath()).rsplit('.', 1)[-1]
             if all(callable_rule(extension, line) for line in f.NewContents()):
                 # No violation found in full text: can skip considering diff.
                 continue
@@ -162,7 +162,7 @@ class MockInputApi(object):
                                   include_deletes=False)
 
     def FilterSourceFile(self, file, files_to_check=(), files_to_skip=()):
-        local_path = file.LocalPath()
+        local_path = file.UnixLocalPath()
         found_in_files_to_check = not files_to_check
         if files_to_check:
             if type(files_to_check) is str:
@@ -298,6 +298,12 @@ class MockFile(object):
     def AbsoluteLocalPath(self):
         return os.path.join(_REPO_ROOT, self._local_path)
 
+    # This method must be functionally identical to
+    # AffectedFile.UnixLocalPath(), but must normalize Windows-style
+    # paths even on non-Windows platforms because tests contain them
+    def UnixLocalPath(self):
+        return self._local_path.replace('\\', '/')
+
     def GenerateScmDiff(self):
         return self._scm_diff