[analyzer] Faster relative-path-if-inside-same-dir function for `AbstractDartSdk.getPath`

AbstractDartSdk._getPath has some "phases" trying to determine a
"dart import uri" from a File.
One of these phases checks if the given file is inside a directory that
one of the known sdk platforms is inside, thus allowing us to construct
a uri from there.
For instance if we know that `/a/b/c/d/e.dart` is called
`dart:foo/e.dart` and we’re presented with `/a/b/c/d/f.dart` we’ll
figure out that it’s called `dart:foo/f.dart`.
The code for this is very simple, but very inefficient.

This CL makes it more efficient.

This is only applicable when analyzing with the flutter platform.

The below benchmarks show a ~7 to ~33% improvement depending on what's
being analyzed. It's all done via `dart analyze` where the first run
has been discarded so everything is in cache.

Running on `rwf-materials`:

```
Before:
real    1m54.491s
real    1m55.969s
real    1m53.802s

Now:
real    1m16.348s
real    1m17.570s
real    1m17.149s

Difference at 95.0% confidence
        -37.7317 +/- 1.06819
        -32.8805% +/- 0.930855%
        (Student's t, pooled s = 0.471277)
```

Running on "flutter gallery":

```
Before:
real    0m2.836s
real    0m2.783s
real    0m2.718s

Now:
real    0m2.478s
real    0m2.492s
real    0m2.451s

Difference at 95.0% confidence
        -0.305333 +/- 0.10044
        -10.9872% +/- 3.61426%
        (Student's t, pooled s = 0.0443133)
```

Running on "flutter-wonderous-app":

```
Before:
real    0m2.019s
real    0m1.999s
real    0m1.984s

Now:
real    0m1.863s
real    0m1.821s
real    0m1.856s

Difference at 95.0% confidence
        -0.154 +/- 0.0457455
        -7.69743% +/- 2.28652%
        (Student's t, pooled s = 0.0201825)
```

Running on "flutter gallery" and "flutter-wonderous-app" at the same time:

```
Before:
real    0m3.446s
real    0m3.407s
real    0m3.431s

Now:
real    0m2.993s
real    0m2.965s
real    0m3.008s

Difference at 95.0% confidence
        -0.439333 +/- 0.0470921
        -12.816% +/- 1.37375%
        (Student's t, pooled s = 0.0207766)
```

Change-Id: I13e81e96312d9cb5ad2a8244a0cb7722a49c4460
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/282280
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/sdk/sdk.dart b/pkg/analyzer/lib/src/dart/sdk/sdk.dart
index dc71419..d3199db 100644
--- a/pkg/analyzer/lib/src/dart/sdk/sdk.dart
+++ b/pkg/analyzer/lib/src/dart/sdk/sdk.dart
@@ -10,6 +10,7 @@
 import 'package:analyzer/exception/exception.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/file_system/memory_file_system.dart';
+import 'package:analyzer/src/dart/sdk/sdk_utils.dart';
 import 'package:analyzer/src/generated/engine.dart';
 import 'package:analyzer/src/generated/java_engine_io.dart';
 import 'package:analyzer/src/generated/sdk.dart';
