[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 https://github.com/Dart-Code/Dart-Code/issues/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)
This commit is contained in:
Danny Tuppeny 2024-06-28 07:36:03 +01:00 committed by GitHub
parent ed8dde8ba0
commit eb58fe16b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 61 additions and 1 deletions

View File

@ -118,7 +118,11 @@ class FlutterErrorFormatter {
final bool allChildrenAreLeaf = node.children.isNotEmpty &&
!node.children.any((_ErrorNode child) => child.children.isNotEmpty);
if (node.level == _DiagnosticsNodeLevel.summary || allChildrenAreLeaf) {
_writeNode(node, recursive: false);
// DiagnosticsBlock is a container, so recurse into its children if
// there's only a single level. The container may be
// "The relevant error-causing widget was" and the child may be
// the specific widget details.
_writeNode(node, recursive: node.type == _DiagnosticsNodeType.DiagnosticsBlock && allChildrenAreLeaf);
}
}
}
@ -148,6 +152,10 @@ enum _DiagnosticsNodeStyle {
flat,
}
enum _DiagnosticsNodeType {
DiagnosticsBlock,
}
class _ErrorData extends _ErrorNode {
_ErrorData(super.data);
@ -167,6 +175,7 @@ class _ErrorNode {
List<_ErrorNode> get properties => asList('properties', _ErrorNode.new);
bool get showName => data['showName'] != false;
_DiagnosticsNodeStyle? get style => asEnum('style', _DiagnosticsNodeStyle.values);
_DiagnosticsNodeType? get type => asEnum('type', _DiagnosticsNodeType.values);
String? asString(String field) {
final Object? value = data[field];

View File

@ -3,12 +3,14 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'package:dds/dap.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/debug_adapters/error_formatter.dart';
import 'package:flutter_tools/src/debug_adapters/flutter_adapter.dart';
import 'package:flutter_tools/src/debug_adapters/flutter_adapter_args.dart';
import 'package:flutter_tools/src/globals.dart' as globals show fs, platform;
@ -797,6 +799,55 @@ void main() {
expect(adapter.processArgs, contains('tool_args'));
});
});
group('error formatter', () {
/// Helpers to build a string representation of the DAP OutputEvents for
/// the structured error [errorData].
String getFormattedError(Map<String, Object?> errorData) {
// Format the error and write into a buffer in a text format convenient
// for test expectations.
final StringBuffer buffer = StringBuffer();
FlutterErrorFormatter()
..formatError(errorData)
..sendOutput((String category, String message, {bool? parseStackFrames, int? variablesReference}) {
buffer.writeln('${category.padRight(6)} ${jsonEncode(message)}');
});
return buffer.toString();
}
test('includes children of DiagnosticsBlock when writing a summary', () {
// Format a simulated error that nests the error-causing widget in a
// diagnostic block and will be displayed in summary mode (because it
// is not the first error since the last reload).
// https://github.com/Dart-Code/Dart-Code/issues/4743
final String error = getFormattedError(<String, Object?>{
'errorsSinceReload': 1, // Force summary mode
'description': 'Exception caught...',
'properties': <Map<String, Object?>>[
<String, Object>{
'description': 'The following assertion was thrown...',
},
<String, Object?>{
'description': '',
'type': 'DiagnosticsBlock',
'name': 'The relevant error-causing widget was',
'children': <Map<String, Object>>[
<String, Object>{
'description': 'MyWidget:file:///path/to/widget.dart:1:2',
}
]
}
],
});
expect(error, r'''
stdout "\n"
stderr "════════ Exception caught... ═══════════════════════════════════════════════════\n"
stdout "The relevant error-causing widget was:\n MyWidget:file:///path/to/widget.dart:1:2\n"
stderr "════════════════════════════════════════════════════════════════════════════════\n"
''');
});
});
});
}