0
0
mirror of https://github.com/nodejs/node.git synced 2024-11-29 23:16:30 +01:00
nodejs/lib/util.js
Ruben Bridgewater 1fe824bcbb
util,console: handle symbols as defined in the spec
The `console` functions rely on the `util.format()` behavior. It
did not follow the whatwg spec when it comes to symbols in combination
with the %d, %i and %f format specifiers. Using a symbol argument in
combination with one of these specifiers resulted in an error instead
of returning `'NaN'`. This is now fixed by this patch.

PR-URL: https://github.com/nodejs/node/pull/23708
Refs: https://console.spec.whatwg.org/#formatter
Reviewed-By: Roman Reiss <me@silverwind.io>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
2018-12-03 12:34:42 +01:00

469 lines
13 KiB
JavaScript

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const errors = require('internal/errors');
const { inspect } = require('internal/util/inspect');
const {
ERR_FALSY_VALUE_REJECTION,
ERR_INVALID_ARG_TYPE,
ERR_OUT_OF_RANGE
} = errors.codes;
const { validateNumber } = require('internal/validators');
const { TextDecoder, TextEncoder } = require('internal/encoding');
const { isBuffer } = require('buffer').Buffer;
const types = internalBinding('types');
Object.assign(types, require('internal/util/types'));
const {
isRegExp,
isDate,
} = types;
const {
deprecate,
getSystemErrorName: internalErrorName,
isError,
promisify,
} = require('internal/util');
let CIRCULAR_ERROR_MESSAGE;
let internalDeepEqual;
function tryStringify(arg) {
try {
return JSON.stringify(arg);
} catch (err) {
// Populate the circular error message lazily
if (!CIRCULAR_ERROR_MESSAGE) {
try {
const a = {}; a.a = a; JSON.stringify(a);
} catch (err) {
CIRCULAR_ERROR_MESSAGE = err.message;
}
}
if (err.name === 'TypeError' && err.message === CIRCULAR_ERROR_MESSAGE)
return '[Circular]';
throw err;
}
}
const emptyOptions = {};
function format(...args) {
return formatWithOptions(emptyOptions, ...args);
}
function formatValue(val, inspectOptions) {
const inspectTypes = ['object', 'symbol', 'function', 'number'];
if (inspectTypes.includes(typeof val)) {
return inspect(val, inspectOptions);
} else {
return String(val);
}
}
function formatWithOptions(inspectOptions, ...args) {
const first = args[0];
const parts = [];
const firstIsString = typeof first === 'string';
if (firstIsString && args.length === 1) {
return first;
}
if (firstIsString && /%[sjdOoif%]/.test(first)) {
let i, tempStr;
let str = '';
let a = 1;
let lastPos = 0;
for (i = 0; i < first.length - 1; i++) {
if (first.charCodeAt(i) === 37) { // '%'
const nextChar = first.charCodeAt(++i);
if (a !== args.length) {
switch (nextChar) {
case 115: // 's'
tempStr = String(args[a++]);
break;
case 106: // 'j'
tempStr = tryStringify(args[a++]);
break;
case 100: // 'd'
const tempNum = args[a++];
// eslint-disable-next-line valid-typeof
if (typeof tempNum === 'bigint') {
tempStr = `${tempNum}n`;
} else if (typeof tempNum === 'symbol') {
tempStr = 'NaN';
} else {
tempStr = `${Number(tempNum)}`;
}
break;
case 79: // 'O'
tempStr = inspect(args[a++], inspectOptions);
break;
case 111: // 'o'
{
const opts = Object.assign({}, inspectOptions, {
showHidden: true,
showProxy: true,
depth: 4
});
tempStr = inspect(args[a++], opts);
break;
}
case 105: // 'i'
const tempInteger = args[a++];
// eslint-disable-next-line valid-typeof
if (typeof tempInteger === 'bigint') {
tempStr = `${tempInteger}n`;
} else if (typeof tempInteger === 'symbol') {
tempStr = 'NaN';
} else {
tempStr = `${parseInt(tempInteger)}`;
}
break;
case 102: // 'f'
const tempFloat = args[a++];
if (typeof tempFloat === 'symbol') {
tempStr = 'NaN';
} else {
tempStr = `${parseFloat(tempFloat)}`;
}
break;
case 37: // '%'
str += first.slice(lastPos, i);
lastPos = i + 1;
continue;
default: // any other character is not a correct placeholder
continue;
}
if (lastPos !== i - 1) {
str += first.slice(lastPos, i - 1);
}
str += tempStr;
lastPos = i + 1;
} else if (nextChar === 37) {
str += first.slice(lastPos, i);
lastPos = i + 1;
}
}
}
if (lastPos === 0) {
str = first;
} else if (lastPos < first.length) {
str += first.slice(lastPos);
}
parts.push(str);
while (a < args.length) {
parts.push(formatValue(args[a], inspectOptions));
a++;
}
} else {
for (const arg of args) {
parts.push(formatValue(arg, inspectOptions));
}
}
return parts.join(' ');
}
const debugs = {};
let debugEnvRegex = /^$/;
if (process.env.NODE_DEBUG) {
let debugEnv = process.env.NODE_DEBUG;
debugEnv = debugEnv.replace(/[|\\{}()[\]^$+?.]/g, '\\$&')
.replace(/\*/g, '.*')
.replace(/,/g, '$|^')
.toUpperCase();
debugEnvRegex = new RegExp(`^${debugEnv}$`, 'i');
}
// Emits warning when user sets
// NODE_DEBUG=http or NODE_DEBUG=http2.
function emitWarningIfNeeded(set) {
if ('HTTP' === set || 'HTTP2' === set) {
process.emitWarning('Setting the NODE_DEBUG environment variable ' +
'to \'' + set.toLowerCase() + '\' can expose sensitive ' +
'data (such as passwords, tokens and authentication headers) ' +
'in the resulting log.');
}
}
function debuglog(set) {
set = set.toUpperCase();
if (!debugs[set]) {
if (debugEnvRegex.test(set)) {
const pid = process.pid;
emitWarningIfNeeded(set);
debugs[set] = function debug() {
const msg = exports.format.apply(exports, arguments);
console.error('%s %d: %s', set, pid, msg);
};
} else {
debugs[set] = function debug() {};
}
}
return debugs[set];
}
function isBoolean(arg) {
return typeof arg === 'boolean';
}
function isNull(arg) {
return arg === null;
}
function isNullOrUndefined(arg) {
return arg === null || arg === undefined;
}
function isNumber(arg) {
return typeof arg === 'number';
}
function isString(arg) {
return typeof arg === 'string';
}
function isSymbol(arg) {
return typeof arg === 'symbol';
}
function isUndefined(arg) {
return arg === undefined;
}
function isObject(arg) {
return arg !== null && typeof arg === 'object';
}
function isFunction(arg) {
return typeof arg === 'function';
}
function isPrimitive(arg) {
return arg === null ||
typeof arg !== 'object' && typeof arg !== 'function';
}
function pad(n) {
return n.toString().padStart(2, '0');
}
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep',
'Oct', 'Nov', 'Dec'];
// 26 Feb 16:19:34
function timestamp() {
const d = new Date();
const time = [pad(d.getHours()),
pad(d.getMinutes()),
pad(d.getSeconds())].join(':');
return [d.getDate(), months[d.getMonth()], time].join(' ');
}
// log is just a thin wrapper to console.log that prepends a timestamp
function log() {
console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments));
}
/**
* Inherit the prototype methods from one constructor into another.
*
* The Function.prototype.inherits from lang.js rewritten as a standalone
* function (not on Function.prototype). NOTE: If this file is to be loaded
* during bootstrapping this function needs to be rewritten using some native
* functions as prototype setup using normal JavaScript does not work as
* expected during bootstrapping (see mirror.js in r114903).
*
* @param {function} ctor Constructor function which needs to inherit the
* prototype.
* @param {function} superCtor Constructor function to inherit prototype from.
* @throws {TypeError} Will error if either constructor is null, or if
* the super constructor lacks a prototype.
*/
function inherits(ctor, superCtor) {
if (ctor === undefined || ctor === null)
throw new ERR_INVALID_ARG_TYPE('ctor', 'Function', ctor);
if (superCtor === undefined || superCtor === null)
throw new ERR_INVALID_ARG_TYPE('superCtor', 'Function', superCtor);
if (superCtor.prototype === undefined) {
throw new ERR_INVALID_ARG_TYPE('superCtor.prototype',
'Function', superCtor.prototype);
}
Object.defineProperty(ctor, 'super_', {
value: superCtor,
writable: true,
configurable: true
});
Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
}
function _extend(target, source) {
// Don't do anything if source isn't an object
if (source === null || typeof source !== 'object') return target;
const keys = Object.keys(source);
let i = keys.length;
while (i--) {
target[keys[i]] = source[keys[i]];
}
return target;
}
// Deprecated old stuff.
function print(...args) {
for (var i = 0, len = args.length; i < len; ++i) {
process.stdout.write(String(args[i]));
}
}
function puts(...args) {
for (var i = 0, len = args.length; i < len; ++i) {
process.stdout.write(`${args[i]}\n`);
}
}
function debug(x) {
process.stderr.write(`DEBUG: ${x}\n`);
}
function error(...args) {
for (var i = 0, len = args.length; i < len; ++i) {
process.stderr.write(`${args[i]}\n`);
}
}
function callbackifyOnRejected(reason, cb) {
// `!reason` guard inspired by bluebird (Ref: https://goo.gl/t5IS6M).
// Because `null` is a special error value in callbacks which means "no error
// occurred", we error-wrap so the callback consumer can distinguish between
// "the promise rejected with null" or "the promise fulfilled with undefined".
if (!reason) {
const newReason = new ERR_FALSY_VALUE_REJECTION();
newReason.reason = reason;
reason = newReason;
Error.captureStackTrace(reason, callbackifyOnRejected);
}
return cb(reason);
}
function callbackify(original) {
if (typeof original !== 'function') {
throw new ERR_INVALID_ARG_TYPE('original', 'Function', original);
}
// We DO NOT return the promise as it gives the user a false sense that
// the promise is actually somehow related to the callback's execution
// and that the callback throwing will reject the promise.
function callbackified(...args) {
const maybeCb = args.pop();
if (typeof maybeCb !== 'function') {
throw new ERR_INVALID_ARG_TYPE('last argument', 'Function', maybeCb);
}
const cb = (...args) => { Reflect.apply(maybeCb, this, args); };
// In true node style we process the callback on `nextTick` with all the
// implications (stack, `uncaughtException`, `async_hooks`)
Reflect.apply(original, this, args)
.then((ret) => process.nextTick(cb, null, ret),
(rej) => process.nextTick(callbackifyOnRejected, rej, cb));
}
Object.setPrototypeOf(callbackified, Object.getPrototypeOf(original));
Object.defineProperties(callbackified,
Object.getOwnPropertyDescriptors(original));
return callbackified;
}
function getSystemErrorName(err) {
validateNumber(err, 'err');
if (err >= 0 || !Number.isSafeInteger(err)) {
throw new ERR_OUT_OF_RANGE('err', 'a negative integer', err);
}
return internalErrorName(err);
}
// Keep the `exports =` so that various functions can still be monkeypatched
module.exports = exports = {
_errnoException: errors.errnoException,
_exceptionWithHostPort: errors.exceptionWithHostPort,
_extend,
callbackify,
debuglog,
deprecate,
format,
formatWithOptions,
getSystemErrorName,
inherits,
inspect,
isArray: Array.isArray,
isBoolean,
isBuffer,
isDeepStrictEqual(a, b) {
if (internalDeepEqual === undefined) {
internalDeepEqual = require('internal/util/comparisons')
.isDeepStrictEqual;
}
return internalDeepEqual(a, b);
},
isNull,
isNullOrUndefined,
isNumber,
isString,
isSymbol,
isUndefined,
isRegExp,
isObject,
isDate,
isError,
isFunction,
isPrimitive,
log,
promisify,
TextDecoder,
TextEncoder,
types,
// Deprecated Old Stuff
debug: deprecate(debug,
'util.debug is deprecated. Use console.error instead.',
'DEP0028'),
error: deprecate(error,
'util.error is deprecated. Use console.error instead.',
'DEP0029'),
print: deprecate(print,
'util.print is deprecated. Use console.log instead.',
'DEP0026'),
puts: deprecate(puts,
'util.puts is deprecated. Use console.log instead.',
'DEP0027')
};