UNPKG

6.88 kBJavaScriptView Raw
1const isArray = Array.isArray;
2const isObject = (value) => value.constructor === Object;
3const slice = Array.prototype.slice;
4const sameValue = Object.is;
5const isNative = (value) => (value + '').includes('[native code]');
6const HIDDEN_CONTENT = '{…}';
7
8const buildPrefix = (indent) => {
9 let output = '\n';
10
11 for (let i = 0; i < indent; i++) {
12 output += ' ';
13 }
14
15 return output;
16};
17
18const numberString = (value) => {
19 if (!isFinite(value)) {
20 return value + '';
21 }
22
23 if (sameValue(value, -0)) {
24 return '-0';
25 }
26
27 return value.toLocaleString('default', {
28 maximumFractionDigits: 20
29 });
30};
31
32const bigIntString = (value) => {
33 if (sameValue(value, -0n)) {
34 return '-0n';
35 }
36
37 return Number(value)
38 .toLocaleString('default', {
39 maximumFractionDigits: 20
40 }) + 'n';
41};
42
43const symbolString = (value) => {
44 if (value.description !== undefined) {
45 return 'Symbol(' + value.description + ')';
46 }
47
48 return value.toString();
49};
50
51const functionString = (value) => {
52 const string = value + '';
53
54 if (value.name !== '' && isNative(string)) {
55 return value.name;
56 }
57
58 return string
59 .slice(0, string.indexOf('{'))
60 .replace('function', 'ƒ')
61 .replace('ƒ(', 'ƒ (') + HIDDEN_CONTENT;
62};
63
64const nonNativeInstanceString = (value) => {
65 if (value.toString) {
66 const string = value.toString();
67
68 if (string !== '[object Object]') {
69 return string;
70 }
71 }
72
73 return `[object ${value.constructor.name}]`;
74};
75
76const addValue = (output, value, key, indentString, type, settings, isAnArray) => {
77 if (output.length !== 1) {
78 output += settings.separator;
79 }
80 else if (!isAnArray && !settings.beautify) {
81 output += ' ';
82 }
83
84 if (isAnArray || type === 'set') {
85 return output + (
86 (output.length === 1 || value.charAt(0) !== '{') ?
87 indentString :
88 settings.space
89 ) + value;
90 }
91
92 return output + indentString + '"' + key + '": ' + value;
93};
94
95const stringify = (value, indent, settings, type) => {
96 const indentString = settings.beautify ? buildPrefix(indent) : '';
97 const isAnArray = type === 'array' || type === 'typedarray' || type === 'arraylike';
98 let output = isAnArray ? '[' : '{';
99
100 if (isAnArray || type === 'object') {
101 for (const key in value) {
102 output = addValue(
103 output,
104 processValue(value[key], indent, settings),
105 key,
106 indentString,
107 type,
108 settings,
109 isAnArray
110 );
111 }
112 }
113 else {
114 for (const key of value) {
115 output = addValue(
116 output,
117 processValue(type === 'map' ? key[1] : key, indent, settings),
118 key[0],
119 indentString,
120 type,
121 settings,
122 isAnArray
123 );
124 }
125 }
126
127 if (output.length !== 1) {
128 if (settings.beautify) {
129 output += buildPrefix(indent - 1);
130 }
131 else if (!isAnArray) {
132 output += ' ';
133 }
134 }
135
136 return output + (isAnArray ? ']' : '}');
137};
138
139const getType = (value) => {
140 const type = typeof value;
141
142 if (type !== 'object') {
143 return type;
144 }
145
146 if (value !== null && value !== undefined) {
147 if (value instanceof String) {
148 return 'string';
149 }
150
151 if (isArray(value)) {
152 return 'array';
153 }
154
155 if (isObject(value) || value.toJSON && !(value instanceof Date)) {
156 if (value.length !== undefined) {
157 return 'arraylike';
158 }
159
160 return 'object';
161 }
162
163 if (value instanceof Set) {
164 return 'set';
165 }
166
167 if (value instanceof Map) {
168 return 'map';
169 }
170
171 if (value instanceof WeakSet) {
172 return 'weakset';
173 }
174
175 if (value instanceof WeakMap) {
176 return 'weakmap';
177 }
178
179 if (
180 value instanceof Int8Array ||
181 value instanceof Uint8Array ||
182 value instanceof Uint8ClampedArray ||
183 value instanceof Int16Array ||
184 value instanceof Uint16Array ||
185 value instanceof Int32Array ||
186 value instanceof Uint32Array ||
187 value instanceof BigInt64Array ||
188 value instanceof BigUint64Array ||
189 value instanceof Float32Array ||
190 value instanceof Float64Array
191 ) {
192 return 'typedarray';
193 }
194
195 if (value.constructor !== undefined && !isNative(value.constructor)) {
196 return 'constructor';
197 }
198 }
199};
200
201const processValue = (value, indent, settings) => {
202 const type = getType(value);
203
204 switch (type) {
205 case 'string':
206 return `"${value}"`;
207 case 'number':
208 return numberString(value);
209 case 'bigint':
210 return bigIntString(value);
211 case 'symbol':
212 return symbolString(value);
213 case 'function':
214 return functionString(value);
215 case 'array':
216 case 'object':
217 case 'arraylike':
218 return stringify(value, indent + 1, settings, type);
219 case 'typedarray':
220 case 'set':
221 case 'map':
222 return value.constructor.name + ' ' + stringify(value, indent + 1, settings, type);
223 case 'weakset':
224 case 'weakmap':
225 return value.constructor.name + ' ' + HIDDEN_CONTENT;
226 case 'constructor':
227 return nonNativeInstanceString(value);
228 default:
229 return value + '';
230 }
231};
232
233const defaultSettings = {
234 space: '',
235 separator: ', ',
236 beautify: false
237};
238
239const beautifySettings = {
240 space: ' ',
241 separator: ',',
242 beautify: true
243};
244
245/**
246 * Designed for use in test messages, displayValue takes a javascript value and returns a human readable string representation of that value.
247 *
248 * Notes:
249 * - finite numbers are passed through number.toLocaleString()
250 * - -0 is rendered as -0
251 * - 1300 is rendered as 1,300 (depending on locale)
252 * - strings are wrapped in double quotes
253 * - Arrays and Objects are passed through a function similar to JSON.stringify, but values are individually run through displayValue
254 * - Array-like values such as arguments are handled like Arrays
255 * - Object-like values such as ClientRect and DOMRect are handled like Objects
256 * - Constructors will return the constructor's name
257 * - Instances of non-native constructors:
258 * - will return the result of .toString() if other than '[object Object]'
259 * - otherwise returns '[object Name]' where Name is the constructor's name
260 *
261 * @example
262 *
263 * ``` javascript
264 * import displayValue from 'display-value';
265 *
266 * displayValue(-0); // '-0'
267 * displayValue(1300000); // '1,300,000'
268 * displayValue('foo'); // '"foo"'
269 * displayValue({x: 1}); // '{"x": 1}'
270 *
271 * displayValue(() => {}); // '() => {…}'
272 * displayValue(function(param) {}); // 'ƒ (param) {…}'
273 * displayValue(function name() {}); // 'ƒ name() {…}'
274 *
275 * displayValue(Symbol()); // 'Symbol()'
276 * displayValue(Symbol('name')); // 'Symbol(name)'
277 *
278 * displayValue(new CustomClass()); // '[object CustomClass]'
279 *
280 * displayValue([{x: 1}, {x: 2000}], {beautify: true});
281 * // '[
282 * // {
283 * // "x": 1
284 * // }, {
285 * // "x": 2,000
286 * // }
287 * // ]'
288 * ```
289 *
290 * @function displayValue
291 *
292 * @arg {*} value
293 * @arg {Object} [settings]
294 * @arg {Boolean} [settings.beautify=false] - If true and value is an Array or Object then the output is rendered in multiple lines with indentation
295 *
296 * @returns {string}
297 */
298export default function displayValue(value, settings = {}) {
299 return processValue(value, 0, settings.beautify === true ? beautifySettings : defaultSettings);
300};