@@ -162,12 +163,10 @@
     for (final library in libraryMap.sdkLibraries) {
       final pathContext = resourceProvider.pathContext;
       if (pathContext.isAbsolute(library.path)) {
-        final libraryFile = resourceProvider.getFile(library.path);
-        final libraryFolder = libraryFile.parent;
-        if (libraryFolder.contains(file.path)) {
-          final relPath = pathContext
-              .relative(file.path, from: libraryFolder.path)
-              .replaceAll(separator, '/');
+        String? relativePathIfInside =
+            getRelativePathIfInside(library.path, file.path);
+        if (relativePathIfInside != null) {
+          final relPath = relativePathIfInside.replaceAll(separator, '/');
           return '${library.shortName}/$relPath';
         }
       }
diff --git a/pkg/analyzer/lib/src/dart/sdk/sdk_utils.dart b/pkg/analyzer/lib/src/dart/sdk/sdk_utils.dart
new file mode 100644
index 0000000..976a4d9
--- /dev/null
+++ b/pkg/analyzer/lib/src/dart/sdk/sdk_utils.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:math' show min;
+
+String? getRelativePathIfInside(String libraryPath, String filePath) {
+  int minLength = min(libraryPath.length, filePath.length);
+
+  // Find how far the strings are the same.
+  int same = 0;
+  for (int i = 0; i < minLength; i++) {
+    if (libraryPath.codeUnitAt(i) == filePath.codeUnitAt(i)) {
+      same++;
+    } else {
+      break;
+    }
+  }
+  // They're the same up to and including index [same].
+  // If there isn't a path seperator left in the string [libPath],
+  // [filePath] is inside the same dir as [libPath] (possibly within
+  // subdirs).
+  const int forwardSlash = 47;
+  const int backwardsSlash = 92;
+  for (int i = same; i < libraryPath.length; i++) {
+    int c = libraryPath.codeUnitAt(i);
+    if (c == forwardSlash || c == backwardsSlash) {
+      return null;
+    }
+  }
+
+  // To get the relative path we need to go back to the previous path
+  // seperator.
+  for (int i = same; i >= 0; i--) {
+    int c = libraryPath.codeUnitAt(i);
+    if (c == forwardSlash || c == backwardsSlash) {
+      return filePath.substring(i + 1);
+    }
+  }
+  throw UnsupportedError("Unsupported input: $libraryPath and $filePath");
+}
diff --git a/pkg/analyzer/test/src/dart/sdk/sdk_utils_test.dart b/pkg/analyzer/test/src/dart/sdk/sdk_utils_test.dart
new file mode 100644
index 0000000..88f13d3
--- /dev/null
+++ b/pkg/analyzer/test/src/dart/sdk/sdk_utils_test.dart
@@ -0,0 +1,122 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/file_system/memory_file_system.dart';
+import 'package:analyzer/src/dart/sdk/sdk_utils.dart';
+import 'package:path/path.dart' as pathos;
+import 'package:test/test.dart';
+
+void main() {
+  for (List<dynamic> osData in [
+    ["C:\\windowspaths\\", "\\", pathos.windows],
+    ["/posixpaths/", "/", pathos.posix],
+  ]) {
+    var prefix = osData[0] as String;
+    var separator = osData[1] as String;
+    var context = osData[2] as pathos.Context;
+    var resourceProvider = MemoryResourceProvider(context: context);
+
+    for (var libraryPath in getPaths(sdkPaths,
+        addFilenames: false, prefix: prefix, separator: separator)) {
+      for (var filePath in getPaths(inputPaths,
+          addFilenames: true, prefix: prefix, separator: separator)) {
+        File library = resourceProvider.getFile(libraryPath);
+        File file = resourceProvider.getFile(filePath);
+        test("sdk_util_test $libraryPath vs $filePath", () {
+          String? relativePathIfInside =
+              getRelativePathIfInside(library.path, file.path);
+          expect(
+              relativePathIfInside,
+              getRelativePathIfInsideSlow(
+                  resourceProvider.getFile(library.path), file, context));
+          expect(
+              relativePathIfInside,
+              getRelativePathIfInsideSemi(
+                  library.path, file.path, context.separator));
+        });
+      }
+    }
+  }
+}
+
+List<String> inputPaths = [
+  "sky_engine/lib/async",
+  "sky_engine/lib/collection",
+  "sky_engine/lib/convert",
+  "sky_engine/lib/core",
+  "sky_engine/lib/developer",
+  "sky_engine/lib/ffi",
+  "sky_engine/lib/_http",
+  "sky_engine/lib/_interceptors",
+  "sky_engine/lib/internal",
+  "sky_engine/lib/io",
+  "sky_engine/lib/isolate",
+  "sky_engine/lib/math",
+  "sky_engine/lib/typed_data",
+  "sky_engine/lib/ui",
+  "flutter/lib",
+  "project/test",
+  "project/lib",
+  ".pub-cache/hosted/pub.dev/foo/lib/src",
+  ".pub-cache/hosted/pub.dev/foo/lib",
+];
+
+List<String> sdkPaths = [
+  "sky_engine/lib/async/async.dart",
+  "sky_engine/lib/collection/collection.dart",
+  "sky_engine/lib/convert/convert.dart",
+  "sky_engine/lib/core/core.dart",
+  "sky_engine/lib/developer/developer.dart",
+  "sky_engine/lib/ffi/ffi.dart",
+  "sky_engine/lib/html/html_dart2js.dart",
+  "sky_engine/lib/io/io.dart",
+  "sky_engine/lib/isolate/isolate.dart",
+  "sky_engine/lib/js/js.dart",
+  "sky_engine/lib/js_util/js_util.dart",
+  "sky_engine/lib/math/math.dart",
+  "sky_engine/lib/typed_data/typed_data.dart",
+  "sky_engine/lib/ui/ui.dart",
+  "sky_engine/lib/wasm/wasm_types.dart",
+  "sky_engine/lib/_http/http.dart",
+  "sky_engine/lib/_interceptors/interceptors.dart",
+  "sky_engine/lib/internal/internal.dart",
+  "sky_engine/lib/_empty.dart",
+];
+
+Iterable<String> getPaths(List<String> input,
+    {required bool addFilenames,
+    required String prefix,
+    required String separator}) sync* {
+  for (String s in input) {
+    String base = "$prefix${s.replaceAll("/", separator)}";
+    if (addFilenames) {
+      yield "$base${separator}a.dart";
+      yield "$base${separator}ab.dart";
+    } else {
+      yield base;
+    }
+  }
+}
+
+String? getRelativePathIfInsideSemi(
+    String libraryPath, String filePath, String separator) {
+  String libDirPath =
+      libraryPath.substring(0, libraryPath.lastIndexOf(separator) + 1);
+  String fileDirPath =
+      filePath.substring(0, filePath.lastIndexOf(separator) + 1);
+  if (fileDirPath.startsWith(libDirPath)) {
+    return filePath.substring(libDirPath.length);
+  }
+  return null;
+}
+
+String? getRelativePathIfInsideSlow(
+    File libraryFile, File file, pathos.Context pathContext) {
+  var libraryFolder = libraryFile.parent;
+  if (libraryFolder.contains(file.path)) {
+    return pathContext.relative(file.path, from: libraryFolder.path);
+  }
+  return null;
+}
diff --git a/pkg/analyzer/test/src/dart/sdk/test_all.dart b/pkg/analyzer/test/src/dart/sdk/test_all.dart
index a68b8ef..3b92471 100644
--- a/pkg/analyzer/test/src/dart/sdk/test_all.dart
+++ b/pkg/analyzer/test/src/dart/sdk/test_all.dart
@@ -5,10 +5,12 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'sdk_test.dart' as sdk;
+import 'sdk_utils_test.dart' as sdk_utils;
 
 /// Utility for manually running all tests.
 main() {
   defineReflectiveSuite(() {
     sdk.main();
+    sdk_utils.main();
   }, name: 'sdk');
 }