1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'dart:developer';
6///
7/// @docImport 'package:flutter/rendering.dart';
8/// @docImport 'package:flutter/widgets.dart';
9library;
10
11import 'dart:math' as math;
12import 'dart:ui' show clampDouble;
13
14import 'package:meta/meta.dart';
15
16import 'assertions.dart';
17import 'constants.dart';
18import 'debug.dart';
19import 'object.dart';
20
21// Examples can assume:
22// late int rows, columns;
23// late String _name;
24// late bool inherit;
25// abstract class ExampleSuperclass with Diagnosticable { }
26// late String message;
27// late double stepWidth;
28// late double scale;
29// late double hitTestExtent;
30// late double paintExtent;
31// late double maxWidth;
32// late double progress;
33// late int maxLines;
34// late Duration duration;
35// late int depth;
36// late bool primary;
37// late bool isCurrent;
38// late bool keepAlive;
39// late bool obscureText;
40// late TextAlign textAlign;
41// late ImageRepeat repeat;
42// late Widget widget;
43// late List boxShadow;
44// late Size size;
45// late bool hasSize;
46// late Matrix4 transform;
47// late Color color;
48// late Map? handles;
49// late DiagnosticsTreeStyle style;
50// late IconData icon;
51// late double devicePixelRatio;
52
53/// The various priority levels used to filter which diagnostics are shown and
54/// omitted.
55///
56/// Trees of Flutter diagnostics can be very large so filtering the diagnostics
57/// shown matters. Typically filtering to only show diagnostics with at least
58/// level [debug] is appropriate.
59///
60/// In release mode, this level may not have any effect, as diagnostics in
61/// release mode are compacted or truncated to reduce binary size.
62enum DiagnosticLevel {
63 /// Diagnostics that should not be shown.
64 ///
65 /// If a user chooses to display [hidden] diagnostics, they should not expect
66 /// the diagnostics to be formatted consistently with other diagnostics and
67 /// they should expect them to sometimes be misleading. For example,
68 /// [FlagProperty] and [ObjectFlagProperty] have uglier formatting when the
69 /// property `value` does not match a value with a custom flag
70 /// description. An example of a misleading diagnostic is a diagnostic for
71 /// a property that has no effect because some other property of the object is
72 /// set in a way that causes the hidden property to have no effect.
73 hidden,
74
75 /// A diagnostic that is likely to be low value but where the diagnostic
76 /// display is just as high quality as a diagnostic with a higher level.
77 ///
78 /// Use this level for diagnostic properties that match their default value
79 /// and other cases where showing a diagnostic would not add much value such
80 /// as an [IterableProperty] where the value is empty.
81 fine,
82
83 /// Diagnostics that should only be shown when performing fine grained
84 /// debugging of an object.
85 ///
86 /// Unlike a [fine] diagnostic, these diagnostics provide important
87 /// information about the object that is likely to be needed to debug. Used by
88 /// properties that are important but where the property value is too verbose
89 /// (e.g. 300+ characters long) to show with a higher diagnostic level.
90 debug,
91
92 /// Interesting diagnostics that should be typically shown.
93 info,
94
95 /// Very important diagnostics that indicate problematic property values.
96 ///
97 /// For example, use if you would write the property description
98 /// message in ALL CAPS.
99 warning,
100
101 /// Diagnostics that provide a hint about best practices.
102 ///
103 /// For example, a diagnostic describing best practices for fixing an error.
104 hint,
105
106 /// Diagnostics that summarize other diagnostics present.
107 ///
108 /// For example, use this level for a short one or two line summary
109 /// describing other diagnostics present.
110 summary,
111
112 /// Diagnostics that indicate errors or unexpected conditions.
113 ///
114 /// For example, use for property values where computing the value throws an
115 /// exception.
116 error,
117
118 /// Special level indicating that no diagnostics should be shown.
119 ///
120 /// Do not specify this level for diagnostics. This level is only used to
121 /// filter which diagnostics are shown.
122 off,
123}
124
125/// Styles for displaying a node in a [DiagnosticsNode] tree.
126///
127/// In release mode, these styles may be ignored, as diagnostics are compacted
128/// or truncated to save on binary size.
129///
130/// See also:
131///
132/// * [DiagnosticsNode.toStringDeep], which dumps text art trees for these
133/// styles.
134enum DiagnosticsTreeStyle {
135 /// A style that does not display the tree, for release mode.
136 none,
137
138 /// Sparse style for displaying trees.
139 ///
140 /// See also:
141 ///
142 /// * [RenderObject], which uses this style.
143 sparse,
144
145 /// Connects a node to its parent with a dashed line.
146 ///
147 /// See also:
148 ///
149 /// * [RenderSliverMultiBoxAdaptor], which uses this style to distinguish
150 /// offstage children from onstage children.
151 offstage,
152
153 /// Slightly more compact version of the [sparse] style.
154 ///
155 /// See also:
156 ///
157 /// * [Element], which uses this style.
158 dense,
159
160 /// Style that enables transitioning from nodes of one style to children of
161 /// another.
162 ///
163 /// See also:
164 ///
165 /// * [RenderParagraph], which uses this style to display a [TextSpan] child
166 /// in a way that is compatible with the [DiagnosticsTreeStyle.sparse]
167 /// style of the [RenderObject] tree.
168 transition,
169
170 /// Style for displaying content describing an error.
171 ///
172 /// See also:
173 ///
174 /// * [FlutterError], which uses this style for the root node in a tree
175 /// describing an error.
176 error,
177
178 /// Render the tree just using whitespace without connecting parents to
179 /// children using lines.
180 ///
181 /// See also:
182 ///
183 /// * [SliverGeometry], which uses this style.
184 whitespace,
185
186 /// Render the tree without indenting children at all.
187 ///
188 /// See also:
189 ///
190 /// * [DiagnosticsStackTrace], which uses this style.
191 flat,
192
193 /// Render the tree on a single line without showing children.
194 singleLine,
195
196 /// Render the tree using a style appropriate for properties that are part
197 /// of an error message.
198 ///
199 /// The name is placed on one line with the value and properties placed on
200 /// the following line.
201 ///
202 /// See also:
203 ///
204 /// * [singleLine], which displays the same information but keeps the
205 /// property and value on the same line.
206 errorProperty,
207
208 /// Render only the immediate properties of a node instead of the full tree.
209 ///
210 /// See also:
211 ///
212 /// * [DebugOverflowIndicatorMixin], which uses this style to display just
213 /// the immediate children of a node.
214 shallow,
215
216 /// Render only the children of a node truncating before the tree becomes too
217 /// large.
218 truncateChildren,
219}
220
221/// Configuration specifying how a particular [DiagnosticsTreeStyle] should be
222/// rendered as text art.
223///
224/// In release mode, these configurations may be ignored, as diagnostics are
225/// compacted or truncated to save on binary size.
226///
227/// See also:
228///
229/// * [sparseTextConfiguration], which is a typical style.
230/// * [transitionTextConfiguration], which is an example of a complex tree style.
231/// * [DiagnosticsNode.toStringDeep], for code using [TextTreeConfiguration]
232/// to render text art for arbitrary trees of [DiagnosticsNode] objects.
233class TextTreeConfiguration {
234 /// Create a configuration object describing how to render a tree as text.
235 TextTreeConfiguration({
236 required this.prefixLineOne,
237 required this.prefixOtherLines,
238 required this.prefixLastChildLineOne,
239 required this.prefixOtherLinesRootNode,
240 required this.linkCharacter,
241 required this.propertyPrefixIfChildren,
242 required this.propertyPrefixNoChildren,
243 this.lineBreak = '\n',
244 this.lineBreakProperties = true,
245 this.afterName = ':',
246 this.afterDescriptionIfBody = '',
247 this.afterDescription = '',
248 this.beforeProperties = '',
249 this.afterProperties = '',
250 this.mandatoryAfterProperties = '',
251 this.propertySeparator = '',
252 this.bodyIndent = '',
253 this.footer = '',
254 this.showChildren = true,
255 this.addBlankLineIfNoChildren = true,
256 this.isNameOnOwnLine = false,
257 this.isBlankLineBetweenPropertiesAndChildren = true,
258 this.beforeName = '',
259 this.suffixLineOne = '',
260 this.mandatoryFooter = '',
261 }) : childLinkSpace = ' ' * linkCharacter.length;
262
263 /// Prefix to add to the first line to display a child with this style.
264 final String prefixLineOne;
265
266 /// Suffix to add to end of the first line to make its length match the footer.
267 final String suffixLineOne;
268
269 /// Prefix to add to other lines to display a child with this style.
270 ///
271 /// [prefixOtherLines] should typically be one character shorter than
272 /// [prefixLineOne] is.
273 final String prefixOtherLines;
274
275 /// Prefix to add to the first line to display the last child of a node with
276 /// this style.
277 final String prefixLastChildLineOne;
278
279 /// Additional prefix to add to other lines of a node if this is the root node
280 /// of the tree.
281 final String prefixOtherLinesRootNode;
282
283 /// Prefix to add before each property if the node as children.
284 ///
285 /// Plays a similar role to [linkCharacter] except that some configurations
286 /// intentionally use a different line style than the [linkCharacter].
287 final String propertyPrefixIfChildren;
288
289 /// Prefix to add before each property if the node does not have children.
290 ///
291 /// This string is typically a whitespace string the same length as
292 /// [propertyPrefixIfChildren] but can have a different length.
293 final String propertyPrefixNoChildren;
294
295 /// Character to use to draw line linking parent to child.
296 ///
297 /// The first child does not require a line but all subsequent children do
298 /// with the line drawn immediately before the left edge of the previous
299 /// sibling.
300 final String linkCharacter;
301
302 /// Whitespace to draw instead of the childLink character if this node is the
303 /// last child of its parent so no link line is required.
304 final String childLinkSpace;
305
306 /// Character(s) to use to separate lines.
307 ///
308 /// Typically leave set at the default value of '\n' unless this style needs
309 /// to treat lines differently as is the case for
310 /// [singleLineTextConfiguration].
311 final String lineBreak;
312
313 /// Whether to place line breaks between properties or to leave all
314 /// properties on one line.
315 final bool lineBreakProperties;
316
317
318 /// Text added immediately before the name of the node.
319 ///
320 /// See [errorTextConfiguration] for an example of using this to achieve a
321 /// custom line art style.
322 final String beforeName;
323
324 /// Text added immediately after the name of the node.
325 ///
326 /// See [transitionTextConfiguration] for an example of using a value other
327 /// than ':' to achieve a custom line art style.
328 final String afterName;
329
330 /// Text to add immediately after the description line of a node with
331 /// properties and/or children if the node has a body.
332 final String afterDescriptionIfBody;
333
334 /// Text to add immediately after the description line of a node with
335 /// properties and/or children.
336 final String afterDescription;
337
338 /// Optional string to add before the properties of a node.
339 ///
340 /// Only displayed if the node has properties.
341 /// See [singleLineTextConfiguration] for an example of using this field
342 /// to enclose the property list with parenthesis.
343 final String beforeProperties;
344
345 /// Optional string to add after the properties of a node.
346 ///
347 /// See documentation for [beforeProperties].
348 final String afterProperties;
349
350 /// Mandatory string to add after the properties of a node regardless of
351 /// whether the node has any properties.
352 final String mandatoryAfterProperties;
353
354 /// Property separator to add between properties.
355 ///
356 /// See [singleLineTextConfiguration] for an example of using this field
357 /// to render properties as a comma separated list.
358 final String propertySeparator;
359
360 /// Prefix to add to all lines of the body of the tree node.
361 ///
362 /// The body is all content in the node other than the name and description.
363 final String bodyIndent;
364
365 /// Whether the children of a node should be shown.
366 ///
367 /// See [singleLineTextConfiguration] for an example of using this field to
368 /// hide all children of a node.
369 final bool showChildren;
370
371 /// Whether to add a blank line at the end of the output for a node if it has
372 /// no children.
373 ///
374 /// See [denseTextConfiguration] for an example of setting this to false.
375 final bool addBlankLineIfNoChildren;
376
377 /// Whether the name should be displayed on the same line as the description.
378 final bool isNameOnOwnLine;
379
380 /// Footer to add as its own line at the end of a non-root node.
381 ///
382 /// See [transitionTextConfiguration] for an example of using footer to draw a box
383 /// around the node. [footer] is indented the same amount as [prefixOtherLines].
384 final String footer;
385
386 /// Footer to add even for root nodes.
387 final String mandatoryFooter;
388
389 /// Add a blank line between properties and children if both are present.
390 final bool isBlankLineBetweenPropertiesAndChildren;
391}
392
393/// Default text tree configuration.
394///
395/// Example:
396///
397/// <root_name>: <root_description>
398/// │ <property1>
399/// │ <property2>
400/// │ ...
401/// │ <propertyN>
402/// ├─<child_name>: <child_description>
403/// │ │ <property1>
404/// │ │ <property2>
405/// │ │ ...
406/// │ │ <propertyN>
407/// │ │
408/// │ └─<child_name>: <child_description>
409/// │ <property1>
410/// │ <property2>
411/// │ ...
412/// │ <propertyN>
413/// │
414/// └─<child_name>: <child_description>'
415/// <property1>
416/// <property2>
417/// ...
418/// <propertyN>
419///
420/// See also:
421///
422/// * [DiagnosticsTreeStyle.sparse], uses this style for ASCII art display.
423final TextTreeConfiguration sparseTextConfiguration = TextTreeConfiguration(
424 prefixLineOne: '├─',
425 prefixOtherLines: ' ',
426 prefixLastChildLineOne: '└─',
427 linkCharacter: '│',
428 propertyPrefixIfChildren: '│ ',
429 propertyPrefixNoChildren: ' ',
430 prefixOtherLinesRootNode: ' ',
431);
432
433/// Identical to [sparseTextConfiguration] except that the lines connecting
434/// parent to children are dashed.
435///
436/// Example:
437///
438/// <root_name>: <root_description>
439/// │ <property1>
440/// │ <property2>
441/// │ ...
442/// │ <propertyN>
443/// ├─<normal_child_name>: <child_description>
444/// ╎ │ <property1>
445/// ╎ │ <property2>
446/// ╎ │ ...
447/// ╎ │ <propertyN>
448/// ╎ │
449/// ╎ └─<child_name>: <child_description>
450/// ╎ <property1>
451/// ╎ <property2>
452/// ╎ ...
453/// ╎ <propertyN>
454/// ╎
455/// ╎╌<dashed_child_name>: <child_description>
456/// ╎ │ <property1>
457/// ╎ │ <property2>
458/// ╎ │ ...
459/// ╎ │ <propertyN>
460/// ╎ │
461/// ╎ └─<child_name>: <child_description>
462/// ╎ <property1>
463/// ╎ <property2>
464/// ╎ ...
465/// ╎ <propertyN>
466/// ╎
467/// └╌<dashed_child_name>: <child_description>'
468/// <property1>
469/// <property2>
470/// ...
471/// <propertyN>
472///
473/// See also:
474///
475/// * [DiagnosticsTreeStyle.offstage], uses this style for ASCII art display.
476final TextTreeConfiguration dashedTextConfiguration = TextTreeConfiguration(
477 prefixLineOne: '╎╌',
478 prefixLastChildLineOne: '└╌',
479 prefixOtherLines: ' ',
480 linkCharacter: '╎',
481 // Intentionally not set as a dashed line as that would make the properties
482 // look like they were disabled.
483 propertyPrefixIfChildren: '│ ',
484 propertyPrefixNoChildren: ' ',
485 prefixOtherLinesRootNode: ' ',
486);
487
488/// Dense text tree configuration that minimizes horizontal whitespace.
489///
490/// Example:
491///
492/// <root_name>: <root_description>(<property1>; <property2> <propertyN>)
493/// ├<child_name>: <child_description>(<property1>, <property2>, <propertyN>)
494/// └<child_name>: <child_description>(<property1>, <property2>, <propertyN>)
495///
496/// See also:
497///
498/// * [DiagnosticsTreeStyle.dense], uses this style for ASCII art display.
499final TextTreeConfiguration denseTextConfiguration = TextTreeConfiguration(
500 propertySeparator: ', ',
501 beforeProperties: '(',
502 afterProperties: ')',
503 lineBreakProperties: false,
504 prefixLineOne: '├',
505 prefixOtherLines: '',
506 prefixLastChildLineOne: '└',
507 linkCharacter: '│',
508 propertyPrefixIfChildren: '│',
509 propertyPrefixNoChildren: ' ',
510 prefixOtherLinesRootNode: '',
511 addBlankLineIfNoChildren: false,
512 isBlankLineBetweenPropertiesAndChildren: false,
513);
514
515/// Configuration that draws a box around a leaf node.
516///
517/// Used by leaf nodes such as [TextSpan] to draw a clear border around the
518/// contents of a node.
519///
520/// Example:
521///
522/// <parent_node>
523/// ╞═╦══ <name> ═══
524/// │ ║ <description>:
525/// │ ║ <body>
526/// │ ║ ...
527/// │ ╚═══════════
528/// ╘═╦══ <name> ═══
529/// ║ <description>:
530/// ║ <body>
531/// ║ ...
532/// ╚═══════════
533///
534/// See also:
535///
536/// * [DiagnosticsTreeStyle.transition], uses this style for ASCII art display.
537final TextTreeConfiguration transitionTextConfiguration = TextTreeConfiguration(
538 prefixLineOne: '╞═╦══ ',
539 prefixLastChildLineOne: '╘═╦══ ',
540 prefixOtherLines: ' ║ ',
541 footer: ' ╚═══════════',
542 linkCharacter: '│',
543 // Subtree boundaries are clear due to the border around the node so omit the
544 // property prefix.
545 propertyPrefixIfChildren: '',
546 propertyPrefixNoChildren: '',
547 prefixOtherLinesRootNode: '',
548 afterName: ' ═══',
549 // Add a colon after the description if the node has a body to make the
550 // connection between the description and the body clearer.
551 afterDescriptionIfBody: ':',
552 // Members are indented an extra two spaces to disambiguate as the description
553 // is placed within the box instead of along side the name as is the case for
554 // other styles.
555 bodyIndent: ' ',
556 isNameOnOwnLine: true,
557 // No need to add a blank line as the footer makes the boundary of this
558 // subtree unambiguous.
559 addBlankLineIfNoChildren: false,
560 isBlankLineBetweenPropertiesAndChildren: false,
561);
562
563/// Configuration that draws a box around a node ignoring the connection to the
564/// parents.
565///
566/// If nested in a tree, this node is best displayed in the property box rather
567/// than as a traditional child.
568///
569/// Used to draw a decorative box around detailed descriptions of an exception.
570///
571/// Example:
572///
573/// ══╡ <name>: <description> ╞═════════════════════════════════════
574/// <body>
575/// ...
576/// ├─<normal_child_name>: <child_description>
577/// ╎ │ <property1>
578/// ╎ │ <property2>
579/// ╎ │ ...
580/// ╎ │ <propertyN>
581/// ╎ │
582/// ╎ └─<child_name>: <child_description>
583/// ╎ <property1>
584/// ╎ <property2>
585/// ╎ ...
586/// ╎ <propertyN>
587/// ╎
588/// ╎╌<dashed_child_name>: <child_description>
589/// ╎ │ <property1>
590/// ╎ │ <property2>
591/// ╎ │ ...
592/// ╎ │ <propertyN>
593/// ╎ │
594/// ╎ └─<child_name>: <child_description>
595/// ╎ <property1>
596/// ╎ <property2>
597/// ╎ ...
598/// ╎ <propertyN>
599/// ╎
600/// └╌<dashed_child_name>: <child_description>'
601/// ════════════════════════════════════════════════════════════════
602///
603/// See also:
604///
605/// * [DiagnosticsTreeStyle.error], uses this style for ASCII art display.
606final TextTreeConfiguration errorTextConfiguration = TextTreeConfiguration(
607 prefixLineOne: '╞═╦',
608 prefixLastChildLineOne: '╘═╦',
609 prefixOtherLines: ' ║ ',
610 footer: ' ╚═══════════',
611 linkCharacter: '│',
612 // Subtree boundaries are clear due to the border around the node so omit the
613 // property prefix.
614 propertyPrefixIfChildren: '',
615 propertyPrefixNoChildren: '',
616 prefixOtherLinesRootNode: '',
617 beforeName: '══╡ ',
618 suffixLineOne: ' ╞══',
619 mandatoryFooter: '═════',
620 // No need to add a blank line as the footer makes the boundary of this
621 // subtree unambiguous.
622 addBlankLineIfNoChildren: false,
623 isBlankLineBetweenPropertiesAndChildren: false,
624);
625
626/// Whitespace only configuration where children are consistently indented
627/// two spaces.
628///
629/// Use this style for displaying properties with structured values or for
630/// displaying children within a [transitionTextConfiguration] as using a style that
631/// draws line art would be visually distracting for those cases.
632///
633/// Example:
634///
635/// <parent_node>
636/// <name>: <description>:
637/// <properties>
638/// <children>
639/// <name>: <description>:
640/// <properties>
641/// <children>
642///
643/// See also:
644///
645/// * [DiagnosticsTreeStyle.whitespace], uses this style for ASCII art display.
646final TextTreeConfiguration whitespaceTextConfiguration = TextTreeConfiguration(
647 prefixLineOne: '',
648 prefixLastChildLineOne: '',
649 prefixOtherLines: ' ',
650 prefixOtherLinesRootNode: ' ',
651 propertyPrefixIfChildren: '',
652 propertyPrefixNoChildren: '',
653 linkCharacter: ' ',
654 addBlankLineIfNoChildren: false,
655 // Add a colon after the description and before the properties to link the
656 // properties to the description line.
657 afterDescriptionIfBody: ':',
658 isBlankLineBetweenPropertiesAndChildren: false,
659);
660
661/// Whitespace only configuration where children are not indented.
662///
663/// Use this style when indentation is not needed to disambiguate parents from
664/// children as in the case of a [DiagnosticsStackTrace].
665///
666/// Example:
667///
668/// <parent_node>
669/// <name>: <description>:
670/// <properties>
671/// <children>
672/// <name>: <description>:
673/// <properties>
674/// <children>
675///
676/// See also:
677///
678/// * [DiagnosticsTreeStyle.flat], uses this style for ASCII art display.
679final TextTreeConfiguration flatTextConfiguration = TextTreeConfiguration(
680 prefixLineOne: '',
681 prefixLastChildLineOne: '',
682 prefixOtherLines: '',
683 prefixOtherLinesRootNode: '',
684 propertyPrefixIfChildren: '',
685 propertyPrefixNoChildren: '',
686 linkCharacter: '',
687 addBlankLineIfNoChildren: false,
688 // Add a colon after the description and before the properties to link the
689 // properties to the description line.
690 afterDescriptionIfBody: ':',
691 isBlankLineBetweenPropertiesAndChildren: false,
692);
693/// Render a node as a single line omitting children.
694///
695/// Example:
696/// `<name>: <description>(<property1>, <property2>, ..., <propertyN>)`
697///
698/// See also:
699///
700/// * [DiagnosticsTreeStyle.singleLine], uses this style for ASCII art display.
701final TextTreeConfiguration singleLineTextConfiguration = TextTreeConfiguration(
702 propertySeparator: ', ',
703 beforeProperties: '(',
704 afterProperties: ')',
705 prefixLineOne: '',
706 prefixOtherLines: '',
707 prefixLastChildLineOne: '',
708 lineBreak: '',
709 lineBreakProperties: false,
710 addBlankLineIfNoChildren: false,
711 showChildren: false,
712 propertyPrefixIfChildren: ' ',
713 propertyPrefixNoChildren: ' ',
714 linkCharacter: '',
715 prefixOtherLinesRootNode: '',
716);
717
718/// Render the name on a line followed by the body and properties on the next
719/// line omitting the children.
720///
721/// Example:
722///
723/// <name>:
724/// <description>(<property1>, <property2>, ..., <propertyN>)
725///
726/// See also:
727///
728/// * [DiagnosticsTreeStyle.errorProperty], uses this style for ASCII art
729/// display.
730final TextTreeConfiguration errorPropertyTextConfiguration = TextTreeConfiguration(
731 propertySeparator: ', ',
732 beforeProperties: '(',
733 afterProperties: ')',
734 prefixLineOne: '',
735 prefixOtherLines: '',
736 prefixLastChildLineOne: '',
737 lineBreakProperties: false,
738 addBlankLineIfNoChildren: false,
739 showChildren: false,
740 propertyPrefixIfChildren: ' ',
741 propertyPrefixNoChildren: ' ',
742 linkCharacter: '',
743 prefixOtherLinesRootNode: '',
744 isNameOnOwnLine: true,
745);
746
747/// Render a node on multiple lines omitting children.
748///
749/// Example:
750/// `<name>: <description>
751/// <property1>
752/// <property2>
753/// <propertyN>`
754///
755/// See also:
756///
757/// * [DiagnosticsTreeStyle.shallow]
758final TextTreeConfiguration shallowTextConfiguration = TextTreeConfiguration(
759 prefixLineOne: '',
760 prefixLastChildLineOne: '',
761 prefixOtherLines: ' ',
762 prefixOtherLinesRootNode: ' ',
763 propertyPrefixIfChildren: '',
764 propertyPrefixNoChildren: '',
765 linkCharacter: ' ',
766 addBlankLineIfNoChildren: false,
767 // Add a colon after the description and before the properties to link the
768 // properties to the description line.
769 afterDescriptionIfBody: ':',
770 isBlankLineBetweenPropertiesAndChildren: false,
771 showChildren: false,
772);
773
774enum _WordWrapParseMode { inSpace, inWord, atBreak }
775
776/// Builder that builds a String with specified prefixes for the first and
777/// subsequent lines.
778///
779/// Allows for the incremental building of strings using `write*()` methods.
780/// The strings are concatenated into a single string with the first line
781/// prefixed by [prefixLineOne] and subsequent lines prefixed by
782/// [prefixOtherLines].
783class _PrefixedStringBuilder {
784 _PrefixedStringBuilder({
785 required this.prefixLineOne,
786 required String? prefixOtherLines,
787 this.wrapWidth,
788 }) : _prefixOtherLines = prefixOtherLines;
789
790 /// Prefix to add to the first line.
791 final String prefixLineOne;
792
793 /// Prefix to add to subsequent lines.
794 ///
795 /// The prefix can be modified while the string is being built in which case
796 /// subsequent lines will be added with the modified prefix.
797 String? get prefixOtherLines => _nextPrefixOtherLines ?? _prefixOtherLines;
798 String? _prefixOtherLines;
799 set prefixOtherLines(String? prefix) {
800 _prefixOtherLines = prefix;
801 _nextPrefixOtherLines = null;
802 }
803
804 String? _nextPrefixOtherLines;
805 void incrementPrefixOtherLines(String suffix, {required bool updateCurrentLine}) {
806 if (_currentLine.isEmpty || updateCurrentLine) {
807 _prefixOtherLines = prefixOtherLines! + suffix;
808 _nextPrefixOtherLines = null;
809 } else {
810 _nextPrefixOtherLines = prefixOtherLines! + suffix;
811 }
812 }
813
814 final int? wrapWidth;
815
816 /// Buffer containing lines that have already been completely laid out.
817 final StringBuffer _buffer = StringBuffer();
818 /// Buffer containing the current line that has not yet been wrapped.
819 final StringBuffer _currentLine = StringBuffer();
820 /// List of pairs of integers indicating the start and end of each block of
821 /// text within _currentLine that can be wrapped.
822 final List<int> _wrappableRanges = <int>[];
823
824 /// Whether the string being built already has more than 1 line.
825 bool get requiresMultipleLines => _numLines > 1 || (_numLines == 1 && _currentLine.isNotEmpty) ||
826 (_currentLine.length + _getCurrentPrefix(true)!.length > wrapWidth!);
827
828 bool get isCurrentLineEmpty => _currentLine.isEmpty;
829
830 int _numLines = 0;
831
832 void _finalizeLine(bool addTrailingLineBreak) {
833 final bool firstLine = _buffer.isEmpty;
834 final String text = _currentLine.toString();
835 _currentLine.clear();
836
837 if (_wrappableRanges.isEmpty) {
838 // Fast path. There were no wrappable spans of text.
839 _writeLine(
840 text,
841 includeLineBreak: addTrailingLineBreak,
842 firstLine: firstLine,
843 );
844 return;
845 }
846 final Iterable<String> lines = _wordWrapLine(
847 text,
848 _wrappableRanges,
849 wrapWidth!,
850 startOffset: firstLine ? prefixLineOne.length : _prefixOtherLines!.length,
851 otherLineOffset: _prefixOtherLines!.length,
852 );
853 int i = 0;
854 final int length = lines.length;
855 for (final String line in lines) {
856 i++;
857 _writeLine(
858 line,
859 includeLineBreak: addTrailingLineBreak || i < length,
860 firstLine: firstLine,
861 );
862 }
863 _wrappableRanges.clear();
864 }
865
866 /// Wraps the given string at the given width.
867 ///
868 /// Wrapping occurs at space characters (U+0020).
869 ///
870 /// This is not suitable for use with arbitrary Unicode text. For example, it
871 /// doesn't implement UAX #14, can't handle ideographic text, doesn't hyphenate,
872 /// and so forth. It is only intended for formatting error messages.
873 ///
874 /// This method wraps a sequence of text where only some spans of text can be
875 /// used as wrap boundaries.
876 static Iterable<String> _wordWrapLine(String message, List<int> wrapRanges, int width, { int startOffset = 0, int otherLineOffset = 0}) {
877 if (message.length + startOffset < width) {
878 // Nothing to do. The line doesn't wrap.
879 return <String>[message];
880 }
881 final List<String> wrappedLine = <String>[];
882 int startForLengthCalculations = -startOffset;
883 bool addPrefix = false;
884 int index = 0;
885 _WordWrapParseMode mode = _WordWrapParseMode.inSpace;
886 late int lastWordStart;
887 int? lastWordEnd;
888 int start = 0;
889
890 int currentChunk = 0;
891
892 // This helper is called with increasing indexes.
893 bool noWrap(int index) {
894 while (true) {
895 if (currentChunk >= wrapRanges.length) {
896 return true;
897 }
898
899 if (index < wrapRanges[currentChunk + 1]) {
900 break; // Found nearest chunk.
901 }
902 currentChunk+= 2;
903 }
904 return index < wrapRanges[currentChunk];
905 }
906 while (true) {
907 switch (mode) {
908 case _WordWrapParseMode.inSpace: // at start of break point (or start of line); can't break until next break
909 while ((index < message.length) && (message[index] == ' ')) {
910 index += 1;
911 }
912 lastWordStart = index;
913 mode = _WordWrapParseMode.inWord;
914 case _WordWrapParseMode.inWord: // looking for a good break point. Treat all text
915 while ((index < message.length) && (message[index] != ' ' || noWrap(index))) {
916 index += 1;
917 }
918 mode = _WordWrapParseMode.atBreak;
919 case _WordWrapParseMode.atBreak: // at start of break point
920 if ((index - startForLengthCalculations > width) || (index == message.length)) {
921 // we are over the width line, so break
922 if ((index - startForLengthCalculations <= width) || (lastWordEnd == null)) {
923 // we should use this point, because either it doesn't actually go over the
924 // end (last line), or it does, but there was no earlier break point
925 lastWordEnd = index;
926 }
927 final String line = message.substring(start, lastWordEnd);
928 wrappedLine.add(line);
929 addPrefix = true;
930 if (lastWordEnd >= message.length) {
931 return wrappedLine;
932 }
933 // just yielded a line
934 if (lastWordEnd == index) {
935 // we broke at current position
936 // eat all the wrappable spaces, then set our start point
937 // Even if some of the spaces are not wrappable that is ok.
938 while ((index < message.length) && (message[index] == ' ')) {
939 index += 1;
940 }
941 start = index;
942 mode = _WordWrapParseMode.inWord;
943 } else {
944 // we broke at the previous break point, and we're at the start of a new one
945 assert(lastWordStart > lastWordEnd);
946 start = lastWordStart;
947 mode = _WordWrapParseMode.atBreak;
948 }
949 startForLengthCalculations = start - otherLineOffset;
950 assert(addPrefix);
951 lastWordEnd = null;
952 } else {
953 // save this break point, we're not yet over the line width
954 lastWordEnd = index;
955 // skip to the end of this break point
956 mode = _WordWrapParseMode.inSpace;
957 }
958 }
959 }
960 }
961
962 /// Write text ensuring the specified prefixes for the first and subsequent
963 /// lines.
964 ///
965 /// If [allowWrap] is true, the text may be wrapped to stay within the
966 /// allow `wrapWidth`.
967 void write(String s, {bool allowWrap = false}) {
968 if (s.isEmpty) {
969 return;
970 }
971
972 final List<String> lines = s.split('\n');
973 for (int i = 0; i < lines.length; i += 1) {
974 if (i > 0) {
975 _finalizeLine(true);
976 _updatePrefix();
977 }
978 final String line = lines[i];
979 if (line.isNotEmpty) {
980 if (allowWrap && wrapWidth != null) {
981 final int wrapStart = _currentLine.length;
982 final int wrapEnd = wrapStart + line.length;
983 if (_wrappableRanges.lastOrNull == wrapStart) {
984 // Extend last range.
985 _wrappableRanges.last = wrapEnd;
986 } else {
987 _wrappableRanges..add(wrapStart)..add(wrapEnd);
988 }
989 }
990 _currentLine.write(line);
991 }
992 }
993 }
994 void _updatePrefix() {
995 if (_nextPrefixOtherLines != null) {
996 _prefixOtherLines = _nextPrefixOtherLines;
997 _nextPrefixOtherLines = null;
998 }
999 }
1000
1001 void _writeLine(
1002 String line, {
1003 required bool includeLineBreak,
1004 required bool firstLine,
1005 }) {
1006 line = '${_getCurrentPrefix(firstLine)}$line';
1007 _buffer.write(line.trimRight());
1008 if (includeLineBreak) {
1009 _buffer.write('\n');
1010 }
1011 _numLines++;
1012 }
1013
1014 String? _getCurrentPrefix(bool firstLine) {
1015 return _buffer.isEmpty ? prefixLineOne : _prefixOtherLines;
1016 }
1017
1018 /// Write lines assuming the lines obey the specified prefixes. Ensures that
1019 /// a newline is added if one is not present.
1020 void writeRawLines(String lines) {
1021 if (lines.isEmpty) {
1022 return;
1023 }
1024
1025 if (_currentLine.isNotEmpty) {
1026 _finalizeLine(true);
1027 }
1028 assert (_currentLine.isEmpty);
1029
1030 _buffer.write(lines);
1031 if (!lines.endsWith('\n')) {
1032 _buffer.write('\n');
1033 }
1034 _numLines++;
1035 _updatePrefix();
1036 }
1037
1038 /// Finishes the current line with a stretched version of text.
1039 void writeStretched(String text, int targetLineLength) {
1040 write(text);
1041 final int currentLineLength = _currentLine.length + _getCurrentPrefix(_buffer.isEmpty)!.length;
1042 assert (_currentLine.length > 0);
1043 final int targetLength = targetLineLength - currentLineLength;
1044 if (targetLength > 0) {
1045 assert(text.isNotEmpty);
1046 final String lastChar = text[text.length - 1];
1047 assert(lastChar != '\n');
1048 _currentLine.write(lastChar * targetLength);
1049 }
1050 // Mark the entire line as not wrappable.
1051 _wrappableRanges.clear();
1052 }
1053
1054 String build() {
1055 if (_currentLine.isNotEmpty) {
1056 _finalizeLine(false);
1057 }
1058
1059 return _buffer.toString();
1060 }
1061}
1062
1063class _NoDefaultValue {
1064 const _NoDefaultValue();
1065}
1066
1067/// Marker object indicating that a [DiagnosticsNode] has no default value.
1068const Object kNoDefaultValue = _NoDefaultValue();
1069
1070bool _isSingleLine(DiagnosticsTreeStyle? style) {
1071 return style == DiagnosticsTreeStyle.singleLine;
1072}
1073
1074/// Renderer that creates ASCII art representations of trees of
1075/// [DiagnosticsNode] objects.
1076///
1077/// See also:
1078///
1079/// * [DiagnosticsNode.toStringDeep], which uses a [TextTreeRenderer] to return a
1080/// string representation of this node and its descendants.
1081class TextTreeRenderer {
1082 /// Creates a [TextTreeRenderer] object with the given arguments specifying
1083 /// how the tree is rendered.
1084 ///
1085 /// Lines are wrapped to at the maximum of [wrapWidth] and the current indent
1086 /// plus [wrapWidthProperties] characters. This ensures that wrapping does not
1087 /// become too excessive when displaying very deep trees and that wrapping
1088 /// only occurs at the overall [wrapWidth] when the tree is not very indented.
1089 /// If [maxDescendentsTruncatableNode] is specified, [DiagnosticsNode] objects
1090 /// with `allowTruncate` set to `true` are truncated after including
1091 /// [maxDescendentsTruncatableNode] descendants of the node to be truncated.
1092 TextTreeRenderer({
1093 DiagnosticLevel minLevel = DiagnosticLevel.debug,
1094 int wrapWidth = 100,
1095 int wrapWidthProperties = 65,
1096 int maxDescendentsTruncatableNode = -1,
1097 }) : _minLevel = minLevel,
1098 _wrapWidth = wrapWidth,
1099 _wrapWidthProperties = wrapWidthProperties,
1100 _maxDescendentsTruncatableNode = maxDescendentsTruncatableNode;
1101
1102 final int _wrapWidth;
1103 final int _wrapWidthProperties;
1104 final DiagnosticLevel _minLevel;
1105 final int _maxDescendentsTruncatableNode;
1106
1107 /// Text configuration to use to connect this node to a `child`.
1108 ///
1109 /// The singleLine styles are special cased because the connection from the
1110 /// parent to the child should be consistent with the parent's style as the
1111 /// single line style does not provide any meaningful style for how children
1112 /// should be connected to their parents.
1113 TextTreeConfiguration? _childTextConfiguration(
1114 DiagnosticsNode child,
1115 TextTreeConfiguration textStyle,
1116 ) {
1117 final DiagnosticsTreeStyle? childStyle = child.style;
1118 return (_isSingleLine(childStyle) || childStyle == DiagnosticsTreeStyle.errorProperty) ? textStyle : child.textTreeConfiguration;
1119 }
1120
1121 /// Renders a [node] to a String.
1122 String render(
1123 DiagnosticsNode node, {
1124 String prefixLineOne = '',
1125 String? prefixOtherLines,
1126 TextTreeConfiguration? parentConfiguration,
1127 }) {
1128 if (kReleaseMode) {
1129 return '';
1130 } else {
1131 return _debugRender(
1132 node,
1133 prefixLineOne: prefixLineOne,
1134 prefixOtherLines: prefixOtherLines,
1135 parentConfiguration: parentConfiguration,
1136 );
1137 }
1138 }
1139
1140 String _debugRender(
1141 DiagnosticsNode node, {
1142 String prefixLineOne = '',
1143 String? prefixOtherLines,
1144 TextTreeConfiguration? parentConfiguration,
1145 }) {
1146 final bool isSingleLine = _isSingleLine(node.style) && parentConfiguration?.lineBreakProperties != true;
1147 prefixOtherLines ??= prefixLineOne;
1148 if (node.linePrefix != null) {
1149 prefixLineOne += node.linePrefix!;
1150 prefixOtherLines += node.linePrefix!;
1151 }
1152
1153 final TextTreeConfiguration config = node.textTreeConfiguration!;
1154 if (prefixOtherLines.isEmpty) {
1155 prefixOtherLines += config.prefixOtherLinesRootNode;
1156 }
1157
1158 if (node.style == DiagnosticsTreeStyle.truncateChildren) {
1159 // This style is different enough that it isn't worthwhile to reuse the
1160 // existing logic.
1161 final List<String> descendants = <String>[];
1162 const int maxDepth = 5;
1163 int depth = 0;
1164 const int maxLines = 25;
1165 int lines = 0;
1166 void visitor(DiagnosticsNode node) {
1167 for (final DiagnosticsNode child in node.getChildren()) {
1168 if (lines < maxLines) {
1169 depth += 1;
1170 descendants.add('$prefixOtherLines${" " * depth}$child');
1171 if (depth < maxDepth) {
1172 visitor(child);
1173 }
1174 depth -= 1;
1175 } else if (lines == maxLines) {
1176 descendants.add('$prefixOtherLines ...(descendants list truncated after $lines lines)');
1177 }
1178 lines += 1;
1179 }
1180 }
1181 visitor(node);
1182 final StringBuffer information = StringBuffer(prefixLineOne);
1183 if (lines > 1) {
1184 information.writeln('This ${node.name} had the following descendants (showing up to depth $maxDepth):');
1185 } else if (descendants.length == 1) {
1186 information.writeln('This ${node.name} had the following child:');
1187 } else {
1188 information.writeln('This ${node.name} has no descendants.');
1189 }
1190 information.writeAll(descendants, '\n');
1191 return information.toString();
1192 }
1193 final _PrefixedStringBuilder builder = _PrefixedStringBuilder(
1194 prefixLineOne: prefixLineOne,
1195 prefixOtherLines: prefixOtherLines,
1196 wrapWidth: math.max(_wrapWidth, prefixOtherLines.length + _wrapWidthProperties),
1197 );
1198
1199 List<DiagnosticsNode> children = node.getChildren();
1200
1201 String description = node.toDescription(parentConfiguration: parentConfiguration);
1202 if (config.beforeName.isNotEmpty) {
1203 builder.write(config.beforeName);
1204 }
1205 final bool wrapName = !isSingleLine && node.allowNameWrap;
1206 final bool wrapDescription = !isSingleLine && node.allowWrap;
1207 final bool uppercaseTitle = node.style == DiagnosticsTreeStyle.error;
1208 String? name = node.name;
1209 if (uppercaseTitle) {
1210 name = name?.toUpperCase();
1211 }
1212 if (description.isEmpty) {
1213 if (node.showName && name != null) {
1214 builder.write(name, allowWrap: wrapName);
1215 }
1216 } else {
1217 bool includeName = false;
1218 if (name != null && name.isNotEmpty && node.showName) {
1219 includeName = true;
1220 builder.write(name, allowWrap: wrapName);
1221 if (node.showSeparator) {
1222 builder.write(config.afterName, allowWrap: wrapName);
1223 }
1224
1225 builder.write(
1226 config.isNameOnOwnLine || description.contains('\n') ? '\n' : ' ',
1227 allowWrap: wrapName,
1228 );
1229 }
1230 if (!isSingleLine && builder.requiresMultipleLines && !builder.isCurrentLineEmpty) {
1231 // Make sure there is a break between the current line and next one if
1232 // there is not one already.
1233 builder.write('\n');
1234 }
1235 if (includeName) {
1236 builder.incrementPrefixOtherLines(
1237 children.isEmpty ? config.propertyPrefixNoChildren : config.propertyPrefixIfChildren,
1238 updateCurrentLine: true,
1239 );
1240 }
1241
1242 if (uppercaseTitle) {
1243 description = description.toUpperCase();
1244 }
1245 builder.write(description.trimRight(), allowWrap: wrapDescription);
1246
1247 if (!includeName) {
1248 builder.incrementPrefixOtherLines(
1249 children.isEmpty ? config.propertyPrefixNoChildren : config.propertyPrefixIfChildren,
1250 updateCurrentLine: false,
1251 );
1252 }
1253 }
1254 if (config.suffixLineOne.isNotEmpty) {
1255 builder.writeStretched(config.suffixLineOne, builder.wrapWidth!);
1256 }
1257
1258 final Iterable<DiagnosticsNode> propertiesIterable = node.getProperties().where(
1259 (DiagnosticsNode n) => !n.isFiltered(_minLevel),
1260 );
1261 List<DiagnosticsNode> properties;
1262 if (_maxDescendentsTruncatableNode >= 0 && node.allowTruncate) {
1263 if (propertiesIterable.length < _maxDescendentsTruncatableNode) {
1264 properties =
1265 propertiesIterable.take(_maxDescendentsTruncatableNode).toList();
1266 properties.add(DiagnosticsNode.message('...'));
1267 } else {
1268 properties = propertiesIterable.toList();
1269 }
1270 if (_maxDescendentsTruncatableNode < children.length) {
1271 children = children.take(_maxDescendentsTruncatableNode).toList();
1272 children.add(DiagnosticsNode.message('...'));
1273 }
1274 } else {
1275 properties = propertiesIterable.toList();
1276 }
1277
1278 // If the node does not show a separator and there is no description then
1279 // we should not place a separator between the name and the value.
1280 // Essentially in this case the properties are treated a bit like a value.
1281 if ((properties.isNotEmpty || children.isNotEmpty || node.emptyBodyDescription != null) &&
1282 (node.showSeparator || description.isNotEmpty)) {
1283 builder.write(config.afterDescriptionIfBody);
1284 }
1285
1286 if (config.lineBreakProperties) {
1287 builder.write(config.lineBreak);
1288 }
1289
1290 if (properties.isNotEmpty) {
1291 builder.write(config.beforeProperties);
1292 }
1293
1294 builder.incrementPrefixOtherLines(config.bodyIndent, updateCurrentLine: false);
1295
1296 if (node.emptyBodyDescription != null &&
1297 properties.isEmpty &&
1298 children.isEmpty &&
1299 prefixLineOne.isNotEmpty) {
1300 builder.write(node.emptyBodyDescription!);
1301 if (config.lineBreakProperties) {
1302 builder.write(config.lineBreak);
1303 }
1304 }
1305
1306 for (int i = 0; i < properties.length; ++i) {
1307 final DiagnosticsNode property = properties[i];
1308 if (i > 0) {
1309 builder.write(config.propertySeparator);
1310 }
1311
1312 final TextTreeConfiguration propertyStyle = property.textTreeConfiguration!;
1313 if (_isSingleLine(property.style)) {
1314 // We have to treat single line properties slightly differently to deal
1315 // with cases where a single line properties output may not have single
1316 // linebreak.
1317 final String propertyRender = render(property,
1318 prefixLineOne: propertyStyle.prefixLineOne,
1319 prefixOtherLines: '${propertyStyle.childLinkSpace}${propertyStyle.prefixOtherLines}',
1320 parentConfiguration: config,
1321 );
1322 final List<String> propertyLines = propertyRender.split('\n');
1323 if (propertyLines.length == 1 && !config.lineBreakProperties) {
1324 builder.write(propertyLines.first);
1325 } else {
1326 builder.write(propertyRender);
1327 if (!propertyRender.endsWith('\n')) {
1328 builder.write('\n');
1329 }
1330 }
1331 } else {
1332 final String propertyRender = render(property,
1333 prefixLineOne: '${builder.prefixOtherLines}${propertyStyle.prefixLineOne}',
1334 prefixOtherLines: '${builder.prefixOtherLines}${propertyStyle.childLinkSpace}${propertyStyle.prefixOtherLines}',
1335 parentConfiguration: config,
1336 );
1337 builder.writeRawLines(propertyRender);
1338 }
1339 }
1340 if (properties.isNotEmpty) {
1341 builder.write(config.afterProperties);
1342 }
1343
1344 builder.write(config.mandatoryAfterProperties);
1345
1346 if (!config.lineBreakProperties) {
1347 builder.write(config.lineBreak);
1348 }
1349
1350 final String prefixChildren = config.bodyIndent;
1351 final String prefixChildrenRaw = '$prefixOtherLines$prefixChildren';
1352 if (children.isEmpty &&
1353 config.addBlankLineIfNoChildren &&
1354 builder.requiresMultipleLines &&
1355 builder.prefixOtherLines!.trimRight().isNotEmpty
1356 ) {
1357 builder.write(config.lineBreak);
1358 }
1359
1360 if (children.isNotEmpty && config.showChildren) {
1361 if (config.isBlankLineBetweenPropertiesAndChildren &&
1362 properties.isNotEmpty &&
1363 children.first.textTreeConfiguration!.isBlankLineBetweenPropertiesAndChildren) {
1364 builder.write(config.lineBreak);
1365 }
1366
1367 builder.prefixOtherLines = prefixOtherLines;
1368
1369 for (int i = 0; i < children.length; i++) {
1370 final DiagnosticsNode child = children[i];
1371 final TextTreeConfiguration childConfig = _childTextConfiguration(child, config)!;
1372 if (i == children.length - 1) {
1373 final String lastChildPrefixLineOne = '$prefixChildrenRaw${childConfig.prefixLastChildLineOne}';
1374 final String childPrefixOtherLines = '$prefixChildrenRaw${childConfig.childLinkSpace}${childConfig.prefixOtherLines}';
1375 builder.writeRawLines(render(
1376 child,
1377 prefixLineOne: lastChildPrefixLineOne,
1378 prefixOtherLines: childPrefixOtherLines,
1379 parentConfiguration: config,
1380 ));
1381 if (childConfig.footer.isNotEmpty) {
1382 builder.prefixOtherLines = prefixChildrenRaw;
1383 builder.write('${childConfig.childLinkSpace}${childConfig.footer}');
1384 if (childConfig.mandatoryFooter.isNotEmpty) {
1385 builder.writeStretched(
1386 childConfig.mandatoryFooter,
1387 math.max(builder.wrapWidth!, _wrapWidthProperties + childPrefixOtherLines.length),
1388 );
1389 }
1390 builder.write(config.lineBreak);
1391 }
1392 } else {
1393 final TextTreeConfiguration nextChildStyle = _childTextConfiguration(children[i + 1], config)!;
1394 final String childPrefixLineOne = '$prefixChildrenRaw${childConfig.prefixLineOne}';
1395 final String childPrefixOtherLines ='$prefixChildrenRaw${nextChildStyle.linkCharacter}${childConfig.prefixOtherLines}';
1396 builder.writeRawLines(render(
1397 child,
1398 prefixLineOne: childPrefixLineOne,
1399 prefixOtherLines: childPrefixOtherLines,
1400 parentConfiguration: config,
1401 ));
1402 if (childConfig.footer.isNotEmpty) {
1403 builder.prefixOtherLines = prefixChildrenRaw;
1404 builder.write('${childConfig.linkCharacter}${childConfig.footer}');
1405 if (childConfig.mandatoryFooter.isNotEmpty) {
1406 builder.writeStretched(
1407 childConfig.mandatoryFooter,
1408 math.max(builder.wrapWidth!, _wrapWidthProperties + childPrefixOtherLines.length),
1409 );
1410 }
1411 builder.write(config.lineBreak);
1412 }
1413 }
1414 }
1415 }
1416 if (parentConfiguration == null && config.mandatoryFooter.isNotEmpty) {
1417 builder.writeStretched(config.mandatoryFooter, builder.wrapWidth!);
1418 builder.write(config.lineBreak);
1419 }
1420 return builder.build();
1421 }
1422}
1423
1424/// Defines diagnostics data for a [value].
1425///
1426/// For debug and profile modes, [DiagnosticsNode] provides a high quality
1427/// multiline string dump via [toStringDeep]. The core members are the [name],
1428/// [toDescription], [getProperties], [value], and [getChildren]. All other
1429/// members exist typically to provide hints for how [toStringDeep] and
1430/// debugging tools should format output.
1431///
1432/// In release mode, far less information is retained and some information may
1433/// not print at all.
1434abstract class DiagnosticsNode {
1435 /// Initializes the object.
1436 ///
1437 /// The [style], [showName], and [showSeparator] arguments must not
1438 /// be null.
1439 DiagnosticsNode({
1440 required this.name,
1441 this.style,
1442 this.showName = true,
1443 this.showSeparator = true,
1444 this.linePrefix,
1445 }) : assert(
1446 // A name ending with ':' indicates that the user forgot that the ':' will
1447 // be automatically added for them when generating descriptions of the
1448 // property.
1449 name == null || !name.endsWith(':'),
1450 'Names of diagnostic nodes must not end with colons.\n'
1451 'name:\n'
1452 ' "$name"',
1453 );
1454
1455 /// Diagnostics containing just a string `message` and not a concrete name or
1456 /// value.
1457 ///
1458 /// See also:
1459 ///
1460 /// * [MessageProperty], which is better suited to messages that are to be
1461 /// formatted like a property with a separate name and message.
1462 factory DiagnosticsNode.message(
1463 String message, {
1464 DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
1465 DiagnosticLevel level = DiagnosticLevel.info,
1466 bool allowWrap = true,
1467 }) {
1468 return DiagnosticsProperty<void>(
1469 '',
1470 null,
1471 description: message,
1472 style: style,
1473 showName: false,
1474 allowWrap: allowWrap,
1475 level: level,
1476 );
1477 }
1478
1479 /// Label describing the [DiagnosticsNode], typically shown before a separator
1480 /// (see [showSeparator]).
1481 ///
1482 /// The name will be omitted if the [showName] property is false.
1483 final String? name;
1484
1485 /// Returns a description with a short summary of the node itself not
1486 /// including children or properties.
1487 ///
1488 /// `parentConfiguration` specifies how the parent is rendered as text art.
1489 /// For example, if the parent does not line break between properties, the
1490 /// description of a property should also be a single line if possible.
1491 String toDescription({ TextTreeConfiguration? parentConfiguration });
1492
1493 /// Whether to show a separator between [name] and description.
1494 ///
1495 /// If false, name and description should be shown with no separation.
1496 /// `:` is typically used as a separator when displaying as text.
1497 final bool showSeparator;
1498
1499 /// Whether the diagnostic should be filtered due to its [level] being lower
1500 /// than `minLevel`.
1501 ///
1502 /// If `minLevel` is [DiagnosticLevel.hidden] no diagnostics will be filtered.
1503 /// If `minLevel` is [DiagnosticLevel.off] all diagnostics will be filtered.
1504 bool isFiltered(DiagnosticLevel minLevel) => kReleaseMode || level.index < minLevel.index;
1505
1506 /// Priority level of the diagnostic used to control which diagnostics should
1507 /// be shown and filtered.
1508 ///
1509 /// Typically this only makes sense to set to a different value than
1510 /// [DiagnosticLevel.info] for diagnostics representing properties. Some
1511 /// subclasses have a [level] argument to their constructor which influences
1512 /// the value returned here but other factors also influence it. For example,
1513 /// whether an exception is thrown computing a property value
1514 /// [DiagnosticLevel.error] is returned.
1515 DiagnosticLevel get level => kReleaseMode ? DiagnosticLevel.hidden : DiagnosticLevel.info;
1516
1517 /// Whether the name of the property should be shown when showing the default
1518 /// view of the tree.
1519 ///
1520 /// This could be set to false (hiding the name) if the value's description
1521 /// will make the name self-evident.
1522 final bool showName;
1523
1524 /// Prefix to include at the start of each line.
1525 final String? linePrefix;
1526
1527 /// Description to show if the node has no displayed properties or children.
1528 String? get emptyBodyDescription => null;
1529
1530 /// The actual object this is diagnostics data for.
1531 Object? get value;
1532
1533 /// Hint for how the node should be displayed.
1534 final DiagnosticsTreeStyle? style;
1535
1536 /// Whether to wrap text on onto multiple lines or not.
1537 bool get allowWrap => false;
1538
1539 /// Whether to wrap the name onto multiple lines or not.
1540 bool get allowNameWrap => false;
1541
1542 /// Whether to allow truncation when displaying the node and its children.
1543 bool get allowTruncate => false;
1544
1545 /// Properties of this [DiagnosticsNode].
1546 ///
1547 /// Properties and children are kept distinct even though they are both
1548 /// [List<DiagnosticsNode>] because they should be grouped differently.
1549 List<DiagnosticsNode> getProperties();
1550
1551 /// Children of this [DiagnosticsNode].
1552 ///
1553 /// See also:
1554 ///
1555 /// * [getProperties], which returns the properties of the [DiagnosticsNode]
1556 /// object.
1557 List<DiagnosticsNode> getChildren();
1558
1559 String get _separator => showSeparator ? ':' : '';
1560
1561 /// Converts the properties ([getProperties]) of this node to a form useful
1562 /// for [Timeline] event arguments (as in [Timeline.startSync]).
1563 ///
1564 /// Children ([getChildren]) are omitted.
1565 ///
1566 /// This method is only valid in debug builds. In profile builds, this method
1567 /// throws an exception. In release builds it returns null.
1568 ///
1569 /// See also:
1570 ///
1571 /// * [toJsonMap], which converts this node to a structured form intended for
1572 /// data exchange (e.g. with an IDE).
1573 Map<String, String>? toTimelineArguments() {
1574 if (!kReleaseMode) {
1575 // We don't throw in release builds, to avoid hurting users. We also don't do anything useful.
1576 if (kProfileMode) {
1577 throw FlutterError(
1578 // Parts of this string are searched for verbatim by a test in dev/bots/test.dart.
1579 '$DiagnosticsNode.toTimelineArguments used in non-debug build.\n'
1580 'The $DiagnosticsNode.toTimelineArguments API is expensive and causes timeline traces '
1581 'to be non-representative. As such, it should not be used in profile builds. However, '
1582 'this application is compiled in profile mode and yet still invoked the method.'
1583 );
1584 }
1585 final Map<String, String> result = <String, String>{};
1586 for (final DiagnosticsNode property in getProperties()) {
1587 if (property.name != null) {
1588 result[property.name!] = property.toDescription(parentConfiguration: singleLineTextConfiguration);
1589 }
1590 }
1591 return result;
1592 }
1593 return null;
1594 }
1595
1596 /// Serialize the node to a JSON map according to the configuration provided
1597 /// in the [DiagnosticsSerializationDelegate].
1598 ///
1599 /// Subclasses should override if they have additional properties that are
1600 /// useful for the GUI tools that consume this JSON.
1601 ///
1602 /// See also:
1603 ///
1604 /// * [WidgetInspectorService], which forms the bridge between JSON returned
1605 /// by this method and interactive tree views in the Flutter IntelliJ
1606 /// plugin.
1607 @mustCallSuper
1608 Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
1609 Map<String, Object?> result = <String, Object?>{};
1610 assert(() {
1611 final bool hasChildren = getChildren().isNotEmpty;
1612 result = <String, Object?>{
1613 'description': toDescription(),
1614 'type': runtimeType.toString(),
1615 if (name != null)
1616 'name': name,
1617 if (!showSeparator)
1618 'showSeparator': showSeparator,
1619 if (level != DiagnosticLevel.info)
1620 'level': level.name,
1621 if (!showName)
1622 'showName': showName,
1623 if (emptyBodyDescription != null)
1624 'emptyBodyDescription': emptyBodyDescription,
1625 if (style != DiagnosticsTreeStyle.sparse)
1626 'style': style!.name,
1627 if (allowTruncate)
1628 'allowTruncate': allowTruncate,
1629 if (hasChildren)
1630 'hasChildren': hasChildren,
1631 if (linePrefix?.isNotEmpty ?? false)
1632 'linePrefix': linePrefix,
1633 if (!allowWrap)
1634 'allowWrap': allowWrap,
1635 if (allowNameWrap)
1636 'allowNameWrap': allowNameWrap,
1637 ...delegate.additionalNodeProperties(this),
1638 if (delegate.includeProperties)
1639 'properties': toJsonList(
1640 delegate.filterProperties(getProperties(), this),
1641 this,
1642 delegate,
1643 ),
1644 if (delegate.subtreeDepth > 0)
1645 'children': toJsonList(
1646 delegate.filterChildren(getChildren(), this),
1647 this,
1648 delegate,
1649 ),
1650 };
1651 return true;
1652 }());
1653 return result;
1654 }
1655
1656 /// Serializes a [List] of [DiagnosticsNode]s to a JSON list according to
1657 /// the configuration provided by the [DiagnosticsSerializationDelegate].
1658 ///
1659 /// The provided `nodes` may be properties or children of the `parent`
1660 /// [DiagnosticsNode].
1661 static List<Map<String, Object?>> toJsonList(
1662 List<DiagnosticsNode>? nodes,
1663 DiagnosticsNode? parent,
1664 DiagnosticsSerializationDelegate delegate,
1665 ) {
1666 bool truncated = false;
1667 if (nodes == null) {
1668 return const <Map<String, Object?>>[];
1669 }
1670 final int originalNodeCount = nodes.length;
1671 nodes = delegate.truncateNodesList(nodes, parent);
1672 if (nodes.length != originalNodeCount) {
1673 nodes.add(DiagnosticsNode.message('...'));
1674 truncated = true;
1675 }
1676 final List<Map<String, Object?>> json = nodes.map<Map<String, Object?>>((DiagnosticsNode node) {
1677 return node.toJsonMap(delegate.delegateForNode(node));
1678 }).toList();
1679 if (truncated) {
1680 json.last['truncated'] = true;
1681 }
1682 return json;
1683 }
1684
1685 /// Returns a string representation of this diagnostic that is compatible with
1686 /// the style of the parent if the node is not the root.
1687 ///
1688 /// `parentConfiguration` specifies how the parent is rendered as text art.
1689 /// For example, if the parent places all properties on one line, the
1690 /// [toString] for each property should avoid line breaks if possible.
1691 ///
1692 /// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
1693 /// in the output.
1694 ///
1695 /// In release mode, far less information is retained and some information may
1696 /// not print at all.
1697 @override
1698 String toString({
1699 TextTreeConfiguration? parentConfiguration,
1700 DiagnosticLevel minLevel = DiagnosticLevel.info,
1701 }) {
1702 String result = super.toString();
1703 assert(style != null);
1704 assert(() {
1705 if (_isSingleLine(style)) {
1706 result = toStringDeep(parentConfiguration: parentConfiguration, minLevel: minLevel);
1707 } else {
1708 final String description = toDescription(parentConfiguration: parentConfiguration);
1709
1710 if (name == null || name!.isEmpty || !showName) {
1711 result = description;
1712 } else {
1713 result = description.contains('\n') ? '$name$_separator\n$description'
1714 : '$name$_separator $description';
1715 }
1716 }
1717 return true;
1718 }());
1719 return result;
1720 }
1721
1722 /// Returns a configuration specifying how this object should be rendered
1723 /// as text art.
1724 @protected
1725 TextTreeConfiguration? get textTreeConfiguration {
1726 assert(style != null);
1727 return switch (style!) {
1728 DiagnosticsTreeStyle.none => null,
1729 DiagnosticsTreeStyle.dense => denseTextConfiguration,
1730 DiagnosticsTreeStyle.sparse => sparseTextConfiguration,
1731 DiagnosticsTreeStyle.offstage => dashedTextConfiguration,
1732 DiagnosticsTreeStyle.whitespace => whitespaceTextConfiguration,
1733 DiagnosticsTreeStyle.transition => transitionTextConfiguration,
1734 DiagnosticsTreeStyle.singleLine => singleLineTextConfiguration,
1735 DiagnosticsTreeStyle.errorProperty => errorPropertyTextConfiguration,
1736 DiagnosticsTreeStyle.shallow => shallowTextConfiguration,
1737 DiagnosticsTreeStyle.error => errorTextConfiguration,
1738 DiagnosticsTreeStyle.flat => flatTextConfiguration,
1739
1740 // Truncate children doesn't really need its own text style as the
1741 // rendering is quite custom.
1742 DiagnosticsTreeStyle.truncateChildren => whitespaceTextConfiguration,
1743 };
1744 }
1745
1746 /// Returns a string representation of this node and its descendants.
1747 ///
1748 /// `prefixLineOne` will be added to the front of the first line of the
1749 /// output. `prefixOtherLines` will be added to the front of each other line.
1750 /// If `prefixOtherLines` is null, the `prefixLineOne` is used for every line.
1751 /// By default, there is no prefix.
1752 ///
1753 /// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
1754 /// in the output.
1755 ///
1756 /// `wrapWidth` specifies the column number where word wrapping will be
1757 /// applied.
1758 ///
1759 /// The [toStringDeep] method takes other arguments, but those are intended
1760 /// for internal use when recursing to the descendants, and so can be ignored.
1761 ///
1762 /// In release mode, far less information is retained and some information may
1763 /// not print at all.
1764 ///
1765 /// See also:
1766 ///
1767 /// * [toString], for a brief description of the [value] but not its
1768 /// children.
1769 String toStringDeep({
1770 String prefixLineOne = '',
1771 String? prefixOtherLines,
1772 TextTreeConfiguration? parentConfiguration,
1773 DiagnosticLevel minLevel = DiagnosticLevel.debug,
1774 int wrapWidth = 65,
1775 }) {
1776 String result = '';
1777 assert(() {
1778 result = TextTreeRenderer(
1779 minLevel: minLevel,
1780 wrapWidth: wrapWidth,
1781 ).render(
1782 this,
1783 prefixLineOne: prefixLineOne,
1784 prefixOtherLines: prefixOtherLines,
1785 parentConfiguration: parentConfiguration,
1786 );
1787 return true;
1788 }());
1789 return result;
1790 }
1791}
1792
1793/// Debugging message displayed like a property.
1794///
1795/// {@tool snippet}
1796///
1797/// The following two properties are better expressed using this
1798/// [MessageProperty] class, rather than [StringProperty], as the intent is to
1799/// show a message with property style display rather than to describe the value
1800/// of an actual property of the object:
1801///
1802/// ```dart
1803/// MessageProperty table = MessageProperty('table size', '$columns\u00D7$rows');
1804/// MessageProperty usefulness = MessageProperty('usefulness ratio', 'no metrics collected yet (never painted)');
1805/// ```
1806/// {@end-tool}
1807/// {@tool snippet}
1808///
1809/// On the other hand, [StringProperty] is better suited when the property has a
1810/// concrete value that is a string:
1811///
1812/// ```dart
1813/// StringProperty name = StringProperty('name', _name);
1814/// ```
1815/// {@end-tool}
1816///
1817/// See also:
1818///
1819/// * [DiagnosticsNode.message], which serves the same role for messages
1820/// without a clear property name.
1821/// * [StringProperty], which is a better fit for properties with string values.
1822class MessageProperty extends DiagnosticsProperty<void> {
1823 /// Create a diagnostics property that displays a message.
1824 ///
1825 /// Messages have no concrete [value] (so [value] will return null). The
1826 /// message is stored as the description.
1827 MessageProperty(
1828 String name,
1829 String message, {
1830 DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
1831 DiagnosticLevel level = DiagnosticLevel.info,
1832 }) : super(name, null, description: message, style: style, level: level);
1833}
1834
1835/// Property which encloses its string [value] in quotes.
1836///
1837/// See also:
1838///
1839/// * [MessageProperty], which is a better fit for showing a message
1840/// instead of describing a property with a string value.
1841class StringProperty extends DiagnosticsProperty<String> {
1842 /// Create a diagnostics property for strings.
1843 StringProperty(
1844 String super.name,
1845 super.value, {
1846 super.description,
1847 super.tooltip,
1848 super.showName,
1849 super.defaultValue,
1850 this.quoted = true,
1851 super.ifEmpty,
1852 super.style,
1853 super.level,
1854 });
1855
1856 /// Whether the value is enclosed in double quotes.
1857 final bool quoted;
1858
1859 @override
1860 Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
1861 final Map<String, Object?> json = super.toJsonMap(delegate);
1862 json['quoted'] = quoted;
1863 return json;
1864 }
1865
1866 @override
1867 String valueToString({ TextTreeConfiguration? parentConfiguration }) {
1868 String? text = _description ?? value;
1869 if (parentConfiguration != null &&
1870 !parentConfiguration.lineBreakProperties &&
1871 text != null) {
1872 // Escape linebreaks in multiline strings to avoid confusing output when
1873 // the parent of this node is trying to display all properties on the same
1874 // line.
1875 text = text.replaceAll('\n', r'\n');
1876 }
1877
1878 if (quoted && text != null) {
1879 // An empty value would not appear empty after being surrounded with
1880 // quotes so we have to handle this case separately.
1881 if (ifEmpty != null && text.isEmpty) {
1882 return ifEmpty!;
1883 }
1884 return '"$text"';
1885 }
1886 return text.toString();
1887 }
1888}
1889
1890abstract class _NumProperty<T extends num> extends DiagnosticsProperty<T> {
1891 _NumProperty(
1892 String super.name,
1893 super.value, {
1894 super.ifNull,
1895 this.unit,
1896 super.showName,
1897 super.defaultValue,
1898 super.tooltip,
1899 super.style,
1900 super.level,
1901 });
1902
1903 _NumProperty.lazy(
1904 String super.name,
1905 super.computeValue, {
1906 super.ifNull,
1907 this.unit,
1908 super.showName,
1909 super.defaultValue,
1910 super.tooltip,
1911 super.style,
1912 super.level,
1913 }) : super.lazy();
1914
1915 @override
1916 Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
1917 final Map<String, Object?> json = super.toJsonMap(delegate);
1918 if (unit != null) {
1919 json['unit'] = unit;
1920 }
1921
1922 json['numberToString'] = numberToString();
1923 return json;
1924 }
1925
1926 /// Optional unit the [value] is measured in.
1927 ///
1928 /// Unit must be acceptable to display immediately after a number with no
1929 /// spaces. For example: 'physical pixels per logical pixel' should be a
1930 /// [tooltip] not a [unit].
1931 final String? unit;
1932
1933 /// String describing just the numeric [value] without a unit suffix.
1934 String numberToString();
1935
1936 @override
1937 String valueToString({ TextTreeConfiguration? parentConfiguration }) {
1938 if (value == null) {
1939 return value.toString();
1940 }
1941
1942 return unit != null ? '${numberToString()}$unit' : numberToString();
1943 }
1944}
1945/// Property describing a [double] [value] with an optional [unit] of measurement.
1946///
1947/// Numeric formatting is optimized for debug message readability.
1948class DoubleProperty extends _NumProperty<double> {
1949 /// If specified, [unit] describes the unit for the [value] (e.g. px).
1950 DoubleProperty(
1951 super.name,
1952 super.value, {
1953 super.ifNull,
1954 super.unit,
1955 super.tooltip,
1956 super.defaultValue,
1957 super.showName,
1958 super.style,
1959 super.level,
1960 });
1961
1962 /// Property with a [value] that is computed only when needed.
1963 ///
1964 /// Use if computing the property [value] may throw an exception or is
1965 /// expensive.
1966 DoubleProperty.lazy(
1967 super.name,
1968 super.computeValue, {
1969 super.ifNull,
1970 super.showName,
1971 super.unit,
1972 super.tooltip,
1973 super.defaultValue,
1974 super.level,
1975 }) : super.lazy();
1976
1977 @override
1978 String numberToString() => debugFormatDouble(value);
1979}
1980
1981/// An int valued property with an optional unit the value is measured in.
1982///
1983/// Examples of units include 'px' and 'ms'.
1984class IntProperty extends _NumProperty<int> {
1985 /// Create a diagnostics property for integers.
1986 IntProperty(
1987 super.name,
1988 super.value, {
1989 super.ifNull,
1990 super.showName,
1991 super.unit,
1992 super.defaultValue,
1993 super.style,
1994 super.level,
1995 });
1996
1997 @override
1998 String numberToString() => value.toString();
1999}
2000
2001/// Property which clamps a [double] to between 0 and 1 and formats it as a
2002/// percentage.
2003class PercentProperty extends DoubleProperty {
2004 /// Create a diagnostics property for doubles that represent percentages or
2005 /// fractions.
2006 ///
2007 /// Setting [showName] to false is often reasonable for [PercentProperty]
2008 /// objects, as the fact that the property is shown as a percentage tends to
2009 /// be sufficient to disambiguate its meaning.
2010 PercentProperty(
2011 super.name,
2012 super.fraction, {
2013 super.ifNull,
2014 super.showName,
2015 super.tooltip,
2016 super.unit,
2017 super.level,
2018 });
2019
2020 @override
2021 String valueToString({ TextTreeConfiguration? parentConfiguration }) {
2022 if (value == null) {
2023 return value.toString();
2024 }
2025 return unit != null ? '${numberToString()} $unit' : numberToString();
2026 }
2027
2028 @override
2029 String numberToString() {
2030 final double? v = value;
2031 if (v == null) {
2032 return value.toString();
2033 }
2034 return '${(clampDouble(v, 0.0, 1.0) * 100.0).toStringAsFixed(1)}%';
2035 }
2036}
2037
2038/// Property where the description is either [ifTrue] or [ifFalse] depending on
2039/// whether [value] is true or false.
2040///
2041/// Using [FlagProperty] instead of [DiagnosticsProperty<bool>] can make
2042/// diagnostics display more polished. For example, given a property named
2043/// `visible` that is typically true, the following code will return 'hidden'
2044/// when `visible` is false and nothing when visible is true, in contrast to
2045/// `visible: true` or `visible: false`.
2046///
2047/// {@tool snippet}
2048///
2049/// ```dart
2050/// FlagProperty(
2051/// 'visible',
2052/// value: true,
2053/// ifFalse: 'hidden',
2054/// )
2055/// ```
2056/// {@end-tool}
2057/// {@tool snippet}
2058///
2059/// [FlagProperty] should also be used instead of [DiagnosticsProperty<bool>]
2060/// if showing the bool value would not clearly indicate the meaning of the
2061/// property value.
2062///
2063/// ```dart
2064/// FlagProperty(
2065/// 'inherit',
2066/// value: inherit,
2067/// ifTrue: '<all styles inherited>',
2068/// ifFalse: '<no style specified>',
2069/// )
2070/// ```
2071/// {@end-tool}
2072///
2073/// See also:
2074///
2075/// * [ObjectFlagProperty], which provides similar behavior describing whether
2076/// a [value] is null.
2077class FlagProperty extends DiagnosticsProperty<bool> {
2078 /// Constructs a FlagProperty with the given descriptions with the specified descriptions.
2079 ///
2080 /// [showName] defaults to false as typically [ifTrue] and [ifFalse] should
2081 /// be descriptions that make the property name redundant.
2082 FlagProperty(
2083 String name, {
2084 required bool? value,
2085 this.ifTrue,
2086 this.ifFalse,
2087 bool showName = false,
2088 Object? defaultValue,
2089 DiagnosticLevel level = DiagnosticLevel.info,
2090 }) : assert(ifTrue != null || ifFalse != null),
2091 super(
2092 name,
2093 value,
2094 showName: showName,
2095 defaultValue: defaultValue,
2096 level: level,
2097 );
2098
2099 @override
2100 Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
2101 final Map<String, Object?> json = super.toJsonMap(delegate);
2102 if (ifTrue != null) {
2103 json['ifTrue'] = ifTrue;
2104 }
2105 if (ifFalse != null) {
2106 json['ifFalse'] = ifFalse;
2107 }
2108
2109 return json;
2110 }
2111
2112 /// Description to use if the property [value] is true.
2113 ///
2114 /// If not specified and [value] equals true the property's priority [level]
2115 /// will be [DiagnosticLevel.hidden].
2116 final String? ifTrue;
2117
2118 /// Description to use if the property value is false.
2119 ///
2120 /// If not specified and [value] equals false, the property's priority [level]
2121 /// will be [DiagnosticLevel.hidden].
2122 final String? ifFalse;
2123
2124 @override
2125 String valueToString({ TextTreeConfiguration? parentConfiguration }) {
2126 return switch (value) {
2127 true when ifTrue != null => ifTrue!,
2128 false when ifFalse != null => ifFalse!,
2129 _ => super.valueToString(parentConfiguration: parentConfiguration),
2130 };
2131 }
2132
2133 @override
2134 bool get showName {
2135 if (value == null || ((value ?? false) && ifTrue == null) || (!(value ?? true) && ifFalse == null)) {
2136 // We are missing a description for the flag value so we need to show the
2137 // flag name. The property will have DiagnosticLevel.hidden for this case
2138 // so users will not see this property in this case unless they are
2139 // displaying hidden properties.
2140 return true;
2141 }
2142 return super.showName;
2143 }
2144
2145 @override
2146 DiagnosticLevel get level {
2147 return switch (value) {
2148 true when ifTrue == null => DiagnosticLevel.hidden,
2149 false when ifFalse == null => DiagnosticLevel.hidden,
2150 _ => super.level,
2151 };
2152 }
2153}
2154
2155/// Property with an `Iterable<T>` [value] that can be displayed with
2156/// different [DiagnosticsTreeStyle] for custom rendering.
2157///
2158/// If [style] is [DiagnosticsTreeStyle.singleLine], the iterable is described
2159/// as a comma separated list, otherwise the iterable is described as a line
2160/// break separated list.
2161class IterableProperty<T> extends DiagnosticsProperty<Iterable<T>> {
2162 /// Create a diagnostics property for iterables (e.g. lists).
2163 ///
2164 /// The [ifEmpty] argument is used to indicate how an iterable [value] with 0
2165 /// elements is displayed. If [ifEmpty] equals null that indicates that an
2166 /// empty iterable [value] is not interesting to display similar to how
2167 /// [defaultValue] is used to indicate that a specific concrete value is not
2168 /// interesting to display.
2169 IterableProperty(
2170 String super.name,
2171 super.value, {
2172 super.defaultValue,
2173 super.ifNull,
2174 super.ifEmpty = '[]',
2175 super.style,
2176 super.showName,
2177 super.showSeparator,
2178 super.level,
2179 });
2180
2181 @override
2182 String valueToString({TextTreeConfiguration? parentConfiguration}) {
2183 if (value == null) {
2184 return value.toString();
2185 }
2186
2187 if (value!.isEmpty) {
2188 return ifEmpty ?? '[]';
2189 }
2190
2191 final Iterable<String> formattedValues = value!.map((T v) {
2192 if (T == double && v is double) {
2193 return debugFormatDouble(v);
2194 } else {
2195 return v.toString();
2196 }
2197 });
2198
2199 if (parentConfiguration != null && !parentConfiguration.lineBreakProperties) {
2200 // Always display the value as a single line and enclose the iterable
2201 // value in brackets to avoid ambiguity.
2202 return '[${formattedValues.join(', ')}]';
2203 }
2204
2205 return formattedValues.join(_isSingleLine(style) ? ', ' : '\n');
2206 }
2207
2208 /// Priority level of the diagnostic used to control which diagnostics should
2209 /// be shown and filtered.
2210 ///
2211 /// If [ifEmpty] is null and the [value] is an empty [Iterable] then level
2212 /// [DiagnosticLevel.fine] is returned in a similar way to how an
2213 /// [ObjectFlagProperty] handles when [ifNull] is null and the [value] is
2214 /// null.
2215 @override
2216 DiagnosticLevel get level {
2217 if (ifEmpty == null && value != null && value!.isEmpty && super.level != DiagnosticLevel.hidden) {
2218 return DiagnosticLevel.fine;
2219 }
2220 return super.level;
2221 }
2222
2223 @override
2224 Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
2225 final Map<String, Object?> json = super.toJsonMap(delegate);
2226 if (value != null) {
2227 json['values'] = value!.map<String>((T value) => value.toString()).toList();
2228 }
2229 return json;
2230 }
2231}
2232
2233/// [DiagnosticsProperty] that has an [Enum] as value.
2234///
2235/// The enum value is displayed with the enum name stripped. For example:
2236/// [HitTestBehavior.deferToChild] is shown as `deferToChild`.
2237///
2238/// This class can be used with enums and returns the enum's name getter. It
2239/// can also be used with nullable properties; the null value is represented as
2240/// `null`.
2241///
2242/// See also:
2243///
2244/// * [DiagnosticsProperty] which documents named parameters common to all
2245/// [DiagnosticsProperty].
2246class EnumProperty<T extends Enum?> extends DiagnosticsProperty<T> {
2247 /// Create a diagnostics property that displays an enum.
2248 ///
2249 /// The [level] argument must also not be null.
2250 EnumProperty(
2251 String super.name,
2252 super.value, {
2253 super.defaultValue,
2254 super.level,
2255 });
2256
2257 @override
2258 String valueToString({ TextTreeConfiguration? parentConfiguration }) {
2259 return value?.name ?? 'null';
2260 }
2261}
2262
2263/// A property where the important diagnostic information is primarily whether
2264/// the [value] is present (non-null) or absent (null), rather than the actual
2265/// value of the property itself.
2266///
2267/// The [ifPresent] and [ifNull] strings describe the property [value] when it
2268/// is non-null and null respectively. If one of [ifPresent] or [ifNull] is
2269/// omitted, that is taken to mean that [level] should be
2270/// [DiagnosticLevel.hidden] when [value] is non-null or null respectively.
2271///
2272/// This kind of diagnostics property is typically used for opaque
2273/// values, like closures, where presenting the actual object is of dubious
2274/// value but where reporting the presence or absence of the value is much more
2275/// useful.
2276///
2277/// See also:
2278///
2279///
2280/// * [FlagsSummary], which provides similar functionality but accepts multiple
2281/// flags under the same name, and is preferred if there are multiple such
2282/// values that can fit into a same category (such as "listeners").
2283/// * [FlagProperty], which provides similar functionality describing whether
2284/// a [value] is true or false.
2285class ObjectFlagProperty<T> extends DiagnosticsProperty<T> {
2286 /// Create a diagnostics property for values that can be present (non-null) or
2287 /// absent (null), but for which the exact value's [Object.toString]
2288 /// representation is not very transparent (e.g. a callback).
2289 ///
2290 /// At least one of [ifPresent] or [ifNull] must be non-null.
2291 ObjectFlagProperty(
2292 String super.name,
2293 super.value, {
2294 this.ifPresent,
2295 super.ifNull,
2296 super.showName = false,
2297 super.level,
2298 }) : assert(ifPresent != null || ifNull != null);
2299
2300 /// Shorthand constructor to describe whether the property has a value.
2301 ///
2302 /// Only use if prefixing the property name with the word 'has' is a good
2303 /// flag name.
2304 ObjectFlagProperty.has(
2305 String super.name,
2306 super.value, {
2307 super.level,
2308 }) : ifPresent = 'has $name',
2309 super(
2310 showName: false,
2311 );
2312
2313 /// Description to use if the property [value] is not null.
2314 ///
2315 /// If the property [value] is not null and [ifPresent] is null, the
2316 /// [level] for the property is [DiagnosticLevel.hidden] and the description
2317 /// from superclass is used.
2318 final String? ifPresent;
2319
2320 @override
2321 String valueToString({ TextTreeConfiguration? parentConfiguration }) {
2322 if (value != null) {
2323 if (ifPresent != null) {
2324 return ifPresent!;
2325 }
2326 } else {
2327 if (ifNull != null) {
2328 return ifNull!;
2329 }
2330 }
2331 return super.valueToString(parentConfiguration: parentConfiguration);
2332 }
2333
2334 @override
2335 bool get showName {
2336 if ((value != null && ifPresent == null) || (value == null && ifNull == null)) {
2337 // We are missing a description for the flag value so we need to show the
2338 // flag name. The property will have DiagnosticLevel.hidden for this case
2339 // so users will not see this property in this case unless they are
2340 // displaying hidden properties.
2341 return true;
2342 }
2343 return super.showName;
2344 }
2345
2346 @override
2347 DiagnosticLevel get level {
2348 if (value != null) {
2349 if (ifPresent == null) {
2350 return DiagnosticLevel.hidden;
2351 }
2352 } else {
2353 if (ifNull == null) {
2354 return DiagnosticLevel.hidden;
2355 }
2356 }
2357
2358 return super.level;
2359 }
2360
2361 @override
2362 Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
2363 final Map<String, Object?> json = super.toJsonMap(delegate);
2364 if (ifPresent != null) {
2365 json['ifPresent'] = ifPresent;
2366 }
2367 return json;
2368 }
2369}
2370
2371/// A summary of multiple properties, indicating whether each of them is present
2372/// (non-null) or absent (null).
2373///
2374/// Each entry of [value] is described by its key. The eventual description will
2375/// be a list of keys of non-null entries.
2376///
2377/// The [ifEmpty] describes the entire collection of [value] when it contains no
2378/// non-null entries. If [ifEmpty] is omitted, [level] will be
2379/// [DiagnosticLevel.hidden] when [value] contains no non-null entries.
2380///
2381/// This kind of diagnostics property is typically used for opaque
2382/// values, like closures, where presenting the actual object is of dubious
2383/// value but where reporting the presence or absence of the value is much more
2384/// useful.
2385///
2386/// See also:
2387///
2388/// * [ObjectFlagProperty], which provides similar functionality but accepts
2389/// only one flag, and is preferred if there is only one entry.
2390/// * [IterableProperty], which provides similar functionality describing
2391/// the values a collection of objects.
2392class FlagsSummary<T> extends DiagnosticsProperty<Map<String, T?>> {
2393 /// Create a summary for multiple properties, indicating whether each of them
2394 /// is present (non-null) or absent (null).
2395 ///
2396 /// The [value], [showName], [showSeparator] and [level] arguments must not be
2397 /// null.
2398 FlagsSummary(
2399 String super.name,
2400 Map<String, T?> super.value, {
2401 super.ifEmpty,
2402 super.showName,
2403 super.showSeparator,
2404 super.level,
2405 });
2406
2407 @override
2408 Map<String, T?> get value => super.value!;
2409
2410 @override
2411 String valueToString({TextTreeConfiguration? parentConfiguration}) {
2412 if (!_hasNonNullEntry() && ifEmpty != null) {
2413 return ifEmpty!;
2414 }
2415
2416 final Iterable<String> formattedValues = _formattedValues();
2417 if (parentConfiguration != null && !parentConfiguration.lineBreakProperties) {
2418 // Always display the value as a single line and enclose the iterable
2419 // value in brackets to avoid ambiguity.
2420 return '[${formattedValues.join(', ')}]';
2421 }
2422
2423 return formattedValues.join(_isSingleLine(style) ? ', ' : '\n');
2424 }
2425
2426 /// Priority level of the diagnostic used to control which diagnostics should
2427 /// be shown and filtered.
2428 ///
2429 /// If [ifEmpty] is null and the [value] contains no non-null entries, then
2430 /// level [DiagnosticLevel.hidden] is returned.
2431 @override
2432 DiagnosticLevel get level {
2433 if (!_hasNonNullEntry() && ifEmpty == null) {
2434 return DiagnosticLevel.hidden;
2435 }
2436 return super.level;
2437 }
2438
2439 @override
2440 Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
2441 final Map<String, Object?> json = super.toJsonMap(delegate);
2442 if (value.isNotEmpty) {
2443 json['values'] = _formattedValues().toList();
2444 }
2445 return json;
2446 }
2447
2448 bool _hasNonNullEntry() => value.values.any((T? o) => o != null);
2449
2450 // An iterable of each entry's description in [value].
2451 //
2452 // For a non-null value, its description is its key.
2453 //
2454 // For a null value, it is omitted unless `includeEmpty` is true and
2455 // [ifEntryNull] contains a corresponding description.
2456 Iterable<String> _formattedValues() {
2457 return value.entries
2458 .where((MapEntry<String, T?> entry) => entry.value != null)
2459 .map((MapEntry<String, T?> entry) => entry.key);
2460 }
2461}
2462
2463/// Signature for computing the value of a property.
2464///
2465/// May throw exception if accessing the property would throw an exception
2466/// and callers must handle that case gracefully. For example, accessing a
2467/// property may trigger an assert that layout constraints were violated.
2468typedef ComputePropertyValueCallback<T> = T? Function();
2469
2470/// Property with a [value] of type [T].
2471///
2472/// If the default `value.toString()` does not provide an adequate description
2473/// of the value, specify `description` defining a custom description.
2474///
2475/// The [showSeparator] property indicates whether a separator should be placed
2476/// between the property [name] and its [value].
2477class DiagnosticsProperty<T> extends DiagnosticsNode {
2478 /// Create a diagnostics property.
2479 ///
2480 /// The [level] argument is just a suggestion and can be overridden if
2481 /// something else about the property causes it to have a lower or higher
2482 /// level. For example, if the property value is null and [missingIfNull] is
2483 /// true, [level] is raised to [DiagnosticLevel.warning].
2484 DiagnosticsProperty(
2485 String? name,
2486 T? value, {
2487 String? description,
2488 String? ifNull,
2489 this.ifEmpty,
2490 super.showName,
2491 super.showSeparator,
2492 this.defaultValue = kNoDefaultValue,
2493 this.tooltip,
2494 this.missingIfNull = false,
2495 super.linePrefix,
2496 this.expandableValue = false,
2497 this.allowWrap = true,
2498 this.allowNameWrap = true,
2499 DiagnosticsTreeStyle super.style = DiagnosticsTreeStyle.singleLine,
2500 DiagnosticLevel level = DiagnosticLevel.info,
2501 }) : _description = description,
2502 _valueComputed = true,
2503 _value = value,
2504 _computeValue = null,
2505 ifNull = ifNull ?? (missingIfNull ? 'MISSING' : null),
2506 _defaultLevel = level,
2507 super(
2508 name: name,
2509 );
2510
2511 /// Property with a [value] that is computed only when needed.
2512 ///
2513 /// Use if computing the property [value] may throw an exception or is
2514 /// expensive.
2515 ///
2516 /// The [level] argument is just a suggestion and can be overridden
2517 /// if something else about the property causes it to have a lower or higher
2518 /// level. For example, if calling `computeValue` throws an exception, [level]
2519 /// will always return [DiagnosticLevel.error].
2520 DiagnosticsProperty.lazy(
2521 String? name,
2522 ComputePropertyValueCallback<T> computeValue, {
2523 String? description,
2524 String? ifNull,
2525 this.ifEmpty,
2526 super.showName,
2527 super.showSeparator,
2528 this.defaultValue = kNoDefaultValue,
2529 this.tooltip,
2530 this.missingIfNull = false,
2531 this.expandableValue = false,
2532 this.allowWrap = true,
2533 this.allowNameWrap = true,
2534 DiagnosticsTreeStyle super.style = DiagnosticsTreeStyle.singleLine,
2535 DiagnosticLevel level = DiagnosticLevel.info,
2536 }) : assert(defaultValue == kNoDefaultValue || defaultValue is T?),
2537 _description = description,
2538 _valueComputed = false,
2539 _value = null,
2540 _computeValue = computeValue,
2541 _defaultLevel = level,
2542 ifNull = ifNull ?? (missingIfNull ? 'MISSING' : null),
2543 super(
2544 name: name,
2545 );
2546
2547 final String? _description;
2548
2549 /// Whether to expose properties and children of the value as properties and
2550 /// children.
2551 final bool expandableValue;
2552
2553 @override
2554 final bool allowWrap;
2555
2556 @override
2557 final bool allowNameWrap;
2558
2559 @override
2560 Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
2561 final T? v = value;
2562 List<Map<String, Object?>>? properties;
2563 if (delegate.expandPropertyValues && delegate.includeProperties && v is Diagnosticable && getProperties().isEmpty) {
2564 // Exclude children for expanded nodes to avoid cycles.
2565 delegate = delegate.copyWith(subtreeDepth: 0, includeProperties: false);
2566 properties = DiagnosticsNode.toJsonList(
2567 delegate.filterProperties(v.toDiagnosticsNode().getProperties(), this),
2568 this,
2569 delegate,
2570 );
2571 }
2572 final Map<String, Object?> json = super.toJsonMap(delegate);
2573 if (properties != null) {
2574 json['properties'] = properties;
2575 }
2576 if (defaultValue != kNoDefaultValue) {
2577 json['defaultValue'] = defaultValue.toString();
2578 }
2579 if (ifEmpty != null) {
2580 json['ifEmpty'] = ifEmpty;
2581 }
2582 if (ifNull != null) {
2583 json['ifNull'] = ifNull;
2584 }
2585 if (tooltip != null) {
2586 json['tooltip'] = tooltip;
2587 }
2588 json['missingIfNull'] = missingIfNull;
2589 if (exception != null) {
2590 json['exception'] = exception.toString();
2591 }
2592 json['propertyType'] = propertyType.toString();
2593 json['defaultLevel'] = _defaultLevel.name;
2594 if (value is Diagnosticable || value is DiagnosticsNode) {
2595 json['isDiagnosticableValue'] = true;
2596 }
2597 if (v is num) {
2598 // TODO(jacob314): Workaround, since JSON.stringify replaces infinity and NaN with null,
2599 // https://github.com/flutter/flutter/issues/39937#issuecomment-529558033)
2600 json['value'] = v.isFinite ? v : v.toString();
2601 }
2602 if (value is String || value is bool || value == null) {
2603 json['value'] = value;
2604 }
2605 return json;
2606 }
2607
2608 /// Returns a string representation of the property value.
2609 ///
2610 /// Subclasses should override this method instead of [toDescription] to
2611 /// customize how property values are converted to strings.
2612 ///
2613 /// Overriding this method ensures that behavior controlling how property
2614 /// values are decorated to generate a nice [toDescription] are consistent
2615 /// across all implementations. Debugging tools may also choose to use
2616 /// [valueToString] directly instead of [toDescription].
2617 ///
2618 /// `parentConfiguration` specifies how the parent is rendered as text art.
2619 /// For example, if the parent places all properties on one line, the value
2620 /// of the property should be displayed without line breaks if possible.
2621 String valueToString({ TextTreeConfiguration? parentConfiguration }) {
2622 final T? v = value;
2623 // DiagnosticableTree values are shown using the shorter toStringShort()
2624 // instead of the longer toString() because the toString() for a
2625 // DiagnosticableTree value is likely too large to be useful.
2626 return v is DiagnosticableTree ? v.toStringShort() : v.toString();
2627 }
2628
2629 @override
2630 String toDescription({ TextTreeConfiguration? parentConfiguration }) {
2631 if (_description != null) {
2632 return _addTooltip(_description);
2633 }
2634
2635 if (exception != null) {
2636 return 'EXCEPTION (${exception.runtimeType})';
2637 }
2638
2639 if (ifNull != null && value == null) {
2640 return _addTooltip(ifNull!);
2641 }
2642
2643 String result = valueToString(parentConfiguration: parentConfiguration);
2644 if (result.isEmpty && ifEmpty != null) {
2645 result = ifEmpty!;
2646 }
2647 return _addTooltip(result);
2648 }
2649
2650 /// If a [tooltip] is specified, add the tooltip it to the end of `text`
2651 /// enclosing it parenthesis to disambiguate the tooltip from the rest of
2652 /// the text.
2653 String _addTooltip(String text) {
2654 return tooltip == null ? text : '$text ($tooltip)';
2655 }
2656
2657 /// Description if the property [value] is null.
2658 final String? ifNull;
2659
2660 /// Description if the property description would otherwise be empty.
2661 final String? ifEmpty;
2662
2663 /// Optional tooltip typically describing the property.
2664 ///
2665 /// Example tooltip: 'physical pixels per logical pixel'
2666 ///
2667 /// If present, the tooltip is added in parenthesis after the raw value when
2668 /// generating the string description.
2669 final String? tooltip;
2670
2671 /// Whether a [value] of null causes the property to have [level]
2672 /// [DiagnosticLevel.warning] warning that the property is missing a [value].
2673 final bool missingIfNull;
2674
2675 /// The type of the property [value].
2676 ///
2677 /// This is determined from the type argument `T` used to instantiate the
2678 /// [DiagnosticsProperty] class. This means that the type is available even if
2679 /// [value] is null, but it also means that the [propertyType] is only as
2680 /// accurate as the type provided when invoking the constructor.
2681 ///
2682 /// Generally, this is only useful for diagnostic tools that should display
2683 /// null values in a manner consistent with the property type. For example, a
2684 /// tool might display a null [Color] value as an empty rectangle instead of
2685 /// the word "null".
2686 Type get propertyType => T;
2687
2688 /// Returns the value of the property either from cache or by invoking a
2689 /// [ComputePropertyValueCallback].
2690 ///
2691 /// If an exception is thrown invoking the [ComputePropertyValueCallback],
2692 /// [value] returns null and the exception thrown can be found via the
2693 /// [exception] property.
2694 ///
2695 /// See also:
2696 ///
2697 /// * [valueToString], which converts the property value to a string.
2698 @override
2699 T? get value {
2700 _maybeCacheValue();
2701 return _value;
2702 }
2703
2704 T? _value;
2705
2706 bool _valueComputed;
2707
2708 Object? _exception;
2709
2710 /// Exception thrown if accessing the property [value] threw an exception.
2711 ///
2712 /// Returns null if computing the property value did not throw an exception.
2713 Object? get exception {
2714 _maybeCacheValue();
2715 return _exception;
2716 }
2717
2718 void _maybeCacheValue() {
2719 if (_valueComputed) {
2720 return;
2721 }
2722
2723 _valueComputed = true;
2724 assert(_computeValue != null);
2725 try {
2726 _value = _computeValue!();
2727 } catch (exception) {
2728 // The error is reported to inspector; rethrowing would destroy the
2729 // debugging experience.
2730 _exception = exception;
2731 _value = null;
2732 }
2733 }
2734
2735 /// The default value of this property, when it has not been set to a specific
2736 /// value.
2737 ///
2738 /// For most [DiagnosticsProperty] classes, if the [value] of the property
2739 /// equals [defaultValue], then the priority [level] of the property is
2740 /// downgraded to [DiagnosticLevel.fine] on the basis that the property value
2741 /// is uninteresting. This is implemented by [isInteresting].
2742 ///
2743 /// The [defaultValue] is [kNoDefaultValue] by default. Otherwise it must be of
2744 /// type `T?`.
2745 final Object? defaultValue;
2746
2747 /// Whether to consider the property's value interesting. When a property is
2748 /// uninteresting, its [level] is downgraded to [DiagnosticLevel.fine]
2749 /// regardless of the value provided as the constructor's `level` argument.
2750 bool get isInteresting => defaultValue == kNoDefaultValue || value != defaultValue;
2751
2752 final DiagnosticLevel _defaultLevel;
2753
2754 /// Priority level of the diagnostic used to control which diagnostics should
2755 /// be shown and filtered.
2756 ///
2757 /// The property level defaults to the value specified by the [level]
2758 /// constructor argument. The level is raised to [DiagnosticLevel.error] if
2759 /// an [exception] was thrown getting the property [value]. The level is
2760 /// raised to [DiagnosticLevel.warning] if the property [value] is null and
2761 /// the property is not allowed to be null due to [missingIfNull]. The
2762 /// priority level is lowered to [DiagnosticLevel.fine] if the property
2763 /// [value] equals [defaultValue].
2764 @override
2765 DiagnosticLevel get level {
2766 if (_defaultLevel == DiagnosticLevel.hidden) {
2767 return _defaultLevel;
2768 }
2769
2770 if (exception != null) {
2771 return DiagnosticLevel.error;
2772 }
2773
2774 if (value == null && missingIfNull) {
2775 return DiagnosticLevel.warning;
2776 }
2777
2778 if (!isInteresting) {
2779 return DiagnosticLevel.fine;
2780 }
2781
2782 return _defaultLevel;
2783 }
2784
2785 final ComputePropertyValueCallback<T>? _computeValue;
2786
2787 @override
2788 List<DiagnosticsNode> getProperties() {
2789 if (expandableValue) {
2790 final T? object = value;
2791 if (object is DiagnosticsNode) {
2792 return object.getProperties();
2793 }
2794 if (object is Diagnosticable) {
2795 return object.toDiagnosticsNode(style: style).getProperties();
2796 }
2797 }
2798 return const <DiagnosticsNode>[];
2799 }
2800
2801 @override
2802 List<DiagnosticsNode> getChildren() {
2803 if (expandableValue) {
2804 final T? object = value;
2805 if (object is DiagnosticsNode) {
2806 return object.getChildren();
2807 }
2808 if (object is Diagnosticable) {
2809 return object.toDiagnosticsNode(style: style).getChildren();
2810 }
2811 }
2812 return const <DiagnosticsNode>[];
2813 }
2814}
2815
2816/// [DiagnosticsNode] that lazily calls the associated [Diagnosticable] [value]
2817/// to implement [getChildren] and [getProperties].
2818class DiagnosticableNode<T extends Diagnosticable> extends DiagnosticsNode {
2819 /// Create a diagnostics describing a [Diagnosticable] value.
2820 DiagnosticableNode({
2821 super.name,
2822 required this.value,
2823 required super.style,
2824 });
2825
2826 @override
2827 final T value;
2828
2829 DiagnosticPropertiesBuilder? _cachedBuilder;
2830
2831 /// Retrieve the [DiagnosticPropertiesBuilder] of current node.
2832 ///
2833 /// It will cache the result to prevent duplicate operation.
2834 DiagnosticPropertiesBuilder? get builder {
2835 if (kReleaseMode) {
2836 return null;
2837 } else {
2838 assert(() {
2839 if (_cachedBuilder == null) {
2840 _cachedBuilder = DiagnosticPropertiesBuilder();
2841 value.debugFillProperties(_cachedBuilder!);
2842 }
2843 return true;
2844 }());
2845 return _cachedBuilder;
2846 }
2847 }
2848
2849 @override
2850 DiagnosticsTreeStyle get style {
2851 return kReleaseMode ? DiagnosticsTreeStyle.none : super.style ?? builder!.defaultDiagnosticsTreeStyle;
2852 }
2853
2854 @override
2855 String? get emptyBodyDescription => (kReleaseMode || kProfileMode) ? '' : builder!.emptyBodyDescription;
2856
2857 @override
2858 List<DiagnosticsNode> getProperties() => (kReleaseMode || kProfileMode) ? const <DiagnosticsNode>[] : builder!.properties;
2859
2860 @override
2861 List<DiagnosticsNode> getChildren() {
2862 return const<DiagnosticsNode>[];
2863 }
2864
2865 @override
2866 String toDescription({ TextTreeConfiguration? parentConfiguration }) {
2867 String result = '';
2868 assert(() {
2869 result = value.toStringShort();
2870 return true;
2871 }());
2872 return result;
2873 }
2874}
2875
2876/// [DiagnosticsNode] for an instance of [DiagnosticableTree].
2877class DiagnosticableTreeNode extends DiagnosticableNode<DiagnosticableTree> {
2878 /// Creates a [DiagnosticableTreeNode].
2879 DiagnosticableTreeNode({
2880 super.name,
2881 required super.value,
2882 required super.style,
2883 });
2884
2885 @override
2886 List<DiagnosticsNode> getChildren() => value.debugDescribeChildren();
2887}
2888
2889/// Returns a 5 character long hexadecimal string generated from
2890/// [Object.hashCode]'s 20 least-significant bits.
2891String shortHash(Object? object) {
2892 return object.hashCode.toUnsigned(20).toRadixString(16).padLeft(5, '0');
2893}
2894
2895/// Returns a summary of the runtime type and hash code of `object`.
2896///
2897/// See also:
2898///
2899/// * [Object.hashCode], a value used when placing an object in a [Map] or
2900/// other similar data structure, and which is also used in debug output to
2901/// distinguish instances of the same class (hash collisions are
2902/// possible, but rare enough that its use in debug output is useful).
2903/// * [Object.runtimeType], the [Type] of an object.
2904String describeIdentity(Object? object) => '${objectRuntimeType(object, '<optimized out>')}#${shortHash(object)}';
2905
2906/// Returns a short description of an enum value.
2907///
2908/// Strips off the enum class name from the `enumEntry.toString()`.
2909///
2910/// For real enums, this is redundant with calling the `name` getter on the enum
2911/// value (see [EnumName.name]), a feature that was added to Dart 2.15.
2912///
2913/// This function can also be used with classes whose `toString` return a value
2914/// in the same form as an enum (the class name, a dot, then the value name).
2915/// For example, it's used with [SemanticsAction], which is written to appear to
2916/// be an enum but is actually a bespoke class so that the index values can be
2917/// set as powers of two instead of as sequential integers.
2918///
2919/// {@tool snippet}
2920///
2921/// ```dart
2922/// enum Day {
2923/// monday, tuesday, wednesday, thursday, friday, saturday, sunday
2924/// }
2925///
2926/// void validateDescribeEnum() {
2927/// assert(Day.monday.toString() == 'Day.monday');
2928/// assert(describeEnum(Day.monday) == 'monday');
2929/// assert(Day.monday.name == 'monday'); // preferred for real enums
2930/// }
2931/// ```
2932/// {@end-tool}
2933@Deprecated(
2934 'Use the `name` getter on enums instead. '
2935 'This feature was deprecated after v3.14.0-2.0.pre.'
2936)
2937String describeEnum(Object enumEntry) {
2938 if (enumEntry is Enum) {
2939 return enumEntry.name;
2940 }
2941 final String description = enumEntry.toString();
2942 final int indexOfDot = description.indexOf('.');
2943 assert(
2944 indexOfDot != -1 && indexOfDot < description.length - 1,
2945 'The provided object "$enumEntry" is not an enum.',
2946 );
2947 return description.substring(indexOfDot + 1);
2948}
2949
2950/// Builder to accumulate properties and configuration used to assemble a
2951/// [DiagnosticsNode] from a [Diagnosticable] object.
2952class DiagnosticPropertiesBuilder {
2953 /// Creates a [DiagnosticPropertiesBuilder] with [properties] initialize to
2954 /// an empty array.
2955 DiagnosticPropertiesBuilder() : properties = <DiagnosticsNode>[];
2956
2957 /// Creates a [DiagnosticPropertiesBuilder] with a given [properties].
2958 DiagnosticPropertiesBuilder.fromProperties(this.properties);
2959
2960 /// Add a property to the list of properties.
2961 void add(DiagnosticsNode property) {
2962 assert(() {
2963 properties.add(property);
2964 return true;
2965 }());
2966 }
2967
2968 /// List of properties accumulated so far.
2969 final List<DiagnosticsNode> properties;
2970
2971 /// Default style to use for the [DiagnosticsNode] if no style is specified.
2972 DiagnosticsTreeStyle defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.sparse;
2973
2974 /// Description to show if the node has no displayed properties or children.
2975 String? emptyBodyDescription;
2976}
2977
2978// Examples can assume:
2979// class ExampleSuperclass with Diagnosticable { late String message; late double stepWidth; late double scale; late double paintExtent; late double hitTestExtent; late double paintExtend; late double maxWidth; late bool primary; late double progress; late int maxLines; late Duration duration; late int depth; Iterable? boxShadow; late DiagnosticsTreeStyle style; late bool hasSize; late Matrix4 transform; Map? handles; late Color color; late bool obscureText; late ImageRepeat repeat; late Size size; late Widget widget; late bool isCurrent; late bool keepAlive; late TextAlign textAlign; }
2980
2981/// A mixin class for providing string and [DiagnosticsNode] debug
2982/// representations describing the properties of an object.
2983///
2984/// The string debug representation is generated from the intermediate
2985/// [DiagnosticsNode] representation. The [DiagnosticsNode] representation is
2986/// also used by debugging tools displaying interactive trees of objects and
2987/// properties.
2988///
2989/// See also:
2990///
2991/// * [debugFillProperties], which lists best practices for specifying the
2992/// properties of a [DiagnosticsNode]. The most common use case is to
2993/// override [debugFillProperties] defining custom properties for a subclass
2994/// of [DiagnosticableTreeMixin] using the existing [DiagnosticsProperty]
2995/// subclasses.
2996/// * [DiagnosticableTree], which extends this class to also describe the
2997/// children of a tree structured object.
2998/// * [DiagnosticableTree.debugDescribeChildren], which lists best practices
2999/// for describing the children of a [DiagnosticsNode]. Typically the base
3000/// class already describes the children of a node properly or a node has
3001/// no children.
3002/// * [DiagnosticsProperty], which should be used to create leaf diagnostic
3003/// nodes without properties or children. There are many
3004/// [DiagnosticsProperty] subclasses to handle common use cases.
3005mixin Diagnosticable {
3006 /// A brief description of this object, usually just the [runtimeType] and the
3007 /// [hashCode].
3008 ///
3009 /// See also:
3010 ///
3011 /// * [toString], for a detailed description of the object.
3012 String toStringShort() => describeIdentity(this);
3013
3014 @override
3015 String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
3016 String? fullString;
3017 assert(() {
3018 fullString = toDiagnosticsNode(style: DiagnosticsTreeStyle.singleLine).toString(minLevel: minLevel);
3019 return true;
3020 }());
3021 return fullString ?? toStringShort();
3022 }
3023
3024 /// Returns a debug representation of the object that is used by debugging
3025 /// tools and by [DiagnosticsNode.toStringDeep].
3026 ///
3027 /// Leave [name] as null if there is not a meaningful description of the
3028 /// relationship between the this node and its parent.
3029 ///
3030 /// Typically the [style] argument is only specified to indicate an atypical
3031 /// relationship between the parent and the node. For example, pass
3032 /// [DiagnosticsTreeStyle.offstage] to indicate that a node is offstage.
3033 DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
3034 return DiagnosticableNode<Diagnosticable>(
3035 name: name,
3036 value: this,
3037 style: style,
3038 );
3039 }
3040
3041 /// Add additional properties associated with the node.
3042 ///
3043 /// {@youtube 560 315 https://www.youtube.com/watch?v=DnC7eT-vh1k}
3044 ///
3045 /// Use the most specific [DiagnosticsProperty] existing subclass to describe
3046 /// each property instead of the [DiagnosticsProperty] base class. There are
3047 /// only a small number of [DiagnosticsProperty] subclasses each covering a
3048 /// common use case. Consider what values a property is relevant for users
3049 /// debugging as users debugging large trees are overloaded with information.
3050 /// Common named parameters in [DiagnosticsNode] subclasses help filter when
3051 /// and how properties are displayed.
3052 ///
3053 /// `defaultValue`, `showName`, `showSeparator`, and `level` keep string
3054 /// representations of diagnostics terse and hide properties when they are not
3055 /// very useful.
3056 ///
3057 /// * Use `defaultValue` any time the default value of a property is
3058 /// uninteresting. For example, specify a default value of null any time
3059 /// a property being null does not indicate an error.
3060 /// * Avoid specifying the `level` parameter unless the result you want
3061 /// cannot be achieved by using the `defaultValue` parameter or using
3062 /// the [ObjectFlagProperty] class to conditionally display the property
3063 /// as a flag.
3064 /// * Specify `showName` and `showSeparator` in rare cases where the string
3065 /// output would look clumsy if they were not set.
3066 /// ```dart
3067 /// DiagnosticsProperty<Object>('child(3, 4)', null, ifNull: 'is null', showSeparator: false).toString()
3068 /// ```
3069 /// Shows using `showSeparator` to get output `child(3, 4) is null` which
3070 /// is more polished than `child(3, 4): is null`.
3071 /// ```dart
3072 /// DiagnosticsProperty<IconData>('icon', icon, ifNull: '<empty>', showName: false).toString()
3073 /// ```
3074 /// Shows using `showName` to omit the property name as in this context the
3075 /// property name does not add useful information.
3076 ///
3077 /// `ifNull`, `ifEmpty`, `unit`, and `tooltip` make property
3078 /// descriptions clearer. The examples in the code sample below illustrate
3079 /// good uses of all of these parameters.
3080 ///
3081 /// ## DiagnosticsProperty subclasses for primitive types
3082 ///
3083 /// * [StringProperty], which supports automatically enclosing a [String]
3084 /// value in quotes.
3085 /// * [DoubleProperty], which supports specifying a unit of measurement for
3086 /// a [double] value.
3087 /// * [PercentProperty], which clamps a [double] to between 0 and 1 and
3088 /// formats it as a percentage.
3089 /// * [IntProperty], which supports specifying a unit of measurement for an
3090 /// [int] value.
3091 /// * [FlagProperty], which formats a [bool] value as one or more flags.
3092 /// Depending on the use case it is better to format a bool as
3093 /// `DiagnosticsProperty<bool>` instead of using [FlagProperty] as the
3094 /// output is more verbose but unambiguous.
3095 ///
3096 /// ## Other important [DiagnosticsProperty] variants
3097 ///
3098 /// * [EnumProperty], which provides terse descriptions of enum values
3099 /// working around limitations of the `toString` implementation for Dart
3100 /// enum types.
3101 /// * [IterableProperty], which handles iterable values with display
3102 /// customizable depending on the [DiagnosticsTreeStyle] used.
3103 /// * [ObjectFlagProperty], which provides terse descriptions of whether a
3104 /// property value is present or not. For example, whether an `onClick`
3105 /// callback is specified or an animation is in progress.
3106 /// * [ColorProperty], which must be used if the property value is
3107 /// a [Color] or one of its subclasses.
3108 /// * [IconDataProperty], which must be used if the property value
3109 /// is of type [IconData].
3110 ///
3111 /// If none of these subclasses apply, use the [DiagnosticsProperty]
3112 /// constructor or in rare cases create your own [DiagnosticsProperty]
3113 /// subclass as in the case for [TransformProperty] which handles [Matrix4]
3114 /// that represent transforms. Generally any property value with a good
3115 /// `toString` method implementation works fine using [DiagnosticsProperty]
3116 /// directly.
3117 ///
3118 /// {@tool snippet}
3119 ///
3120 /// This example shows best practices for implementing [debugFillProperties]
3121 /// illustrating use of all common [DiagnosticsProperty] subclasses and all
3122 /// common [DiagnosticsProperty] parameters.
3123 ///
3124 /// ```dart
3125 /// class ExampleObject extends ExampleSuperclass {
3126 ///
3127 /// // ...various members and properties...
3128 ///
3129 /// @override
3130 /// void debugFillProperties(DiagnosticPropertiesBuilder properties) {
3131 /// // Always add properties from the base class first.
3132 /// super.debugFillProperties(properties);
3133 ///
3134 /// // Omit the property name 'message' when displaying this String property
3135 /// // as it would just add visual noise.
3136 /// properties.add(StringProperty('message', message, showName: false));
3137 ///
3138 /// properties.add(DoubleProperty('stepWidth', stepWidth));
3139 ///
3140 /// // A scale of 1.0 does nothing so should be hidden.
3141 /// properties.add(DoubleProperty('scale', scale, defaultValue: 1.0));
3142 ///
3143 /// // If the hitTestExtent matches the paintExtent, it is just set to its
3144 /// // default value so is not relevant.
3145 /// properties.add(DoubleProperty('hitTestExtent', hitTestExtent, defaultValue: paintExtent));
3146 ///
3147 /// // maxWidth of double.infinity indicates the width is unconstrained and
3148 /// // so maxWidth has no impact.
3149 /// properties.add(DoubleProperty('maxWidth', maxWidth, defaultValue: double.infinity));
3150 ///
3151 /// // Progress is a value between 0 and 1 or null. Showing it as a
3152 /// // percentage makes the meaning clear enough that the name can be
3153 /// // hidden.
3154 /// properties.add(PercentProperty(
3155 /// 'progress',
3156 /// progress,
3157 /// showName: false,
3158 /// ifNull: '<indeterminate>',
3159 /// ));
3160 ///
3161 /// // Most text fields have maxLines set to 1.
3162 /// properties.add(IntProperty('maxLines', maxLines, defaultValue: 1));
3163 ///
3164 /// // Specify the unit as otherwise it would be unclear that time is in
3165 /// // milliseconds.
3166 /// properties.add(IntProperty('duration', duration.inMilliseconds, unit: 'ms'));
3167 ///
3168 /// // Tooltip is used instead of unit for this case as a unit should be a
3169 /// // terse description appropriate to display directly after a number
3170 /// // without a space.
3171 /// properties.add(DoubleProperty(
3172 /// 'device pixel ratio',
3173 /// devicePixelRatio,
3174 /// tooltip: 'physical pixels per logical pixel',
3175 /// ));
3176 ///
3177 /// // Displaying the depth value would be distracting. Instead only display
3178 /// // if the depth value is missing.
3179 /// properties.add(ObjectFlagProperty<int>('depth', depth, ifNull: 'no depth'));
3180 ///
3181 /// // bool flag that is only shown when the value is true.
3182 /// properties.add(FlagProperty('using primary controller', value: primary));
3183 ///
3184 /// properties.add(FlagProperty(
3185 /// 'isCurrent',
3186 /// value: isCurrent,
3187 /// ifTrue: 'active',
3188 /// ifFalse: 'inactive',
3189 /// ));
3190 ///
3191 /// properties.add(DiagnosticsProperty<bool>('keepAlive', keepAlive));
3192 ///
3193 /// // FlagProperty could have also been used in this case.
3194 /// // This option results in the text "obscureText: true" instead
3195 /// // of "obscureText" which is a bit more verbose but a bit clearer.
3196 /// properties.add(DiagnosticsProperty<bool>('obscureText', obscureText, defaultValue: false));
3197 ///
3198 /// properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
3199 /// properties.add(EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
3200 ///
3201 /// // Warn users when the widget is missing but do not show the value.
3202 /// properties.add(ObjectFlagProperty<Widget>('widget', widget, ifNull: 'no widget'));
3203 ///
3204 /// properties.add(IterableProperty<BoxShadow>(
3205 /// 'boxShadow',
3206 /// boxShadow,
3207 /// defaultValue: null,
3208 /// style: style,
3209 /// ));
3210 ///
3211 /// // Getting the value of size throws an exception unless hasSize is true.
3212 /// properties.add(DiagnosticsProperty<Size>.lazy(
3213 /// 'size',
3214 /// () => size,
3215 /// description: '${ hasSize ? size : "MISSING" }',
3216 /// ));
3217 ///
3218 /// // If the `toString` method for the property value does not provide a
3219 /// // good terse description, write a DiagnosticsProperty subclass as in
3220 /// // the case of TransformProperty which displays a nice debugging view
3221 /// // of a Matrix4 that represents a transform.
3222 /// properties.add(TransformProperty('transform', transform));
3223 ///
3224 /// // If the value class has a good `toString` method, use
3225 /// // DiagnosticsProperty. Specifying the value type ensures
3226 /// // that debugging tools always know the type of the field and so can
3227 /// // provide the right UI affordances. For example, in this case even
3228 /// // if color is null, a debugging tool still knows the value is a Color
3229 /// // and can display relevant color related UI.
3230 /// properties.add(DiagnosticsProperty<Color>('color', color));
3231 ///
3232 /// // Use a custom description to generate a more terse summary than the
3233 /// // `toString` method on the map class.
3234 /// properties.add(DiagnosticsProperty<Map<Listenable, VoidCallback>>(
3235 /// 'handles',
3236 /// handles,
3237 /// description: handles != null
3238 /// ? '${handles!.length} active client${ handles!.length == 1 ? "" : "s" }'
3239 /// : null,
3240 /// ifNull: 'no notifications ever received',
3241 /// showName: false,
3242 /// ));
3243 /// }
3244 /// }
3245 /// ```
3246 /// {@end-tool}
3247 ///
3248 /// Used by [toDiagnosticsNode] and [toString].
3249 ///
3250 /// Do not add values that have lifetime shorter than the object.
3251 @protected
3252 @mustCallSuper
3253 void debugFillProperties(DiagnosticPropertiesBuilder properties) { }
3254}
3255
3256/// A base class for providing string and [DiagnosticsNode] debug
3257/// representations describing the properties and children of an object.
3258///
3259/// The string debug representation is generated from the intermediate
3260/// [DiagnosticsNode] representation. The [DiagnosticsNode] representation is
3261/// also used by debugging tools displaying interactive trees of objects and
3262/// properties.
3263///
3264/// See also:
3265///
3266/// * [DiagnosticableTreeMixin], a mixin that implements this class.
3267/// * [Diagnosticable], which should be used instead of this class to
3268/// provide diagnostics for objects without children.
3269abstract class DiagnosticableTree with Diagnosticable {
3270 /// Abstract const constructor. This constructor enables subclasses to provide
3271 /// const constructors so that they can be used in const expressions.
3272 const DiagnosticableTree();
3273
3274 /// Returns a one-line detailed description of the object.
3275 ///
3276 /// This description is often somewhat long. This includes the same
3277 /// information given by [toStringDeep], but does not recurse to any children.
3278 ///
3279 /// `joiner` specifies the string which is place between each part obtained
3280 /// from [debugFillProperties]. Passing a string such as `'\n '` will result
3281 /// in a multiline string that indents the properties of the object below its
3282 /// name (as per [toString]).
3283 ///
3284 /// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
3285 /// in the output.
3286 ///
3287 /// See also:
3288 ///
3289 /// * [toString], for a brief description of the object.
3290 /// * [toStringDeep], for a description of the subtree rooted at this object.
3291 String toStringShallow({
3292 String joiner = ', ',
3293 DiagnosticLevel minLevel = DiagnosticLevel.debug,
3294 }) {
3295 String? shallowString;
3296 assert(() {
3297 final StringBuffer result = StringBuffer();
3298 result.write(toString());
3299 result.write(joiner);
3300 final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
3301 debugFillProperties(builder);
3302 result.write(
3303 builder.properties.where((DiagnosticsNode n) => !n.isFiltered(minLevel))
3304 .join(joiner),
3305 );
3306 shallowString = result.toString();
3307 return true;
3308 }());
3309 return shallowString ?? toString();
3310 }
3311
3312 /// Returns a string representation of this node and its descendants.
3313 ///
3314 /// `prefixLineOne` will be added to the front of the first line of the
3315 /// output. `prefixOtherLines` will be added to the front of each other line.
3316 /// If `prefixOtherLines` is null, the `prefixLineOne` is used for every line.
3317 /// By default, there is no prefix.
3318 ///
3319 /// `minLevel` specifies the minimum [DiagnosticLevel] for properties included
3320 /// in the output.
3321 ///
3322 /// `wrapWidth` specifies the column number where word wrapping will be
3323 /// applied.
3324 ///
3325 /// The [toStringDeep] method takes other arguments, but those are intended
3326 /// for internal use when recursing to the descendants, and so can be ignored.
3327 ///
3328 /// See also:
3329 ///
3330 /// * [toString], for a brief description of the object but not its children.
3331 /// * [toStringShallow], for a detailed description of the object but not its
3332 /// children.
3333 String toStringDeep({
3334 String prefixLineOne = '',
3335 String? prefixOtherLines,
3336 DiagnosticLevel minLevel = DiagnosticLevel.debug,
3337 int wrapWidth = 65,
3338 }) {
3339 return toDiagnosticsNode().toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel, wrapWidth: wrapWidth);
3340 }
3341
3342 @override
3343 String toStringShort() => describeIdentity(this);
3344
3345 @override
3346 DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
3347 return DiagnosticableTreeNode(
3348 name: name,
3349 value: this,
3350 style: style,
3351 );
3352 }
3353
3354 /// Returns a list of [DiagnosticsNode] objects describing this node's
3355 /// children.
3356 ///
3357 /// Children that are offstage should be added with `style` set to
3358 /// [DiagnosticsTreeStyle.offstage] to indicate that they are offstage.
3359 ///
3360 /// The list must not contain any null entries. If there are explicit null
3361 /// children to report, consider [DiagnosticsNode.message] or
3362 /// [DiagnosticsProperty<Object>] as possible [DiagnosticsNode] objects to
3363 /// provide.
3364 ///
3365 /// Used by [toStringDeep], [toDiagnosticsNode] and [toStringShallow].
3366 ///
3367 /// See also:
3368 ///
3369 /// * [RenderTable.debugDescribeChildren], which provides high quality custom
3370 /// descriptions for its child nodes.
3371 @protected
3372 List<DiagnosticsNode> debugDescribeChildren() => const <DiagnosticsNode>[];
3373}
3374
3375/// A mixin that helps dump string and [DiagnosticsNode] representations of trees.
3376///
3377/// This mixin is identical to class [DiagnosticableTree].
3378mixin DiagnosticableTreeMixin implements DiagnosticableTree {
3379 @override
3380 String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
3381 return toDiagnosticsNode(style: DiagnosticsTreeStyle.singleLine).toString(minLevel: minLevel);
3382 }
3383
3384 @override
3385 String toStringShallow({
3386 String joiner = ', ',
3387 DiagnosticLevel minLevel = DiagnosticLevel.debug,
3388 }) {
3389 String? shallowString;
3390 assert(() {
3391 final StringBuffer result = StringBuffer();
3392 result.write(toStringShort());
3393 result.write(joiner);
3394 final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
3395 debugFillProperties(builder);
3396 result.write(
3397 builder.properties.where((DiagnosticsNode n) => !n.isFiltered(minLevel))
3398 .join(joiner),
3399 );
3400 shallowString = result.toString();
3401 return true;
3402 }());
3403 return shallowString ?? toString();
3404 }
3405
3406 @override
3407 String toStringDeep({
3408 String prefixLineOne = '',
3409 String? prefixOtherLines,
3410 DiagnosticLevel minLevel = DiagnosticLevel.debug,
3411 int wrapWidth = 65,
3412 }) {
3413 return toDiagnosticsNode().toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel, wrapWidth: wrapWidth);
3414 }
3415
3416 @override
3417 String toStringShort() => describeIdentity(this);
3418
3419 @override
3420 DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
3421 return DiagnosticableTreeNode(
3422 name: name,
3423 value: this,
3424 style: style,
3425 );
3426 }
3427
3428 @override
3429 List<DiagnosticsNode> debugDescribeChildren() => const <DiagnosticsNode>[];
3430
3431 @override
3432 void debugFillProperties(DiagnosticPropertiesBuilder properties) { }
3433}
3434
3435
3436/// [DiagnosticsNode] that exists mainly to provide a container for other
3437/// diagnostics that typically lacks a meaningful value of its own.
3438///
3439/// This class is typically used for displaying complex nested error messages.
3440class DiagnosticsBlock extends DiagnosticsNode {
3441 /// Creates a diagnostic with properties specified by [properties] and
3442 /// children specified by [children].
3443 DiagnosticsBlock({
3444 super.name,
3445 DiagnosticsTreeStyle super.style = DiagnosticsTreeStyle.whitespace,
3446 bool showName = true,
3447 super.showSeparator,
3448 super.linePrefix,
3449 this.value,
3450 String? description,
3451 this.level = DiagnosticLevel.info,
3452 this.allowTruncate = false,
3453 List<DiagnosticsNode> children = const<DiagnosticsNode>[],
3454 List<DiagnosticsNode> properties = const <DiagnosticsNode>[],
3455 }) : _description = description ?? '',
3456 _children = children,
3457 _properties = properties,
3458 super(
3459 showName: showName && name != null,
3460 );
3461
3462 final List<DiagnosticsNode> _children;
3463 final List<DiagnosticsNode> _properties;
3464
3465 @override
3466 final DiagnosticLevel level;
3467
3468 final String _description;
3469
3470 @override
3471 final Object? value;
3472
3473 @override
3474 final bool allowTruncate;
3475
3476 @override
3477 List<DiagnosticsNode> getChildren() => _children;
3478
3479 @override
3480 List<DiagnosticsNode> getProperties() => _properties;
3481
3482 @override
3483 String toDescription({TextTreeConfiguration? parentConfiguration}) => _description;
3484}
3485
3486/// A delegate that configures how a hierarchy of [DiagnosticsNode]s should be
3487/// serialized.
3488///
3489/// Implement this class in a subclass to fully configure how [DiagnosticsNode]s
3490/// get serialized.
3491abstract class DiagnosticsSerializationDelegate {
3492 /// Creates a simple [DiagnosticsSerializationDelegate] that controls the
3493 /// [subtreeDepth] and whether to [includeProperties].
3494 ///
3495 /// For additional configuration options, extend
3496 /// [DiagnosticsSerializationDelegate] and provide custom implementations
3497 /// for the methods of this class.
3498 const factory DiagnosticsSerializationDelegate({
3499 int subtreeDepth,
3500 bool includeProperties,
3501 }) = _DefaultDiagnosticsSerializationDelegate;
3502
3503 /// Returns a serializable map of additional information that will be included
3504 /// in the serialization of the given [DiagnosticsNode].
3505 ///
3506 /// This method is called for every [DiagnosticsNode] that's included in
3507 /// the serialization.
3508 Map<String, Object?> additionalNodeProperties(DiagnosticsNode node);
3509
3510 /// Filters the list of [DiagnosticsNode]s that will be included as children
3511 /// for the given `owner` node.
3512 ///
3513 /// The callback may return a subset of the children in the provided list
3514 /// or replace the entire list with new child nodes.
3515 ///
3516 /// See also:
3517 ///
3518 /// * [subtreeDepth], which controls how many levels of children will be
3519 /// included in the serialization.
3520 List<DiagnosticsNode> filterChildren(List<DiagnosticsNode> nodes, DiagnosticsNode owner);
3521
3522 /// Filters the list of [DiagnosticsNode]s that will be included as properties
3523 /// for the given `owner` node.
3524 ///
3525 /// The callback may return a subset of the properties in the provided list
3526 /// or replace the entire list with new property nodes.
3527 ///
3528 /// By default, `nodes` is returned as-is.
3529 ///
3530 /// See also:
3531 ///
3532 /// * [includeProperties], which controls whether properties will be included
3533 /// at all.
3534 List<DiagnosticsNode> filterProperties(List<DiagnosticsNode> nodes, DiagnosticsNode owner);
3535
3536 /// Truncates the given list of [DiagnosticsNode] that will be added to the
3537 /// serialization as children or properties of the `owner` node.
3538 ///
3539 /// The method must return a subset of the provided nodes and may
3540 /// not replace any nodes. While [filterProperties] and [filterChildren]
3541 /// completely hide a node from the serialization, truncating a node will
3542 /// leave a hint in the serialization that there were additional nodes in the
3543 /// result that are not included in the current serialization.
3544 ///
3545 /// By default, `nodes` is returned as-is.
3546 List<DiagnosticsNode> truncateNodesList(List<DiagnosticsNode> nodes, DiagnosticsNode? owner);
3547
3548 /// Returns the [DiagnosticsSerializationDelegate] to be used
3549 /// for adding the provided [DiagnosticsNode] to the serialization.
3550 ///
3551 /// By default, this will return a copy of this delegate, which has the
3552 /// [subtreeDepth] reduced by one.
3553 ///
3554 /// This is called for nodes that will be added to the serialization as
3555 /// property or child of another node. It may return the same delegate if no
3556 /// changes to it are necessary.
3557 DiagnosticsSerializationDelegate delegateForNode(DiagnosticsNode node);
3558
3559 /// Controls how many levels of children will be included in the serialized
3560 /// hierarchy of [DiagnosticsNode]s.
3561 ///
3562 /// Defaults to zero.
3563 ///
3564 /// See also:
3565 ///
3566 /// * [filterChildren], which provides a way to filter the children that
3567 /// will be included.
3568 int get subtreeDepth;
3569
3570 /// Whether to include the properties of a [DiagnosticsNode] in the
3571 /// serialization.
3572 ///
3573 /// Defaults to false.
3574 ///
3575 /// See also:
3576 ///
3577 /// * [filterProperties], which provides a way to filter the properties that
3578 /// will be included.
3579 bool get includeProperties;
3580
3581 /// Whether properties that have a [Diagnosticable] as value should be
3582 /// expanded.
3583 bool get expandPropertyValues;
3584
3585 /// Creates a copy of this [DiagnosticsSerializationDelegate] with the
3586 /// provided values.
3587 DiagnosticsSerializationDelegate copyWith({
3588 int subtreeDepth,
3589 bool includeProperties,
3590 });
3591}
3592
3593class _DefaultDiagnosticsSerializationDelegate implements DiagnosticsSerializationDelegate {
3594 const _DefaultDiagnosticsSerializationDelegate({
3595 this.includeProperties = false,
3596 this.subtreeDepth = 0,
3597 });
3598
3599 @override
3600 Map<String, Object?> additionalNodeProperties(DiagnosticsNode node) {
3601 return const <String, Object?>{};
3602 }
3603
3604 @override
3605 DiagnosticsSerializationDelegate delegateForNode(DiagnosticsNode node) {
3606 return subtreeDepth > 0 ? copyWith(subtreeDepth: subtreeDepth - 1) : this;
3607 }
3608
3609 @override
3610 bool get expandPropertyValues => false;
3611
3612 @override
3613 List<DiagnosticsNode> filterChildren(List<DiagnosticsNode> nodes, DiagnosticsNode owner) {
3614 return nodes;
3615 }
3616
3617 @override
3618 List<DiagnosticsNode> filterProperties(List<DiagnosticsNode> nodes, DiagnosticsNode owner) {
3619 return nodes;
3620 }
3621
3622 @override
3623 final bool includeProperties;
3624
3625 @override
3626 final int subtreeDepth;
3627
3628 @override
3629 List<DiagnosticsNode> truncateNodesList(List<DiagnosticsNode> nodes, DiagnosticsNode? owner) {
3630 return nodes;
3631 }
3632
3633 @override
3634 DiagnosticsSerializationDelegate copyWith({int? subtreeDepth, bool? includeProperties}) {
3635 return _DefaultDiagnosticsSerializationDelegate(
3636 subtreeDepth: subtreeDepth ?? this.subtreeDepth,
3637 includeProperties: includeProperties ?? this.includeProperties,
3638 );
3639 }
3640}
3641