Skip to content

Commit 2380d90

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 e62a8b5 commit 2380d90

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.
@@ -1060,7 +1065,39 @@ function getBoxedBase(value, ctx, keys, constructor, tag) {
10601065
return ctx.stylize(base, type.toLowerCase());
10611066
}
10621067

1068+
function getClassBase(value, constructor, tag) {
1069+
const hasName = ObjectPrototypeHasOwnProperty(value, 'name');
1070+
const name = (hasName && value.name) || '(anonymous)';
1071+
let base = `class ${name}`;
1072+
if (constructor !== 'Function' && constructor !== null) {
1073+
base += ` [${constructor}]`;
1074+
}
1075+
if (tag !== '' && constructor !== tag) {
1076+
base += ` [${tag}]`;
1077+
}
1078+
if (constructor !== null) {
1079+
const superName = ObjectGetPrototypeOf(value).name;
1080+
if (superName) {
1081+
base += ` extends ${superName}`;
1082+
}
1083+
} else {
1084+
base += ' extends [null prototype]';
1085+
}
1086+
return `[${base}]`;
1087+
}
1088+
10631089
function getFunctionBase(value, constructor, tag) {
1090+
const stringified = FunctionPrototypeToString(value);
1091+
if (stringified.slice(0, 5) === 'class' && stringified.endsWith('}')) {
1092+
const slice = stringified.slice(5, -1);
1093+
const bracketIndex = slice.indexOf('{');
1094+
if (bracketIndex !== -1 &&
1095+
(!slice.slice(0, bracketIndex).includes('(') ||
1096+
// Slow path to guarantee that it's indeed a class.
1097+
classRegExp.test(slice.replace(stripCommentsRegExp)))) {
1098+
return getClassBase(value, constructor, tag);
1099+
}
1100+
}
10641101
let type = 'Function';
10651102
if (isGeneratorFunction(value)) {
10661103
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
@@ -1973,6 +1973,88 @@ assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'");
19731973
);
19741974
});
19751975

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

0 commit comments

Comments
 (0)