Skip to content

Commit eb58fe1

Browse files
authored
[flutter_tools] Include more details in structured errors sent to a DAP client (#150698)
The debug adapter converts Flutter's structured errors into a text format to be sent to the debug client and shown in the console. When an error is not the first error since the last reload, it is shown as just a summary (since it may be caused by a prior error). In this mode, the filter was causing some important information (the erroring widget) to be omitted. This tweaks the logic to include child nodes of a `DiagnosticBlock` in this mode. Fixes Dart-Code/Dart-Code#4743 ## Before: ![image](https://github.com/flutter/flutter/assets/1078012/46ccd2ef-b165-46b4-a8ab-4473f82a904c) ## After: ![image](https://github.com/flutter/flutter/assets/1078012/232f866e-cf6f-4016-9d1d-49323204da04)
1 parent ed8dde8 commit eb58fe1

File tree

2 files changed

+61
-1
lines changed

2 files changed

+61
-1
lines changed

packages/flutter_tools/lib/src/debug_adapters/error_formatter.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,11 @@ class FlutterErrorFormatter {
118118
final bool allChildrenAreLeaf = node.children.isNotEmpty &&
119119
!node.children.any((_ErrorNode child) => child.children.isNotEmpty);
120120
if (node.level == _DiagnosticsNodeLevel.summary || allChildrenAreLeaf) {
121-
_writeNode(node, recursive: false);
121+
// DiagnosticsBlock is a container, so recurse into its children if
122+
// there's only a single level. The container may be
123+
// "The relevant error-causing widget was" and the child may be
124+
// the specific widget details.
125+
_writeNode(node, recursive: node.type == _DiagnosticsNodeType.DiagnosticsBlock && allChildrenAreLeaf);
122126
}
123127
}
124128
}
@@ -148,6 +152,10 @@ enum _DiagnosticsNodeStyle {
148152
flat,
149153
}
150154

155+
enum _DiagnosticsNodeType {
156+
DiagnosticsBlock,
157+
}
158+
151159
class _ErrorData extends _ErrorNode {
152160
_ErrorData(super.data);
153161

@@ -167,6 +175,7 @@ class _ErrorNode {
167175
List<_ErrorNode> get properties => asList('properties', _ErrorNode.new);
168176
bool get showName => data['showName'] != false;
169177
_DiagnosticsNodeStyle? get style => asEnum('style', _DiagnosticsNodeStyle.values);
178+
_DiagnosticsNodeType? get type => asEnum('type', _DiagnosticsNodeType.values);
170179

171180
String? asString(String field) {
172181
final Object? value = data[field];

packages/flutter_tools/test/general.shard/dap/flutter_adapter_test.dart

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
// found in the LICENSE file.
44

55
import 'dart:async';
6+
import 'dart:convert';
67

78
import 'package:dds/dap.dart';
89
import 'package:file/memory.dart';
910
import 'package:flutter_tools/src/base/file_system.dart';
1011
import 'package:flutter_tools/src/base/platform.dart';
1112
import 'package:flutter_tools/src/cache.dart';
13+
import 'package:flutter_tools/src/debug_adapters/error_formatter.dart';
1214
import 'package:flutter_tools/src/debug_adapters/flutter_adapter.dart';
1315
import 'package:flutter_tools/src/debug_adapters/flutter_adapter_args.dart';
1416
import 'package:flutter_tools/src/globals.dart' as globals show fs, platform;
@@ -797,6 +799,55 @@ void main() {
797799
expect(adapter.processArgs, contains('tool_args'));
798800
});
799801
});
802+
803+
group('error formatter', () {
804+
/// Helpers to build a string representation of the DAP OutputEvents for
805+
/// the structured error [errorData].
806+
String getFormattedError(Map<String, Object?> errorData) {
807+
// Format the error and write into a buffer in a text format convenient
808+
// for test expectations.
809+
final StringBuffer buffer = StringBuffer();
810+
FlutterErrorFormatter()
811+
..formatError(errorData)
812+
..sendOutput((String category, String message, {bool? parseStackFrames, int? variablesReference}) {
813+
buffer.writeln('${category.padRight(6)} ${jsonEncode(message)}');
814+
});
815+
return buffer.toString();
816+
}
817+
818+
test('includes children of DiagnosticsBlock when writing a summary', () {
819+
// Format a simulated error that nests the error-causing widget in a
820+
// diagnostic block and will be displayed in summary mode (because it
821+
// is not the first error since the last reload).
822+
// https://github.com/Dart-Code/Dart-Code/issues/4743
823+
final String error = getFormattedError(<String, Object?>{
824+
'errorsSinceReload': 1, // Force summary mode
825+
'description': 'Exception caught...',
826+
'properties': <Map<String, Object?>>[
827+
<String, Object>{
828+
'description': 'The following assertion was thrown...',
829+
},
830+
<String, Object?>{
831+
'description': '',
832+
'type': 'DiagnosticsBlock',
833+
'name': 'The relevant error-causing widget was',
834+
'children': <Map<String, Object>>[
835+
<String, Object>{
836+
'description': 'MyWidget:file:///path/to/widget.dart:1:2',
837+
}
838+
]
839+
}
840+
],
841+
});
842+
843+
expect(error, r'''
844+
stdout "\n"
845+
stderr "════════ Exception caught... ═══════════════════════════════════════════════════\n"
846+
stdout "The relevant error-causing widget was:\n MyWidget:file:///path/to/widget.dart:1:2\n"
847+
stderr "════════════════════════════════════════════════════════════════════════════════\n"
848+
''');
849+
});
850+
});
800851
});
801852
}
802853

0 commit comments

Comments
 (0)