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