@@ -2,6 +2,121 @@ const chalk = require('chalk');
2
2
3
3
const hasOwn = Object . hasOwnProperty ;
4
4
5
+ /**
6
+ * Format a given value into YAML.
7
+ *
8
+ * YAML is a superset of JSON that supports all the same data
9
+ * types and syntax, and more. As such, it is always possible
10
+ * to fallback to JSON.stringfify, but we generally avoid
11
+ * that to make output easier to read for humans.
12
+ *
13
+ * Supported data types:
14
+ *
15
+ * - null
16
+ * - boolean
17
+ * - number
18
+ * - string
19
+ * - array
20
+ * - object
21
+ *
22
+ * Anything else (including NaN, Infinity, and undefined)
23
+ * must be described in strings, for display purposes.
24
+ *
25
+ * Note that quotes are optional in YAML strings if the
26
+ * strings are "simple", and as such we generally prefer
27
+ * that for improved readability. We output strings in
28
+ * one of three ways:
29
+ *
30
+ * - bare unquoted text, for simple one-line strings.
31
+ * - JSON (quoted text), for complex one-line strings.
32
+ * - YAML Block, for complex multi-line strings.
33
+ */
34
+ function prettyYamlValue ( value , indent = 4 ) {
35
+ if ( value === undefined ) {
36
+ // Not supported in JSON/YAML, turn into string
37
+ // and let the below output it as bare string.
38
+ value = String ( value ) ;
39
+ }
40
+
41
+ if ( typeof value === 'number' && ! Number . isFinite ( value ) ) {
42
+ // Turn NaN and Infinity into simple strings.
43
+ // Paranoia: Don't return directly just in case there's
44
+ // a way to add special characters here.
45
+ value = String ( value ) ;
46
+ }
47
+
48
+ if ( typeof value === 'number' ) {
49
+ // Simple numbers
50
+ return JSON . stringify ( value ) ;
51
+ }
52
+
53
+ if ( typeof value === 'string' ) {
54
+ // If any of these match, then we can't output it
55
+ // as bare unquoted text, because that would either
56
+ // cause data loss or invalid YAML syntax.
57
+ //
58
+ // - Quotes, escapes, line breaks, or JSON-like stuff.
59
+ const rSpecialJson = / [ ' " \\ / [ { } \] \r \n ] / ;
60
+ // - Characters that are special at the start of a YAML value
61
+ const rSpecialYaml = / [ - ? : , [ \] { } # & * ! | = > ' " % @ ` ] / ;
62
+ // - Leading or trailing whitespace.
63
+ const rUntrimmed = / ( ^ \s | \s $ ) / ;
64
+ // - Ambiguous as YAML number, e.g. '2', '-1.2', '.2', or '2_000'
65
+ const rNumerical = / ^ [ \d . _ - ] + $ / ;
66
+ // - Ambiguous as YAML bool.
67
+ // Use case-insensitive match, although technically only
68
+ // fully-lower, fully-upper, or uppercase-first would be ambiguous.
69
+ // e.g. true/True/TRUE, but not tRUe.
70
+ const rBool = / ^ ( t r u e | f a l s e | y | n | y e s | n o | o n | o f f ) $ / i;
71
+
72
+ // Is this a complex string?
73
+ if (
74
+ value === '' ||
75
+ rSpecialJson . test ( value ) ||
76
+ rSpecialYaml . test ( value [ 0 ] ) ||
77
+ rUntrimmed . test ( value ) ||
78
+ rNumerical . test ( value ) ||
79
+ rBool . test ( value )
80
+ ) {
81
+ if ( ! / \n / . test ( value ) ) {
82
+ // Complex one-line string, use JSON (quoted string)
83
+ return JSON . stringify ( value ) ;
84
+ }
85
+
86
+ // See also <https://yaml-multiline.info/>
87
+ const prefix = ' ' . repeat ( indent ) ;
88
+
89
+ const trailingLinebreakMatch = value . match ( / \n + $ / ) ;
90
+ const trailingLinebreaks = trailingLinebreakMatch ? trailingLinebreakMatch [ 0 ] . length : 0 ;
91
+
92
+ if ( trailingLinebreaks === 1 ) {
93
+ // Use the most straight-forward "Block" string in YAML
94
+ // without any "Chomping" indicators.
95
+ const lines = value
96
+ // Ignore the last new line, since we'll get that one for free
97
+ // with the straight-forward Block syntax.
98
+ . replace ( / \n $ / , '' )
99
+ . split ( '\n' )
100
+ . map ( line => prefix + line ) ;
101
+ return '|\n' + lines . join ( '\n' ) ;
102
+ } else {
103
+ // This has either no trailing new lines, or more than 1.
104
+ // Use |+ so that YAML parsers will preserve it exactly.
105
+ const lines = value
106
+ . split ( '\n' )
107
+ . map ( line => prefix + line ) ;
108
+ return '|+\n' + lines . join ( '\n' ) ;
109
+ }
110
+ } else {
111
+ // Simple string, use bare unquoted text
112
+ return value ;
113
+ }
114
+ }
115
+
116
+ // Handle null, boolean, array, and object
117
+ return JSON . stringify ( value , null , 2 ) ;
118
+ }
119
+
5
120
module . exports = class TapReporter {
6
121
constructor ( runner ) {
7
122
this . testCount = 0 ;
@@ -44,24 +159,25 @@ module.exports = class TapReporter {
44
159
}
45
160
46
161
logError ( error , severity ) {
47
- console . log ( ' ---' ) ;
48
- console . log ( ` message: " ${ ( error . message || 'failed' ) . replace ( / " / g , '\\"' ) } "` ) ;
49
- console . log ( ` severity: ${ severity || 'failed' } ` ) ;
162
+ let out = ' ---' ;
163
+ out += `\n message: ${ prettyYamlValue ( error . message || 'failed' ) } ` ;
164
+ out += `\n severity: ${ prettyYamlValue ( severity || 'failed' ) } ` ;
50
165
51
166
if ( hasOwn . call ( error , 'actual' ) ) {
52
- const actualStr = error . actual !== undefined ? ( '"' + JSON . stringify ( error . actual , null , 2 ) . replace ( / " / g, '\\"' ) . replace ( / \n / g, '\\n' ) + '"' ) : 'undefined' ;
53
- console . log ( ` actual : ${ actualStr } ` ) ;
167
+ out += `\n actual : ${ prettyYamlValue ( error . actual ) } ` ;
54
168
}
55
169
56
170
if ( hasOwn . call ( error , 'expected' ) ) {
57
- const expectedStr = error . expected !== undefined ? ( '"' + JSON . stringify ( error . expected , null , 2 ) . replace ( / " / g, '\\"' ) . replace ( / \n / g, '\\n' ) + '"' ) : 'undefined' ;
58
- console . log ( ` expected: ${ expectedStr } ` ) ;
171
+ out += `\n expected: ${ prettyYamlValue ( error . expected ) } ` ;
59
172
}
60
173
61
174
if ( error . stack ) {
62
- console . log ( ` stack: "${ error . stack . replace ( / " / g, '\\"' ) . replace ( / \n / g, '\\n' ) } "` ) ;
175
+ // Since stacks aren't user generated, take a bit of liberty by
176
+ // adding a trailing new line to allow a straight-forward YAML Blocks.
177
+ out += `\n stack: ${ prettyYamlValue ( error . stack + '\n' ) } ` ;
63
178
}
64
179
65
- console . log ( ' ...' ) ;
180
+ out += '\n ...' ;
181
+ console . log ( out ) ;
66
182
}
67
183
} ;
0 commit comments