Skip to content

Commit e3533ab

Browse files
Ruben Bridgewatercodebytere
Ruben Bridgewater
authored andcommitted
util: mark classes while inspecting them
This outlines the basic class setup when inspecting a class. Signed-off-by: Ruben Bridgewater <[email protected]> PR-URL: #32332 Fixes: #32270 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Anto Aravinth <[email protected]>
1 parent a94e7da commit e3533ab

File tree

3 files changed

+120
-1
lines changed

3 files changed

+120
-1
lines changed

lib/internal/util/inspect.js

+37
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const {
1212
DatePrototypeToString,
1313
ErrorPrototypeToString,
1414
Float32Array,
15+
FunctionPrototypeToString,
1516
JSONStringify,
1617
Map,
1718
MapPrototype,
@@ -170,6 +171,10 @@ const numberRegExp = /^(0|[1-9][0-9]*)$/;
170171
const coreModuleRegExp = /^ at (?:[^/\\(]+ \(|)((?<![/\\]).+)\.js:\d+:\d+\)?$/;
171172
const nodeModulesRegExp = /[/\\]node_modules[/\\](.+?)(?=[/\\])/g;
172173

174+
const classRegExp = /^(\s+[^(]*?)\s*{/;
175+
// eslint-disable-next-line node-core/no-unescaped-regexp-dot
176+
const stripCommentsRegExp = /(\/\/.*?\n)|(\/\*(.|\n)*?\*\/)/g;
177+
173178
const kMinLineLength = 16;
174179

175180
// Constants to map the iterator state.
@@ -1054,7 +1059,39 @@ function getBoxedBase(value, ctx, keys, constructor, tag) {
10541059
return ctx.stylize(base, type.toLowerCase());
10551060
}
10561061

1062+
function getClassBase(value, constructor, tag) {
1063+
const hasName = ObjectPrototypeHasOwnProperty(value, 'name');
1064+
const name = (hasName && value.name) || '(anonymous)';
1065+
let base = `class ${name}`;
1066+
if (constructor !== 'Function' && constructor !== null) {
1067+
base += ` [${constructor}]`;
1068+
}
1069+
if (tag !== '' && constructor !== tag) {
1070+
base += ` [${tag}]`;
1071+
}
1072+
if (constructor !== null) {
1073+
const superName = ObjectGetPrototypeOf(value).name;
1074+
if (superName) {
1075+
base += ` extends ${superName}`;
1076+
}
1077+
} else {
1078+
base += ' extends [null prototype]';
1079+
}
1080+
return `[${base}]`;
1081+
}
1082+
10571083
function getFunctionBase(value, constructor, tag) {
1084+
const stringified = FunctionPrototypeToString(value);
1085+
if (stringified.slice(0, 5) === 'class' && stringified.endsWith('}')) {
1086+
const slice = stringified.slice(5, -1);
1087+
const bracketIndex = slice.indexOf('{');
1088+
if (bracketIndex !== -1 &&
1089+
(!slice.slice(0, bracketIndex).includes('(') ||
1090+
// Slow path to guarantee that it's indeed a class.
1091+
classRegExp.test(slice.replace(stripCommentsRegExp)))) {
1092+
return getClassBase(value, constructor, tag);
1093+
}
1094+
}
10581095
let type = 'Function';
10591096
if (isGeneratorFunction(value)) {
10601097
type = `Generator${type}`;

test/parallel/test-repl-top-level-await.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ async function ordinaryTests() {
116116
['await 0; function foo() {}'],
117117
['foo', '[Function: foo]'],
118118
['class Foo {}; await 1;', '1'],
119-
['Foo', '[Function: Foo]'],
119+
['Foo', '[class Foo]'],
120120
['if (await true) { function bar() {}; }'],
121121
['bar', '[Function: bar]'],
122122
['if (await true) { class Bar {}; }'],

test/parallel/test-util-inspect.js

+82
Original file line numberDiff line numberDiff line change
@@ -1968,6 +1968,88 @@ assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'");
19681968
);
19691969
});
19701970

1971+
// Verify that classes are properly inspected.
1972+
[
1973+
/* eslint-disable spaced-comment, no-multi-spaces, brace-style */
1974+
// The whitespace is intentional.
1975+
[class { }, '[class (anonymous)]'],
1976+
[class extends Error { log() {} }, '[class (anonymous) extends Error]'],
1977+
[class A { constructor(a) { this.a = a; } log() { return this.a; } },
1978+
'[class A]'],
1979+
[class
1980+
// Random { // comments /* */ are part of the toString() result
1981+
/* eslint-disable-next-line space-before-blocks */
1982+
äß/**/extends/*{*/TypeError{}, '[class äß extends TypeError]'],
1983+
/* The whitespace and new line is intended! */
1984+
// Foobar !!!
1985+
[class X extends /****/ Error
1986+
// More comments
1987+
{}, '[class X extends Error]']
1988+
/* eslint-enable spaced-comment, no-multi-spaces, brace-style */
1989+
].forEach(([clazz, string]) => {
1990+
const inspected = util.inspect(clazz);
1991+
assert.strictEqual(inspected, string);
1992+
Object.defineProperty(clazz, Symbol.toStringTag, {
1993+
value: 'Woohoo'
1994+
});
1995+
const parts = inspected.slice(0, -1).split(' ');
1996+
const [, name, ...rest] = parts;
1997+
rest.unshift('[Woohoo]');
1998+
if (rest.length) {
1999+
rest[rest.length - 1] += ']';
2000+
}
2001+
assert.strictEqual(
2002+
util.inspect(clazz),
2003+
['[class', name, ...rest].join(' ')
2004+
);
2005+
if (rest.length) {
2006+
rest[rest.length - 1] = rest[rest.length - 1].slice(0, -1);
2007+
rest.length = 1;
2008+
}
2009+
Object.setPrototypeOf(clazz, null);
2010+
assert.strictEqual(
2011+
util.inspect(clazz),
2012+
['[class', name, ...rest, 'extends [null prototype]]'].join(' ')
2013+
);
2014+
Object.defineProperty(clazz, 'name', { value: 'Foo' });
2015+
const res = ['[class', 'Foo', ...rest, 'extends [null prototype]]'].join(' ');
2016+
assert.strictEqual(util.inspect(clazz), res);
2017+
clazz.foo = true;
2018+
assert.strictEqual(util.inspect(clazz), `${res} { foo: true }`);
2019+
});
2020+
2021+
// "class" properties should not be detected as "class".
2022+
{
2023+
// eslint-disable-next-line space-before-function-paren
2024+
let obj = { class () {} };
2025+
assert.strictEqual(
2026+
util.inspect(obj),
2027+
'{ class: [Function: class] }'
2028+
);
2029+
obj = { class: () => {} };
2030+
assert.strictEqual(
2031+
util.inspect(obj),
2032+
'{ class: [Function: class] }'
2033+
);
2034+
obj = { ['class Foo {}']() {} };
2035+
assert.strictEqual(
2036+
util.inspect(obj),
2037+
"{ 'class Foo {}': [Function: class Foo {}] }"
2038+
);
2039+
function Foo() {}
2040+
Object.defineProperty(Foo, 'toString', { value: () => 'class Foo {}' });
2041+
assert.strictEqual(
2042+
util.inspect(Foo),
2043+
'[Function: Foo]'
2044+
);
2045+
function fn() {}
2046+
Object.defineProperty(fn, 'name', { value: 'class Foo {}' });
2047+
assert.strictEqual(
2048+
util.inspect(fn),
2049+
'[Function: class Foo {}]'
2050+
);
2051+
}
2052+
19712053
// Verify that throwing in valueOf and toString still produces nice results.
19722054
[
19732055
[new String(55), "[String: '55']"],

0 commit comments

Comments
 (0)