[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');
}