Skip to content

Commit

Permalink
refactor: utils.type() (mochajs#4457); closes mochajs#4306
Browse files Browse the repository at this point in the history
* Split utils.type() into canonicalType() for diffs and type() for everything else

* Change function to be much more generic and replace all the existing calls with the new name

* Make utils.type straightforward and change nodejs serializer calls to it

* Refactor utils.type() and related code to use modern ES
  • Loading branch information
ValeriaVG authored Oct 9, 2020
1 parent fd9fe95 commit 1aa182b
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 22 deletions.
8 changes: 5 additions & 3 deletions lib/nodejs/serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ class SerializableEvent {
parent[key] = Object.create(null);
return;
}
if (type(value) === 'error' || value instanceof Error) {
let _type = type(value);
if (_type === 'error') {
// we need to reference the stack prop b/c it's lazily-loaded.
// `__type` is necessary for deserialization to create an `Error` later.
// `message` is apparently not enumerable, so we must handle it specifically.
Expand All @@ -206,10 +207,11 @@ class SerializableEvent {
__type: 'Error'
});
parent[key] = value;
// after this, the result of type(value) will be `object`, and we'll throw
// after this, set the result of type(value) to be `object`, and we'll throw
// whatever other junk is in the original error into the new `value`.
_type = 'object';
}
switch (type(value)) {
switch (_type) {
case 'object':
if (type(value.serialize) === 'function') {
parent[key] = value.serialize();
Expand Down
6 changes: 4 additions & 2 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var dQuote = utils.dQuote;
var sQuote = utils.sQuote;
var stackFilter = utils.stackTraceFilter();
var stringify = utils.stringify;
var type = utils.type;

var errors = require('./errors');
var createInvalidExceptionError = errors.createInvalidExceptionError;
var createUnsupportedError = errors.createUnsupportedError;
Expand Down Expand Up @@ -1223,7 +1223,9 @@ function isError(err) {
*/
function thrown2Error(err) {
return new Error(
'the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)'
`the ${utils.canonicalType(err)} ${stringify(
err
)} was thrown, throw an Error :)`
);
}

Expand Down
75 changes: 59 additions & 16 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,19 +129,21 @@ function emptyRepresentation(value, typeHint) {
* @param {*} value The value to test.
* @returns {string} Computed type
* @example
* type({}) // 'object'
* type([]) // 'array'
* type(1) // 'number'
* type(false) // 'boolean'
* type(Infinity) // 'number'
* type(null) // 'null'
* type(new Date()) // 'date'
* type(/foo/) // 'regexp'
* type('type') // 'string'
* type(global) // 'global'
* type(new String('foo') // 'object'
* canonicalType({}) // 'object'
* canonicalType([]) // 'array'
* canonicalType(1) // 'number'
* canonicalType(false) // 'boolean'
* canonicalType(Infinity) // 'number'
* canonicalType(null) // 'null'
* canonicalType(new Date()) // 'date'
* canonicalType(/foo/) // 'regexp'
* canonicalType('type') // 'string'
* canonicalType(global) // 'global'
* canonicalType(new String('foo') // 'object'
* canonicalType(async function() {}) // 'asyncfunction'
* canonicalType(await import(name)) // 'module'
*/
var type = (exports.type = function type(value) {
var canonicalType = (exports.canonicalType = function canonicalType(value) {
if (value === undefined) {
return 'undefined';
} else if (value === null) {
Expand All @@ -155,6 +157,47 @@ var type = (exports.type = function type(value) {
.toLowerCase();
});

/**
*
* Returns a general type or data structure of a variable
* @private
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures
* @param {*} value The value to test.
* @returns {string} One of undefined, boolean, number, string, bigint, symbol, object
* @example
* type({}) // 'object'
* type([]) // 'array'
* type(1) // 'number'
* type(false) // 'boolean'
* type(Infinity) // 'number'
* type(null) // 'null'
* type(new Date()) // 'object'
* type(/foo/) // 'object'
* type('type') // 'string'
* type(global) // 'object'
* type(new String('foo') // 'string'
*/
exports.type = function type(value) {
// Null is special
if (value === null) return 'null';
const primitives = new Set([
'undefined',
'boolean',
'number',
'string',
'bigint',
'symbol'
]);
const _type = typeof value;
if (_type === 'function') return _type;
if (primitives.has(_type)) return _type;
if (value instanceof String) return 'string';
if (value instanceof Error) return 'error';
if (Array.isArray(value)) return 'array';

return _type;
};

/**
* Stringify `value`. Different behavior depending on type of value:
*
Expand All @@ -171,7 +214,7 @@ var type = (exports.type = function type(value) {
* @return {string}
*/
exports.stringify = function(value) {
var typeHint = type(value);
var typeHint = canonicalType(value);

if (!~['object', 'array', 'function'].indexOf(typeHint)) {
if (typeHint === 'buffer') {
Expand Down Expand Up @@ -237,7 +280,7 @@ function jsonStringify(object, spaces, depth) {
}

function _stringify(val) {
switch (type(val)) {
switch (canonicalType(val)) {
case 'null':
case 'undefined':
val = '[' + val + ']';
Expand Down Expand Up @@ -318,7 +361,7 @@ exports.canonicalize = function canonicalize(value, stack, typeHint) {
/* eslint-disable no-unused-vars */
var prop;
/* eslint-enable no-unused-vars */
typeHint = typeHint || type(value);
typeHint = typeHint || canonicalType(value);
function withStack(value, fn) {
stack.push(value);
fn();
Expand Down Expand Up @@ -552,7 +595,7 @@ exports.createMap = function(obj) {
* @throws {TypeError} if argument is not a non-empty object.
*/
exports.defineConstants = function(obj) {
if (type(obj) !== 'object' || !Object.keys(obj).length) {
if (canonicalType(obj) !== 'object' || !Object.keys(obj).length) {
throw new TypeError('Invalid argument; expected a non-empty object');
}
return Object.freeze(exports.createMap(obj));
Expand Down
21 changes: 20 additions & 1 deletion test/node-unit/utils.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,29 @@ describe('utils', function() {
});

describe('type()', function() {
it('should return "asyncfunction" if the parameter is an async function', function() {
it('should return "function" if the parameter is an async function', function() {
expect(
utils.type(async () => {}),
'to be',
'function'
);
});
it('should return "error" if the parameter is an Error', function() {
expect(utils.type(new Error('err')), 'to be', 'error');
});
});
describe('canonicalType()', function() {
it('should return "buffer" if the parameter is a Buffer', function() {
expect(
utils.canonicalType(Buffer.from('ff', 'hex')),
'to be',
'buffer'
);
});
it('should return "asyncfunction" if the parameter is an async function', function() {
expect(
utils.canonicalType(async () => {}),
'to be',
'asyncfunction'
);
});
Expand Down
54 changes: 54 additions & 0 deletions test/unit/utils.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,60 @@ describe('lib/utils', function() {
};
});

it('should recognize various types', function() {
expect(type({}), 'to be', 'object');
expect(type([]), 'to be', 'array');
expect(type(1), 'to be', 'number');
expect(type(Infinity), 'to be', 'number');
expect(type(null), 'to be', 'null');
expect(type(undefined), 'to be', 'undefined');
expect(type(new Date()), 'to be', 'object');
expect(type(/foo/), 'to be', 'object');
expect(type('type'), 'to be', 'string');
expect(type(new Error()), 'to be', 'error');
expect(type(global), 'to be', 'object');
expect(type(true), 'to be', 'boolean');
expect(type(Buffer.from('ff', 'hex')), 'to be', 'object');
expect(type(Symbol.iterator), 'to be', 'symbol');
expect(type(new Map()), 'to be', 'object');
expect(type(new WeakMap()), 'to be', 'object');
expect(type(new Set()), 'to be', 'object');
expect(type(new WeakSet()), 'to be', 'object');
expect(
type(async () => {}),
'to be',
'function'
);
});

describe('when toString on null or undefined stringifies window', function() {
it('should recognize null and undefined', function() {
expect(type(null), 'to be', 'null');
expect(type(undefined), 'to be', 'undefined');
});
});

afterEach(function() {
Object.prototype.toString = toString;
});
});

describe('canonicalType()', function() {
/* eslint no-extend-native: off */

var type = utils.canonicalType;
var toString = Object.prototype.toString;

beforeEach(function() {
// some JS engines such as PhantomJS 1.x exhibit this behavior
Object.prototype.toString = function() {
if (this === global) {
return '[object DOMWindow]';
}
return toString.call(this);
};
});

it('should recognize various types', function() {
expect(type({}), 'to be', 'object');
expect(type([]), 'to be', 'array');
Expand Down

0 comments on commit 1aa182b

Please sign in to comment.