diff --git a/dwds/CHANGELOG.md b/dwds/CHANGELOG.md index 212894a5d..478b5278c 100644 --- a/dwds/CHANGELOG.md +++ b/dwds/CHANGELOG.md @@ -1,7 +1,15 @@ -## 11.5.2-dev +## 12.0.0-dev +- Implement `lookupResolvedPackageUris` and `lookupPackageUris` vm service API. +- Update `vm_service` version to `^7.5.0`. +- Make `ExpressionCompilerService` infer location of `libraries.json` from + `sdkDir` parameter. - Show an alert in the Dart Debug Extension for a multi-app scenario. +**Breaking changes:** + +- Add `sdkDir` argument to `Dwds.start` to help file resolution for sdk uris. + ## 11.5.1 - Update SDK contraint to `>=2.15.0 <3.0.0`. diff --git a/dwds/lib/dwds.dart b/dwds/lib/dwds.dart index f3c3d58f3..0b5430e0a 100644 --- a/dwds/lib/dwds.dart +++ b/dwds/lib/dwds.dart @@ -24,6 +24,7 @@ import 'src/readers/asset_reader.dart'; import 'src/servers/devtools.dart'; import 'src/servers/extension_backend.dart'; import 'src/services/expression_compiler.dart'; +import 'src/utilities/dart_uri.dart'; export 'src/connections/app_connection.dart' show AppConnection; export 'src/connections/debug_connection.dart' show DebugConnection; @@ -111,6 +112,7 @@ class Dwds { bool spawnDds, bool enableDevtoolsLaunch, DevtoolsLauncher devtoolsLauncher, + Uri sdkDir, }) async { hostname ??= 'localhost'; enableDebugging ??= true; @@ -122,6 +124,8 @@ class Dwds { spawnDds ??= true; globalLoadStrategy = loadStrategy; + await DartUri.initialize(sdkDir: sdkDir); + DevTools devTools; Future extensionUri; ExtensionBackend extensionBackend; diff --git a/dwds/lib/src/debugging/inspector.dart b/dwds/lib/src/debugging/inspector.dart index 06bd15c8a..22ac9d7f3 100644 --- a/dwds/lib/src/debugging/inspector.dart +++ b/dwds/lib/src/debugging/inspector.dart @@ -87,11 +87,11 @@ class AppInspector extends Domain { Future _initialize() async { var libraries = await libraryHelper.libraryRefs; isolate.rootLib = await libraryHelper.rootLib; - isolate.libraries.addAll(libraries); - await DartUri.recordAbsoluteUris(libraries.map((lib) => lib.uri)); var scripts = await scriptRefs; + + await DartUri.recordAbsoluteUris(libraries.map((lib) => lib.uri)); await DartUri.recordAbsoluteUris(scripts.map((script) => script.uri)); isolate.extensionRPCs.addAll(await _getExtensionRpcs()); diff --git a/dwds/lib/src/services/chrome_proxy_service.dart b/dwds/lib/src/services/chrome_proxy_service.dart index 221beac56..8f50bd4f1 100644 --- a/dwds/lib/src/services/chrome_proxy_service.dart +++ b/dwds/lib/src/services/chrome_proxy_service.dart @@ -655,6 +655,19 @@ ${globalLoadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension( return (await _debugger).pause(); } + @override + Future lookupResolvedPackageUris( + String isolateId, List uris) async { + await isInitialized; + return UriList(uris: uris.map(DartUri.toResolvedUri).toList()); + } + + @override + Future lookupPackageUris(String isolateId, List uris) async { + await isInitialized; + return UriList(uris: uris.map(DartUri.toPackageUri).toList()); + } + @override Future registerService(String service, String alias) async { return _rpcNotSupportedFuture('registerService'); @@ -1037,19 +1050,6 @@ ${globalLoadStrategy.loadModuleSnippet}("dart_sdk").developer.invokeExtension( String isolateId, String breakpointId, bool enable) => throw UnimplementedError(); - @override - Future lookupPackageUris(String isolateId, List uris) { - // TODO(https://github.com/dart-lang/webdev/issues/1446): implement. - throw UnimplementedError(); - } - - @override - Future lookupResolvedPackageUris( - String isolateId, List uris) { - // TODO(https://github.com/dart-lang/webdev/issues/1446): implement. - throw UnimplementedError(); - } - /// Prevent DWDS from blocking Dart SDK rolls if changes in package:vm_service /// are unimplemented in DWDS. @override diff --git a/dwds/lib/src/services/expression_compiler_service.dart b/dwds/lib/src/services/expression_compiler_service.dart index 2411ad49a..76fe5cc35 100644 --- a/dwds/lib/src/services/expression_compiler_service.dart +++ b/dwds/lib/src/services/expression_compiler_service.dart @@ -235,10 +235,8 @@ class _Compiler { /// Uses [_address] and [_port] to communicate and [_assetHandler] to /// redirect asset requests to the asset server. /// -/// [_sdkRoot] is the path to the directory containing the sdk summary files, -/// [_workerPath] is the path to the DDC worker snapshot, -/// [_librariesPath] is the path to libraries definitions file -/// libraries.json. +/// [_sdkDir] is the path to the SDK installation directory. +/// [_workerPath] is the path to the DDC worker snapshot. /// /// Users need to stop the service by calling [stop]. class ExpressionCompilerService implements ExpressionCompiler { @@ -248,15 +246,13 @@ class ExpressionCompilerService implements ExpressionCompiler { final Handler _assetHandler; final bool _verbose; - final String _sdkRoot; - final String _librariesPath; + final String _sdkDir; final String _workerPath; ExpressionCompilerService( this._address, this._port, this._assetHandler, this._verbose, - {String sdkRoot, String librariesPath, String workerPath}) - : _sdkRoot = sdkRoot, - _librariesPath = librariesPath, + {String sdkDir, String workerPath}) + : _sdkDir = sdkDir, _workerPath = workerPath; @override @@ -277,16 +273,14 @@ class ExpressionCompilerService implements ExpressionCompiler { if (_compiler.isCompleted) return; soundNullSafety ??= false; - final executable = Platform.resolvedExecutable; - final binDir = p.dirname(executable); - final sdkDir = p.dirname(binDir); + final binDir = p.dirname(Platform.resolvedExecutable); + var sdkDir = _sdkDir ?? p.dirname(binDir); - var sdkRoot = _sdkRoot ?? p.join(sdkDir, 'lib', '_internal'); + var sdkSummaryRoot = p.join(sdkDir, 'lib', '_internal'); var sdkSummaryPath = soundNullSafety - ? p.join(sdkRoot, 'ddc_outline_sound.dill') - : p.join(sdkRoot, 'ddc_sdk.dill'); - var librariesPath = - _librariesPath ?? p.join(sdkDir, 'lib', 'libraries.json'); + ? p.join(sdkSummaryRoot, 'ddc_outline_sound.dill') + : p.join(sdkSummaryRoot, 'ddc_sdk.dill'); + var librariesPath = p.join(sdkDir, 'lib', 'libraries.json'); var workerPath = _workerPath ?? p.join(binDir, 'snapshots', 'dartdevc.dart.snapshot'); diff --git a/dwds/lib/src/utilities/dart_uri.dart b/dwds/lib/src/utilities/dart_uri.dart index b87e8ed86..4d270df7e 100644 --- a/dwds/lib/src/utilities/dart_uri.dart +++ b/dwds/lib/src/utilities/dart_uri.dart @@ -6,6 +6,9 @@ import 'dart:io'; +// ignore: implementation_imports +import 'package:_fe_analyzer_shared/src/util/libraries_specification.dart'; +import 'package:logging/logging.dart'; import 'package:package_config/package_config.dart'; import 'package:path/path.dart' as p; @@ -14,79 +17,7 @@ import '../loaders/strategy.dart'; /// The URI for a particular Dart file, able to canonicalize from various /// different representations. class DartUri { - /// The canonical web server path part of the URI. - /// - /// This is a relative path, which can be used to fetch the corresponding file - /// from the server. For example, 'hello_world/main.dart' or - /// 'packages/path/src/utils.dart'. - final String serverPath; - - /// The directory in which we're running. - /// - /// We store this here because for tests we may want to act as if we're - /// running in the directory of a target package, even if the current - /// directory of the tests is actually the main dwds directory. - static String currentDirectory = p.current; - - /// The current directory as a file: Uri, saved here to avoid re-computing. - static String currentDirectoryUri = '${p.toUri(currentDirectory)}'; - - /// Load the .packages file associated with the running application so we can - /// resolve file URLs into package: URLs appropriately. - static Future _loadPackageConfig(Uri uri) async { - _packageConfig ??= await loadPackageConfigUri(uri); - } - - // Note that we join using the platform separator, since currentDirectory is a - // platform path, not a URI path. Then the toUri should convert them. - static Uri get _packagesUri => p.toUri(p.join(currentDirectory, '.packages')); - - /// Whether the `.packages` file exists. - static bool _packagesExist; - - static bool get _shouldRecord => - _packagesExist ??= File.fromUri(_packagesUri).existsSync(); - - /// The way we resolve file: URLs into package: URLs - static PackageConfig _packageConfig; - - /// All of the known libraries, indexed by their absolute file URL. - static final Map _libraryNamesByPath = {}; - - /// Record all of the libraries, indexed by their absolute file: URI. - static Future recordAbsoluteUris(Iterable libraryUris) async { - if (_shouldRecord) { - await _loadPackageConfig(_packagesUri); - _libraryNamesByPath.clear(); - for (var uri in libraryUris) { - _recordAbsoluteUri(uri); - } - } - } - - /// Record the library represented by package: or org-dartlang-app: uris - /// indexed by absolute file: URI. - static void _recordAbsoluteUri(String libraryUri) { - var uri = Uri.parse(libraryUri); - if (uri.scheme == 'dart' || - (uri.scheme == '' && !uri.path.endsWith('.dart'))) { - // We ignore dart: libraries, and non-Dart libraries referenced by path. - // e.g. main.dart.bootstrap - // TODO(alanknight): These should not be showing up in the library list, - // fix _getLibraryRefs and then remove this check. - } else if (uri.scheme == 'org-dartlang-app' || uri.scheme == 'google3') { - // Both currentDirectoryUri and the libraryUri path should have '/' - // separators, so we can join them as url paths to get the absolute file - // url. - var libraryPath = p.url.join(currentDirectoryUri, uri.path.substring(1)); - _libraryNamesByPath[libraryPath] = libraryUri; - } else if (uri.scheme == 'package') { - var libraryPath = _packageConfig.resolve(uri); - _libraryNamesByPath['$libraryPath'] = libraryUri; - } else { - throw ArgumentError.value(libraryUri, 'URI scheme not allowed'); - } - } + DartUri._(this.serverPath); /// Accepts various forms of URI and can convert between forms. /// @@ -124,9 +55,6 @@ class DartUri { throw FormatException('Unsupported URI form', uri); } - /// Returns the dirname for the server URI. - static String _dirForServerUri(String uri) => p.dirname(Uri.parse(uri).path); - /// Construct from a package: URI factory DartUri._fromPackageUri(String uri, {String serverUri}) { var packagePath = 'packages/${uri.substring("package:".length)}'; @@ -139,14 +67,12 @@ class DartUri { /// Construct from a file: URI factory DartUri._fromFileUri(String uri) { - var libraryName = _libraryNamesByPath[uri]; + var libraryName = _resolvedUriToUri[uri]; if (libraryName != null) return DartUri(libraryName); // This is not one of our recorded libraries. throw ArgumentError.value(uri, 'uri', 'Unknown library'); } - DartUri._(this.serverPath); - /// Construct from a path, relative to the directory being served. factory DartUri._fromRelativePath(String uri, {String serverUri}) { uri = uri[0] == '.' ? uri.substring(1) : uri; @@ -158,4 +84,161 @@ class DartUri { } return DartUri._(uri); } + + /// The canonical web server path part of the URI. + /// + /// This is a relative path, which can be used to fetch the corresponding file + /// from the server. For example, 'hello_world/main.dart' or + /// 'packages/path/src/utils.dart'. + final String serverPath; + + static final _logger = Logger('DartUri'); + + /// The way we resolve file: URLs into package: URLs + static PackageConfig _packageConfig; + + /// The way we resolve dart: URLs into org-dartland-sdk: URLs + static TargetLibrariesSpecification _librariesSpec; + + /// SDK installation directory. + /// + /// Directory where the SDK client code built with is installed, + /// + /// For example: `/Users/me/.dart-sdks/2.15.0` + /// + /// Used to resolve SDK urls according to vm_service protocol. + static Uri _sdkDir; + + /// All of the known absolute library paths, indexed by their library URL. + /// + /// Examples: + /// + /// We are assuming that all library uris are coming from + /// https://github.com/dart-lang/sdk/blob/main/runtime/vm/service/service.md#getscripts) + /// and can be translated to their absolute paths and back. + /// + /// dart:html <-> + /// org-dartlang-sdk:///sdk/lib/html/html.dart + /// (not supported, issue: https://github.com/dart-lang/webdev/issues/1457) + /// + /// org-dartlang-app:///example/hello_world/main.dart <-> + /// file:///source/webdev/fixtures/_test/example/hello_world/main.dart, + /// + /// org-dartlang-app:///example/hello_world/part.dart <-> + /// file:///source/webdev/fixtures/_test/example/hello_world/part.dart, + /// + /// package:path/path.dart <-> + /// file:///.pub-cache/hosted/pub.dartlang.org/path-1.8.0/lib/path.dart, + /// + /// package:path/src/path_set.dart <-> + /// file:///.pub-cache/hosted/pub.dartlang.org/path-1.8.0/lib/src/path_set.dart, + static final Map _uriToResolvedUri = {}; + + /// All of the known libraries, indexed by their absolute file URL. + static final Map _resolvedUriToUri = {}; + + /// Returns package, app, or dart uri for a resolved path. + static String toPackageUri(String uri) => _resolvedUriToUri[uri]; + + /// Returns resolved path for a package, app, or dart uri. + static String toResolvedUri(String uri) => _uriToResolvedUri[uri]; + + /// The directory in which we're running. + /// + /// We store this here because for tests we may want to act as if we're + /// running in the directory of a target package, even if the current + /// directory of the tests is actually the main dwds directory. + static String currentDirectory = p.current; + + /// The current directory as a file: Uri, saved here to avoid re-computing. + static String currentDirectoryUri = '${p.toUri(currentDirectory)}'; + + /// Record library and script uris to enable resolving library and script paths. + static Future initialize({Uri sdkDir}) async { + _sdkDir = + sdkDir ?? p.toUri(p.dirname(p.dirname(Platform.resolvedExecutable))); + + var librariesPath = + p.toUri(p.join(_sdkDir.toFilePath(), 'lib', 'libraries.json')); + var packagesUri = p.toUri(p.join(currentDirectory, '.packages')); + + clear(); + _loadLibrariesConfig(librariesPath); + return await _loadPackageConfig(packagesUri); + } + + /// Clear the uri resolution tables. + static void clear() { + _resolvedUriToUri.clear(); + _uriToResolvedUri.clear(); + } + + /// Record all of the libraries, indexed by their absolute file: URI. + static Future recordAbsoluteUris(Iterable libraryUris) async { + for (var uri in libraryUris) { + _recordAbsoluteUri(uri); + } + } + + /// Returns the dirname for the server URI. + static String _dirForServerUri(String uri) => p.dirname(Uri.parse(uri).path); + + /// Load the .packages file associated with the running application so we can + /// resolve file URLs into package: URLs appropriately. + static Future _loadPackageConfig(Uri uri) async { + _packageConfig = await loadPackageConfigUri(uri, onError: (e) { + _logger.warning('Cannot read packages spec: $uri', e); + }); + } + + /// Load and parse libraries.json spec file. + /// Used for resolving `dart:` libraries uris. + static void _loadLibrariesConfig(Uri uri) { + try { + var json = File.fromUri(uri).readAsStringSync(); + _librariesSpec = + LibrariesSpecification.parse(uri, json).specificationFor('dartdevc'); + } on LibrariesSpecificationException catch (e, s) { + _logger.warning('Cannot read libraries spec: $uri', e, s); + } + } + + /// Record the library represented by package: or org-dartlang-app: uris + /// indexed by absolute file: URI. + static void _recordAbsoluteUri(String libraryUri) { + var uri = Uri.parse(libraryUri); + if (uri.scheme.isEmpty && !uri.path.endsWith('.dart')) { + // ignore non-dart files + return; + } + + String libraryPath; + switch (uri.scheme) { + case 'dart': + var libSpec = _librariesSpec?.libraryInfoFor(uri.path); + libraryPath = libSpec?.uri?.path; + libraryPath = + libraryPath?.replaceAll(_sdkDir.path, 'org-dartlang-sdk:///sdk'); + break; + case 'org-dartlang-app': + case 'google3': + // Both currentDirectoryUri and the libraryUri path should have '/' + // separators, so we can join them as url paths to get the absolute file + // url. + libraryPath = p.url.join(currentDirectoryUri, uri.path.substring(1)); + break; + case 'package': + libraryPath = _packageConfig?.resolve(uri).toString(); + break; + default: + throw ArgumentError.value(libraryUri, 'URI scheme not allowed'); + } + + if (libraryPath != null) { + _uriToResolvedUri[libraryUri] = libraryPath; + _resolvedUriToUri[libraryPath] = libraryUri; + } else { + _logger.warning('Unresolved uri: $uri'); + } + } } diff --git a/dwds/lib/src/version.dart b/dwds/lib/src/version.dart index 2a3eeb8d1..9a98cfd00 100644 --- a/dwds/lib/src/version.dart +++ b/dwds/lib/src/version.dart @@ -1,2 +1,2 @@ // Generated code. Do not modify. -const packageVersion = '11.5.2-dev'; +const packageVersion = '12.0.0-dev'; diff --git a/dwds/pubspec.yaml b/dwds/pubspec.yaml index f67bca389..0b77503c7 100644 --- a/dwds/pubspec.yaml +++ b/dwds/pubspec.yaml @@ -1,6 +1,6 @@ name: dwds # Every time this changes you need to run `dart run build_runner build`. -version: 11.5.2-dev +version: 12.0.0-dev homepage: https://github.com/dart-lang/webdev/tree/master/dwds description: >- A service that proxies between the Chrome debug protocol and the Dart VM @@ -10,6 +10,7 @@ environment: sdk: ">=2.15.0 <3.0.0" dependencies: + _fe_analyzer_shared: ^31.0.0 async: ^2.3.0 built_collection: ^5.0.0 built_value: '>=6.7.0 <9.0.0' @@ -32,7 +33,7 @@ dependencies: stack_trace: ^1.10.0 sse: ^4.1.0 uuid: '>=2.0.0 <4.0.0' - vm_service: ^7.4.0 + vm_service: ^7.5.0 web_socket_channel: ^2.0.0 webkit_inspection_protocol: ^1.0.0 diff --git a/dwds/test/chrome_proxy_service_test.dart b/dwds/test/chrome_proxy_service_test.dart index f7be25886..1dcab76a6 100644 --- a/dwds/test/chrome_proxy_service_test.dart +++ b/dwds/test/chrome_proxy_service_test.dart @@ -1295,6 +1295,101 @@ void main() { service.getRetainingPath(null, null, null), throwsRPCError); }); + test('lookupResolvedPackageUris converts package and org-dartlang-app uris', + () async { + var vm = await service.getVM(); + var isolateId = vm.isolates.first.id; + var scriptList = await service.getScripts(isolateId); + + var uris = scriptList.scripts.map((e) => e.uri).toList(); + var resolvedUris = + await service.lookupResolvedPackageUris(isolateId, uris); + + expect( + resolvedUris.uris, + containsAll([ + contains('/_test/example/hello_world/main.dart'), + contains('/lib/path.dart'), + contains('/lib/src/path_set.dart'), + ])); + }); + + test('lookupResolvedPackageUris does not translate non-existent paths', + () async { + var vm = await service.getVM(); + var isolateId = vm.isolates.first.id; + + var resolvedUris = await service.lookupResolvedPackageUris(isolateId, [ + 'package:does/not/exist.dart', + 'dart:does_not_exist', + 'file:///does_not_exist.dart', + ]); + expect(resolvedUris.uris, [null, null, null]); + }); + + test('lookupResolvedPackageUris translates dart uris', () async { + var vm = await service.getVM(); + var isolateId = vm.isolates.first.id; + + var resolvedUris = await service.lookupResolvedPackageUris(isolateId, [ + 'dart:html', + 'dart:async', + ]); + + expect(resolvedUris.uris, [ + 'org-dartlang-sdk:///sdk/lib/html/dart2js/html_dart2js.dart', + 'org-dartlang-sdk:///sdk/lib/async/async.dart', + ]); + }); + + test('lookupPackageUris finds package and org-dartlang-app paths', + () async { + var vm = await service.getVM(); + var isolateId = vm.isolates.first.id; + var scriptList = await service.getScripts(isolateId); + + var uris = scriptList.scripts.map((e) => e.uri).toList(); + var resolvedUris = + await service.lookupResolvedPackageUris(isolateId, uris); + + var packageUris = await service.lookupPackageUris( + isolateId, resolvedUris.uris as List); + expect( + packageUris.uris, + containsAll([ + 'org-dartlang-app:///example/hello_world/main.dart', + 'package:path/path.dart', + 'package:path/src/path_set.dart', + ])); + }); + + test('lookupPackageUris does not translate non-existent paths', () async { + var vm = await service.getVM(); + var isolateId = vm.isolates.first.id; + + var resolvedUris = await service.lookupPackageUris(isolateId, [ + 'org-dartlang-sdk:///sdk/does/not/exist.dart', + 'does_not_exist.dart', + 'file:///does_not_exist.dart', + ]); + expect(resolvedUris.uris, [null, null, null]); + }); + + test('lookupPackageUris translates dart uris', () async { + var vm = await service.getVM(); + var isolateId = vm.isolates.first.id; + + var resolvedUris = await service.lookupPackageUris(isolateId, [ + 'org-dartlang-sdk:///sdk/lib/html/dart2js/html_dart2js.dart', + 'org-dartlang-sdk:///sdk/lib/async/async.dart', + ]); + + expect(resolvedUris.uris, [ + 'dart:html', + 'dart:async', + ]); + }); + test('registerService', () async { await expectLater( service.registerService('ext.foo.bar', null), throwsRPCError); diff --git a/dwds/test/dart_uri_test.dart b/dwds/test/dart_uri_test.dart index d73a815ff..1dbd2a5e3 100644 --- a/dwds/test/dart_uri_test.dart +++ b/dwds/test/dart_uri_test.dart @@ -5,8 +5,11 @@ // @dart = 2.9 @TestOn('vm') +import 'dart:io'; + import 'package:dwds/src/loaders/strategy.dart'; import 'package:dwds/src/utilities/dart_uri.dart'; +import 'package:path/path.dart' as p; import 'package:test/test.dart'; import 'fixtures/fakes.dart'; @@ -47,5 +50,64 @@ void main() { var uri = DartUri('http://localhost:8080/web/main.dart'); expect(uri.serverPath, 'web/main.dart'); }); + + group('initialized with current SDK directory', () { + setUpAll(() async { + await DartUri.initialize(); + await DartUri.recordAbsoluteUris(['dart:io', 'dart:html']); + }); + + tearDownAll(DartUri.clear); + + test('can resolve uris', () { + var resolved = DartUri.toResolvedUri('dart:io'); + expect(resolved, 'org-dartlang-sdk:///sdk/lib/io/io.dart'); + }); + + test('can unresolve uris', () { + var unresolved = + DartUri.toPackageUri('org-dartlang-sdk:///sdk/lib/io/io.dart'); + expect(unresolved, 'dart:io'); + }); + }); + + group('initialized with other SDK directory', () { + Directory outputDir; + + setUpAll(() async { + var systemTempDir = Directory.systemTemp; + outputDir = systemTempDir.createTempSync('foo bar'); + + var sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable)); + var librariesPath = p.join(sdkDir, 'lib', 'libraries.json'); + + // copy libraries.json to a new location mimicking SDK directory structure. + var fakeSdkDir = outputDir.path; + var fakeLibrariesDir = p.join(fakeSdkDir, 'lib'); + var fakeLibrariesPath = p.join(fakeLibrariesDir, 'libraries.json'); + + Directory(fakeLibrariesDir).createSync(); + File(librariesPath).copySync(fakeLibrariesPath); + + await DartUri.initialize(sdkDir: Uri.file(fakeSdkDir)); + await DartUri.recordAbsoluteUris(['dart:io', 'dart:html']); + }); + + tearDownAll(() async { + DartUri.clear(); + await outputDir?.delete(recursive: true); + }); + + test('can resolve uris', () { + var resolved = DartUri.toResolvedUri('dart:io'); + expect(resolved, 'org-dartlang-sdk:///sdk/lib/io/io.dart'); + }); + + test('can unresolve uris', () { + var unresolved = + DartUri.toPackageUri('org-dartlang-sdk:///sdk/lib/io/io.dart'); + expect(unresolved, 'dart:io'); + }); + }); }); }