[dds/dap] Remove macro support from debug adapters/DAP

This removes support for dart-macro+file URIs (and using URIs in the DAP protocol in general) and all related code/tests.

Change-Id: I7cbbcc8463e7c352517d5bd58e8cdf63c7d23c0d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/460940
Reviewed-by: Jessy Yameogo <yjessy@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Danny Tuppeny
2025-11-17 09:05:57 -08:00
committed by Commit Queue
parent 5436fcde7d
commit 0a15269bbb
18 changed files with 91 additions and 268 deletions
+1
View File
@@ -1,6 +1,7 @@
# 5.2.0-wip
- [DAP] `Stopped(reason: 'entry')` events will no longer be lost if an isolate has not yet reached the `PauseStart` state when connecting to the VM.
- **Breaking change:** [DAP] Support for the custom `supportsDartUris` client capability and `dart-macro+file:///` mappings that supported the Dart macros experiment have been removed.
# 5.1.0
- Update to version 2.1 of the DDS protocol.
+31 -66
View File
@@ -1240,7 +1240,7 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
// Ensure that we stop watching for a VM Service info file if we are using
// these utils.
if (this case VmServiceInfoFileUtils vmServiceUtils) {
if (this case final VmServiceInfoFileUtils vmServiceUtils) {
vmServiceUtils.stopWaitingForVmServiceInfoFile();
}
@@ -1319,32 +1319,29 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
return false;
}
final packageFileLikeUri = await thread.resolveUriToPackageLibPath(uri);
if (packageFileLikeUri == null) {
final packageFileUri = await thread.resolveUriToPackageLibPath(uri);
if (packageFileUri == null) {
return false;
}
return !isInUserProject(packageFileLikeUri);
return !isInUserProject(packageFileUri);
}
/// Checks whether [uri] is inside the users project. This is used to support
/// debugging "Just My Code" (via [isExternalPackageLibrary]) and also for
/// stack trace highlighting, where non-user code will be faded.
/// Checks whether [targetUri] is inside the users project. This is used to
/// support debugging "Just My Code" (via [isExternalPackageLibrary]) and also
/// for stack trace highlighting, where non-user code will be faded.
bool isInUserProject(Uri targetUri) {
if (!isSupportedFileScheme(targetUri)) {
if (!targetUri.isScheme('file')) {
return false;
}
// We could already be 'file', or we could be another supported file scheme
// like dart-macro+file, but we can only call toFilePath() on a file URI
// and we use the equivalent path to decide if this is within the workspace.
var targetPath = targetUri.replace(scheme: 'file').toFilePath();
// Always compare paths case-insensitively to avoid any issues where APIs
// may have returned different casing (e.g. Windows drive letters). It's
// almost certain a user wouldn't have a "local" package and an "external"
// package with paths differing only be case.
targetPath = targetPath.toLowerCase();
final targetPath = targetUri
.toFilePath()
// Always compare paths case-insensitively to avoid any issues where APIs
// may have returned different casing (e.g. Windows drive letters). It's
// almost certain a user wouldn't have a "local" package and an "external"
// package with paths differing only be case.
.toLowerCase();
return projectPaths
.map((projectPath) => projectPath.toLowerCase())
@@ -1602,9 +1599,7 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
final path = args.source.path;
final name = args.source.name;
final uri = path != null
? normalizeUri(fromClientPathOrUri(path)).toString()
: name!;
final uri = path != null ? normalizeUri(Uri.file(path)).toString() : name!;
// Use a completer to track when the response is sent, so any events related
// to these breakpoints are not sent before the client has the IDs.
@@ -1681,7 +1676,7 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
}
/// Converts a URI in the form org-dartlang-sdk:///sdk/lib/collection/hash_set.dart
/// to a local file-like URI based on the current SDK.
/// to a local file URI based on the current SDK.
Uri? convertOrgDartlangSdkToPath(Uri uri) {
// org-dartlang-sdk URIs can be in multiple forms:
//
@@ -2340,14 +2335,14 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
final framePaths = await Future.wait(frameLocations.map((frame) async {
final uri = frame?.uri;
if (uri == null) return null;
if (isSupportedFileScheme(uri)) {
if (uri.isScheme('file')) {
return (uri: uri, isUserCode: isInUserProject(uri));
}
if (thread == null || !isResolvableUri(uri)) return null;
try {
final fileLikeUri = await thread.resolveUriToPath(uri);
return fileLikeUri != null
? (uri: fileLikeUri, isUserCode: isInUserProject(fileLikeUri))
final fileUri = await thread.resolveUriToPath(uri);
return fileUri != null
? (uri: fileUri, isUserCode: isInUserProject(fileUri))
: null;
} catch (e, s) {
// Swallow errors for the same reason noted above.
@@ -2363,8 +2358,8 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
final uri = frameLocation?.uri;
final framePathInfo = framePaths[i];
// A file-like URI ('file://' or 'dart-macro+file://').
final fileLikeUri = framePathInfo?.uri;
// A file URI.
final fileUri = framePathInfo?.uri;
// Default to true so that if we don't know whether this is user-project
// then we leave the formatting as-is and don't fade anything out.
@@ -2372,7 +2367,7 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
// For the name, we usually use the package URI, but if we only had a file
// URI to begin with, try to make it relative to cwd so it's not so long.
final name = uri != null && fileLikeUri != null
final name = uri != null && fileUri != null
? (uri.isScheme('file')
? _converter.convertToRelativePath(uri.toFilePath())
: uri.toString())
@@ -2397,8 +2392,7 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
continue;
}
final clientPath =
fileLikeUri != null ? toClientPathOrUri(fileLikeUri) : null;
final clientPath = fileUri != null ? toClientPathOrUri(fileUri) : null;
events.add(
OutputEventBody(
category: category,
@@ -2645,19 +2639,19 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
return;
}
// Doesn't need resolving if already file-like.
if (isSupportedFileScheme(uri)) {
// Doesn't need resolving if already file.
if (uri.isScheme('file')) {
return;
}
final fileLikeUri = await thread.resolveUriToPath(uri);
if (fileLikeUri != null) {
final fileUri = await thread.resolveUriToPath(uri);
if (fileUri != null) {
// Convert:
// uri -> resolvedUri
// fileUri -> resolvedFileUri
final resolvedFieldName =
'resolved${field.substring(0, 1).toUpperCase()}${field.substring(1)}';
data[resolvedFieldName] = fileLikeUri.toString();
data[resolvedFieldName] = fileUri.toString();
}
}
@@ -2888,47 +2882,18 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
}
}
/// Whether the current client supports URIs in place of file paths, including
/// file-like URIs that are not the 'file' scheme (such as 'dart-macro+file').
bool get clientSupportsUri => _initializeArgs?.supportsDartUris ?? false;
/// Returns whether [uri] is a file-like URI scheme that is supported by the
/// client.
///
/// Returning `true` here does not guarantee that the client supports URIs,
/// the caller should also check [clientSupportsUri].
bool isSupportedFileScheme(Uri uri) {
return uri.isScheme('file') ||
// Handle all file-like schemes that end '+file' like
// 'dart-macro+file://'.
(clientSupportsUri && uri.scheme.endsWith('+file'));
}
/// Converts a URI into a form that can be used by the client.
///
/// If the client supports URIs (like VS Code), it will be returned unchanged
/// but otherwise it will be the `toFilePath()` equivalent if a 'file://' URI
/// and otherwise `null`.
/// Returns `null` if the uri is not a supported file scheme.
String? toClientPathOrUri(Uri? uri) {
if (uri == null) {
return null;
} else if (clientSupportsUri) {
return uri.toString();
} else if (uri.isScheme('file')) {
return uri.toFilePath();
} else {
return null;
}
}
/// Converts a String used by the client as a path/URI into a [Uri].
Uri fromClientPathOrUri(String filePathOrUriString) {
var uri = Uri.tryParse(filePathOrUriString);
if (uri == null || !isSupportedFileScheme(uri)) {
uri = Uri.file(filePathOrUriString);
}
return uri;
}
}
/// An implementation of [LaunchRequestArguments] that includes all fields used
-7
View File
@@ -260,13 +260,6 @@ mixin FileUtils {
final filePath = uri.toFilePath();
final normalizedPath = normalizePath(filePath);
return Uri.file(normalizedPath);
} else if (uri.scheme.endsWith('+file')) {
// For virtual file schemes, we need to replace the scheme to use
// toFilePath() so we can normalise the path, then convert back.
final originalScheme = uri.scheme;
final filePath = uri.replace(scheme: 'file').toFilePath();
final normalizedPath = normalizePath(filePath);
return Uri.file(normalizedPath).replace(scheme: originalScheme);
} else {
return uri;
}
+21 -24
View File
@@ -922,7 +922,7 @@ class IsolateManager {
var userMessage = error is vm.RPCError
? error.details ?? error.toString()
: error.toString();
var terseMessageMatch =
final terseMessageMatch =
_terseBreakpointFailureRegex.firstMatch(userMessage);
if (terseMessageMatch != null) {
userMessage = terseMessageMatch.group(1) ?? userMessage;
@@ -1298,13 +1298,13 @@ class ThreadInfo with FileUtils {
/// tokenPos) can share the same response.
final _scripts = <String, Future<vm.Script>>{};
/// A cache of requests (Futures) to resolve URIs to their file-like URIs.
/// A cache of requests (Futures) to resolve URIs to their file URIs.
///
/// Used so that multiple requests that require them (for example looking up
/// locations for stack frames from tokenPos) can share the same response.
///
/// Keys are URIs in string form.
/// Values are file-like URIs (file: or similar, such as dart-macro+file:).
/// Values are file URIs.
final _resolvedPaths = <String, Future<Uri?>>{};
/// Whether this isolate has an in-flight user-initiated resume request that
@@ -1370,7 +1370,7 @@ class ThreadInfo with FileUtils {
///
/// sdk-path/lib/core/print.dart -> dart:core/print.dart
/// c:\foo\bar -> package:foo/bar
/// dart-macro+file:///c:/foo/bar -> dart-macro+package:foo/bar
/// file:///c:/foo/bar -> package:foo/bar
///
/// This is required so that when the user sets a breakpoint in an SDK source
/// (which they may have navigated to via the Analysis Server) we generate a
@@ -1417,7 +1417,7 @@ class ThreadInfo with FileUtils {
}
}
/// Batch resolves source URIs from the VM to a file-like URI for the package
/// Batch resolves source URIs from the VM to a file URI for the package
/// lib folder.
///
/// This method is more performant than repeatedly calling
@@ -1437,7 +1437,7 @@ class ThreadInfo with FileUtils {
.toList();
}
/// Batch resolves source URIs from the VM to a file-like URI.
/// Batch resolves source URIs from the VM to a file URI.
///
/// This method is more performant than repeatedly calling [resolveUriToPath]
/// because it resolves multiple URIs in a single request to the VM.
@@ -1513,7 +1513,7 @@ class ThreadInfo with FileUtils {
// because they were either filtered out of [requiredUris] because they were
// already there, or we then populated completers for them above.
final futures = uris.map((uri) async {
if (_manager._adapter.isSupportedFileScheme(uri)) {
if (uri.isScheme('file')) {
return uri;
} else {
return await _resolvedPaths[uri.toString()];
@@ -1562,11 +1562,10 @@ class ThreadInfo with FileUtils {
/// would not be changed.
final _libraryIsDebuggableById = <String, bool>{};
/// Resolves a source URI to a file-like URI for the lib folder of its
/// Resolves a source URI to a file URI for the lib folder of its
/// package.
///
/// package:foo/a/b/c/d.dart -> file:///code/packages/foo/lib
/// dart-macro+package:foo/a/b/c/d.dart -> dart-macro+file:///code/packages/foo/lib
///
/// This method is an optimisation over calling [resolveUriToPath] where only
/// the package root is required (for example when determining whether a
@@ -1578,7 +1577,7 @@ class ThreadInfo with FileUtils {
return result.first;
}
/// Resolves a source URI from the VM to a file-like URI.
/// Resolves a source URI from the VM to a file URI.
///
/// dart:core/print.dart -> sdk-path/lib/core/print.dart
///
@@ -1596,8 +1595,6 @@ class ThreadInfo with FileUtils {
int storeData(Object data) => _manager.storeData(this, data);
Uri? _convertPathToGoogle3Uri(Uri input) {
// TODO(dantup): Do we need to handle non-file here? Eg. can we have
// dart-macro+file:/// for a google3 path?
if (!input.isScheme('file')) {
return null;
}
@@ -1605,8 +1602,8 @@ class ThreadInfo with FileUtils {
const search = '/google3/';
if (inputPath.startsWith('/google') && inputPath.contains(search)) {
var idx = inputPath.indexOf(search);
var remainingPath = inputPath.substring(idx + search.length);
final idx = inputPath.indexOf(search);
final remainingPath = inputPath.substring(idx + search.length);
return Uri(
scheme: 'google3',
host: '',
@@ -1617,17 +1614,17 @@ class ThreadInfo with FileUtils {
return null;
}
/// Converts a VM-returned URI to a file-like URI, taking org-dartlang-sdk
/// Converts a VM-returned URI to a file URI, taking org-dartlang-sdk
/// schemes into account.
///
/// Supports file-like URIs and org-dartlang-sdk:// URIs.
/// Supports file URIs and org-dartlang-sdk:// URIs.
Uri? _convertUriToFilePath(Uri? input) {
if (input == null) {
return null;
} else if (_manager._adapter.isSupportedFileScheme(input)) {
} else if (input.isScheme('file')) {
return input;
} else {
// TODO(dantup): UriConverter should be upgraded to use file-like URIs
// TODO(dantup): UriConverter should be upgraded to use file URIs
// instead of paths, but that might be breaking because it's used
// outside of this package?
final uriConverter = _manager._adapter.uriConverter();
@@ -1644,8 +1641,8 @@ class ThreadInfo with FileUtils {
///
/// [uri] should be the equivalent package: URI and is used to know how many
/// segments to remove from the file path to get to the lib folder.
Uri? _trimPathToLibFolder(Uri? fileLikeUri, Uri uri) {
if (fileLikeUri == null) {
Uri? _trimPathToLibFolder(Uri? fileUri, Uri uri) {
if (fileUri == null) {
return null;
}
@@ -1657,14 +1654,14 @@ class ThreadInfo with FileUtils {
// least as many segments as the path of the URI.
assert(uri.pathSegments.length > libraryPathSegments);
if (uri.pathSegments.length <= libraryPathSegments) {
return fileLikeUri;
return fileUri;
}
// Strip off the correct number of segments to the resulting path points
// to the root of the package:/ URI.
final keepSegments = fileLikeUri.pathSegments.length - libraryPathSegments;
return fileLikeUri.replace(
pathSegments: fileLikeUri.pathSegments.sublist(0, keepSegments));
final keepSegments = fileUri.pathSegments.length - libraryPathSegments;
return fileUri.replace(
pathSegments: fileUri.pathSegments.sublist(0, keepSegments));
}
/// Clears all temporary stored for this thread. This includes:
+2 -3
View File
@@ -609,8 +609,7 @@ class ProtocolConverter {
final uriIsPackage = uri?.isScheme('package') ?? false;
final sourcePathUri =
uri != null ? await thread.resolveUriToPath(uri) : null;
var canShowSource =
sourcePathUri != null && _adapter.isSupportedFileScheme(sourcePathUri);
var canShowSource = sourcePathUri != null && sourcePathUri.isScheme('file');
// If we don't have a local source file but the source is a "dart:" uri we
// might still be able to download the source from the VM.
@@ -639,7 +638,7 @@ class ProtocolConverter {
}
// LSP uses 0 for unknown lines.
var (line, col) = lineCol ?? (0, 0);
final (line, col) = lineCol ?? (0, 0);
// If a source would be considered not-debuggable (for example it's in the
// SDK and debugSdkLibraries=false) then we should also mark it as
@@ -78,7 +78,7 @@ class PacketTransformer extends StreamTransformerBase<List<int>, String> {
/// Whether [buffer] ends in '\r\n\r\n'.
static bool _endsWithCrLfCrLf(List<int> buffer) {
var l = buffer.length;
final l = buffer.length;
return l > 4 &&
buffer[l - 1] == 10 &&
buffer[l - 2] == 13 &&
+9 -13
View File
@@ -9,12 +9,10 @@ import 'package:vm_service/vm_service.dart' as vm;
import '../rpc_error_codes.dart';
/// Returns whether this URI is something that can be resolved to a file-like
/// Returns whether this URI is something that can be resolved to a file
/// URI via the VM Service.
bool isResolvableUri(Uri uri) {
return !uri.isScheme('file') &&
// Custom-scheme versions of file, like `dart-macro+file://`
!uri.scheme.endsWith('+file') &&
!uri.isScheme('http') &&
!uri.isScheme('https') &&
// Parsed stack frames may have URIs with no scheme and the text
@@ -62,9 +60,7 @@ bool _isDartUri(Uri uri) {
// Only accept package: and file: URIs if they end with .dart.
// - package:foo/foo.dart
// - file:///c:/foo/bar.dart
if (uri.isScheme('package') ||
uri.isScheme('file') ||
uri.scheme.endsWith('+file')) {
if (uri.isScheme('package') || uri.isScheme('file')) {
return uri.path.endsWith('.dart');
}
@@ -101,16 +97,16 @@ final _stackFrameLocationPattern =
/// It should not be assumed that if a value is returned that the input
/// was necessarily a stack frame.
StackFrameLocation? _parseStackFrame(String input) {
var match = _stackFrameLocationPattern.firstMatch(input);
final match = _stackFrameLocationPattern.firstMatch(input);
if (match == null) return null;
var uriMatch = match.group(1);
var lineMatch = match.group(2);
var colMatch = match.group(3);
final uriMatch = match.group(1);
final lineMatch = match.group(2);
final colMatch = match.group(3);
var uri = uriMatch != null ? Uri.tryParse(uriMatch) : null;
var line = lineMatch != null ? int.tryParse(lineMatch) : null;
var col = colMatch != null ? int.tryParse(colMatch) : null;
final line = lineMatch != null ? int.tryParse(lineMatch) : null;
final col = colMatch != null ? int.tryParse(colMatch) : null;
if (uriMatch == null || uri == null) {
return null;
@@ -118,7 +114,7 @@ StackFrameLocation? _parseStackFrame(String input) {
// If the URI has no scheme, assume a relative path from Directory.current.
if (!uri.hasScheme && path.isRelative(uriMatch)) {
var currentDirectoryPath = Directory.current.path;
final currentDirectoryPath = Directory.current.path;
if (currentDirectoryPath.isNotEmpty) {
uri = Uri.file(path.join(currentDirectoryPath, uriMatch));
}
@@ -20,8 +20,7 @@ main() {
tearDown(() => dap.tearDown());
group('debug mode breakpoints', () {
testWithUriConfigurations(() => dap, 'stops at a line breakpoint',
() async {
test('stops at a line breakpoint', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleBreakpointProgram);
final breakpointLine = lineWith(testFile, breakpointMarker);
@@ -29,8 +28,7 @@ main() {
await client.hitBreakpoint(testFile, breakpointLine);
});
testWithUriConfigurations(() => dap, 'resolves modified breakpoints',
() async {
test('resolves modified breakpoints', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleMultiBreakpointProgram);
final breakpointLine = lineWith(testFile, breakpointMarker);
@@ -75,8 +73,7 @@ main() {
expect(resolvedBreakpoints, addedBreakpoints);
});
testWithUriConfigurations(
() => dap, 'provides reason for failed breakpoints', () async {
test('provides reason for failed breakpoints', () async {
final client = dap.client;
final testFile = dap.createTestFile(debuggerPauseProgram);
final invalidBreakpointLine = 9999;
@@ -96,8 +93,8 @@ main() {
});
// Set the breakpoint and also collect the original reason.
var bps = await client.setBreakpoint(testFile, invalidBreakpointLine);
var bp = bps.breakpoints.single;
final bps = await client.setBreakpoint(testFile, invalidBreakpointLine);
final bp = bps.breakpoints.single;
breakpointReasons.add('${bp.reason}: ${bp.message}');
// Wait up to a few seconds for the change events to come through to
@@ -118,9 +115,7 @@ main() {
await breakpointChangedSubscription.cancel();
});
testWithUriConfigurations(
() => dap, 'provides reason for not-yet-resolved breakpoints',
() async {
test('provides reason for not-yet-resolved breakpoints', () async {
final client = dap.client;
final testFile = dap.createTestFile(debuggerPauseProgram);
final breakpointLine = lineWith(testFile, breakpointMarker);
@@ -132,7 +127,7 @@ main() {
], eagerError: true);
// Set a breakpoint and verify the result.
var bps = await client.setBreakpoint(testFile, breakpointLine);
final bps = await client.setBreakpoint(testFile, breakpointLine);
expect(bps.breakpoints.single.reason, 'pending');
expect(bps.breakpoints.single.message,
'Breakpoint has not yet been resolved');
@@ -42,9 +42,7 @@ main() {
]);
});
testWithUriConfigurations(
() => dap, 'pauses on uncaught exceptions when mode=Unhandled',
() async {
test('pauses on uncaught exceptions when mode=Unhandled', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleThrowingProgram);
@@ -97,7 +97,7 @@ void main(List<String> args) async {
}
''');
var outputEvents = await dap.client.collectOutput(file: testFile);
final outputEvents = await dap.client.collectOutput(file: testFile);
// Skip the first two lines because it's the VM Service connection info.
final output = outputEvents.skip(2).map((e) => e.output).join();
@@ -16,8 +16,7 @@ main() {
tearDown(() => dap.tearDown());
group('debug mode stack trace', () {
testWithUriConfigurations(
() => dap, 'includes expected names and async boundaries', () async {
test('includes expected names and async boundaries', () async {
final client = dap.client;
final testFile = dap.createTestFile(simpleAsyncProgram);
final breakpointLine = lineWith(testFile, breakpointMarker);
+5 -42
View File
@@ -11,7 +11,6 @@ import 'package:dap/dap.dart';
import 'package:dds/src/dap/adapters/dart.dart';
import 'package:dds/src/dap/logging.dart';
import 'package:dds/src/dap/protocol_stream.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart' as vm;
@@ -34,17 +33,6 @@ class DapTestClient {
final _eventController = StreamController<Event>.broadcast();
int _seq = 1;
/// Whether to advertise support for URIs and expect them in stack traces
/// and breakpoint updates from the debug adapter.
bool supportUris = false;
/// Whether to send URIs in breakpoints even for standard file paths.
///
/// This is separate from [supportUris] so that we can test when support is
/// enabled that the client can still send paths. This is because VS Code will
/// send file paths for file:/// documents even though URIs are supported.
bool sendFileUris = false;
/// Functions provided by tests to handle requests that may come from the
/// server (such as `runInTerminal`).
final _serverRequestHandlers =
@@ -314,7 +302,6 @@ class DapTestClient {
adapterID: 'test',
supportsRunInTerminalRequest: supportsRunInTerminalRequest,
supportsProgressReporting: supportsProgressReporting,
supportsDartUris: supportUris,
)),
sendRequest(
SetExceptionBreakpointsArguments(
@@ -607,7 +594,7 @@ class DapTestClient {
Future<T> _logIfSlow<T>(String name, Future<T> future) {
// Use a loop to periodically check so that we can exit earlier if
// the test is being torn down.
var endTime = DateTime.now().add(_requestWarningDuration);
final endTime = DateTime.now().add(_requestWarningDuration);
late Timer timer;
timer = Timer.periodic(Duration(milliseconds: 100), (_) {
// Shutting down, so just abort.
@@ -760,34 +747,12 @@ extension DapTestClientExtension on DapTestClient {
return stop;
}
/// Converts a file path to a URI to send to the debug adapter if
/// [sendFileUris] is `true` and otherwise returns as-is.
String toPathOrUri(String filePath) {
assert(path.isAbsolute(filePath));
return sendFileUris ? Uri.file(filePath).toString() : filePath;
}
/// Converts a string from the debug adapter back to a file path, asserting
/// it was a URI if [useUris] is `true`, and not if [useUris] is `false`.
String fromPathOrUri(String filePathOrUri) {
if (!supportUris) {
// Expect an absolute path.
assert(path.isAbsolute(filePathOrUri));
return filePathOrUri;
}
// Expect a URI with file:/// scheme.
final uri = Uri.parse(filePathOrUri);
assert(uri.isScheme('file'));
return uri.toFilePath();
}
/// Sets a breakpoint at [line] in [file].
Future<SetBreakpointsResponseBody> setBreakpoint(File file, int line,
{String? condition}) async {
final response = await sendRequest(
SetBreakpointsArguments(
source: Source(path: toPathOrUri(_normalizeBreakpointPath(file.path))),
source: Source(path: _normalizeBreakpointPath(file.path)),
breakpoints: [SourceBreakpoint(line: line, condition: condition)],
),
);
@@ -802,7 +767,7 @@ extension DapTestClientExtension on DapTestClient {
File file, List<int> lines) async {
final response = await sendRequest(
SetBreakpointsArguments(
source: Source(path: toPathOrUri(_normalizeBreakpointPath(file.path))),
source: Source(path: _normalizeBreakpointPath(file.path)),
breakpoints: lines.map((line) => SourceBreakpoint(line: line)).toList(),
),
);
@@ -866,8 +831,7 @@ extension DapTestClientExtension on DapTestClient {
initialize(),
sendRequest(
SetBreakpointsArguments(
source:
Source(path: toPathOrUri(_normalizeBreakpointPath(file.path))),
source: Source(path: _normalizeBreakpointPath(file.path)),
breakpoints: [
SourceBreakpoint(
line: line,
@@ -950,8 +914,7 @@ extension DapTestClientExtension on DapTestClient {
final frame = result.stackFrames[0];
if (file != null) {
expect(fromPathOrUri(frame.source!.path!),
equals(uppercaseDriveLetter(file.path)));
expect(frame.source!.path!, equals(uppercaseDriveLetter(file.path)));
}
if (sourceName != null) {
expect(frame.source?.name, equals(sourceName));
+1 -27
View File
@@ -8,7 +8,6 @@ import 'dart:io';
import 'package:dap/dap.dart';
import 'package:dds/src/dap/logging.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
@@ -320,37 +319,12 @@ environment:
}
}
/// A helper to run [testFunc] as a test in various configurations of URI
/// support.
///
/// This should be used to ensure coverage of each configuration where
/// breakpoints and stack traces are being tested.
@isTest
void testWithUriConfigurations(
DapTestSession Function() dapFunc,
String name,
FutureOr<void> Function() testFunc,
) {
for (final (supportUris, sendFileUris) in [
(false, false),
(true, false),
(true, true),
]) {
test('$name (supportUris: $supportUris, sendFileUris: $sendFileUris)', () {
final client = dapFunc().client;
client.supportUris = supportUris;
client.sendFileUris = sendFileUris;
return testFunc();
});
}
}
/// Sets the casing of a drive letter according to [casing].
String setDriveLetterCasing(String path, DriveLetterCasing? casing) {
if (!Platform.isWindows || path.isEmpty) {
return path;
}
var (driveLetter, rest) = (path.substring(0, 1), path.substring(1));
final (driveLetter, rest) = (path.substring(0, 1), path.substring(1));
return switch (casing) {
DriveLetterCasing.uppercase => driveLetter.toUpperCase() + rest,
DriveLetterCasing.lowercase => driveLetter.toLowerCase() + rest,
@@ -28,6 +28,9 @@ main() {
'breakpoint requests: $breakpointCasing)', () async {
// Set the correct casing of drive letters for this test.
if (actualCwdCasing != null) {
// TODO(dantup): This setting of Directory.current can cause
// test failures when running multiple tests with `dart test`
// due to concurrency!
Directory.current = Directory(
setDriveLetterCasing(Directory.current.path, actualCwdCasing),
);
+1 -49
View File
@@ -39,7 +39,7 @@ main() {
int? col,
]) {
for (var input in inputs) {
var frame = parseDartStackFrame(input);
final frame = parseDartStackFrame(input);
expect(frame, isNotNull, reason: 'Failed to parse "$input"');
expect(frame!.uri, uri, reason: 'Failed to parse URI from "$input"');
expect(frame.line, line, reason: 'Failed to parse line from "$input"');
@@ -141,30 +141,6 @@ main() {
});
});
group('Posix dart-macro+file URIs', () {
test('without line/col', () {
expectFrames(
[
'#1 A.b (dart-macro+file:///a/b/c/d.dart)',
'flutter: #1 A.b (dart-macro+file:///a/b/c/d.dart)',
],
Uri.parse('dart-macro+file:///a/b/c/d.dart'),
);
});
test('with line/col', () {
expectFrames(
[
'#1 A.b (dart-macro+file:///a/b/c/d.dart:1:2)',
'flutter: #1 A.b (dart-macro+file:///a/b/c/d.dart:1:2)',
],
Uri.parse('dart-macro+file:///a/b/c/d.dart'),
1,
2,
);
});
});
group('Posix relative paths', () {
test('without line/col', () {
expectFrames(
@@ -227,30 +203,6 @@ main() {
});
});
group('Windows dart-macro+file URIs', () {
test('without line/col', () {
expectFrames(
[
'#1 A.b (dart-macro+file:///a:/b/c/d.dart)',
'flutter: #1 A.b (dart-macro+file:///a:/b/c/d.dart)',
],
Uri.parse('dart-macro+file:///a:/b/c/d.dart'),
);
});
test('with line/col', () {
expectFrames(
[
'#1 A.b (dart-macro+file:///a:/b/c/d.dart:1:2)',
'flutter: #1 A.b (dart-macro+file:///a:/b/c/d.dart:1:2)',
],
Uri.parse('dart-macro+file:///a:/b/c/d.dart'),
1,
2,
);
});
});
group('Windows relative paths', () {
test('without line/col', () {
expectFrames(
+4
View File
@@ -1,3 +1,7 @@
## 1.5.0-wip
- Removed the `supportsDartUris` flag from `DartInitializeRequestArguments` because it was only required to support the (now removed) Dart macros experiment.
## 1.4.0
- Updated all generated classes using the latest published version of the DAP spec.
+1 -17
View File
@@ -58,33 +58,17 @@ abstract class ToJsonable {
/// A custom version of [InitializeRequestArguments] that adds custom Dart
/// capabilities not covered by the DAP spec.
class DartInitializeRequestArguments extends InitializeRequestArguments {
/// Whether the client supports URIs in places where we would normally send
/// file paths.
///
/// This may be replaced by something standard DAP in future
/// https://github.com/microsoft/debug-adapter-protocol/issues/444
final bool supportsDartUris;
/// A reader for protocol arguments that throws detailed exceptions if
/// arguments aren't of the correct type.
static final arg = DebugAdapterArgumentReader('initialize');
DartInitializeRequestArguments({
required super.adapterID,
this.supportsDartUris = false,
super.supportsRunInTerminalRequest,
super.supportsProgressReporting,
});
DartInitializeRequestArguments.fromMap(super.obj)
: supportsDartUris = arg.read<bool?>(obj, 'supportsDartUris') ?? false,
super.fromMap();
@override
Map<String, Object?> toJson() => {
...super.toJson(),
if (supportsDartUris) 'supportsDartUris': supportsDartUris,
};
DartInitializeRequestArguments.fromMap(super.obj) : super.fromMap();
static DartInitializeRequestArguments fromJson(Map<String, Object?> obj) =>
DartInitializeRequestArguments.fromMap(obj);
+1 -1
View File
@@ -1,5 +1,5 @@
name: dap
version: 1.4.0
version: 1.5.0-wip
description: >-
A package of classes that are generated from the DAP specifications along with
their generating code.