Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

repl: tab auto complete big arrays #22408

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion lib/internal/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,6 @@ function isInsideNodeModules() {
return false;
}


module.exports = {
assertCrypto,
cachedResult,
Expand Down
17 changes: 12 additions & 5 deletions lib/internal/util/comparisons.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ const {
isRegExp,
isSet
} = internalBinding('types');
const { getOwnNonIndexProperties } = process.binding('util');
const {
getOwnNonIndexProperties,
propertyFilter: {
ONLY_ENUMERABLE
}
} = process.binding('util');

const ReflectApply = Reflect.apply;

Expand Down Expand Up @@ -118,8 +123,9 @@ function strictDeepEqual(val1, val2, memos) {
if (val1.length !== val2.length) {
return false;
}
const keys1 = getOwnNonIndexProperties(val1);
if (keys1.length !== getOwnNonIndexProperties(val2).length) {
const keys1 = getOwnNonIndexProperties(val1, ONLY_ENUMERABLE);
const keys2 = getOwnNonIndexProperties(val2, ONLY_ENUMERABLE);
if (keys1.length !== keys2.length) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated optimization idea: would it make sense to return only the length from V8 rather than the properties themselves in the case of keys2?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already spoke with @nodejs/v8 to implement a API that does exactly that. Until that exists, I would rather keep it as is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for needing getOwnNonIndexProperties should be brought up to the TC39 (if it hasn't already). It'd be good to let them know that Node is having to use engine level APIs to work around a JS lang limitation (no way to return a value with methods that return arrays of keys).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already spoke to @littledan and I am writing the proposal at the moment. It's almost done.

return false;
}
return keyCheck(val1, val2, kStrict, memos, kIsArray, keys1);
Expand Down Expand Up @@ -150,8 +156,9 @@ function strictDeepEqual(val1, val2, memos) {
// Buffer.compare returns true, so val1.length === val2.length. If they both
// only contain numeric keys, we don't need to exam further than checking
// the symbols.
const keys1 = getOwnNonIndexProperties(val1);
if (keys1.length !== getOwnNonIndexProperties(val2).length) {
const keys1 = getOwnNonIndexProperties(val1, ONLY_ENUMERABLE);
const keys2 = getOwnNonIndexProperties(val2, ONLY_ENUMERABLE);
if (keys1.length !== keys2.length) {
return false;
}
return keyCheck(val1, val2, kStrict, memos, kNoIterator, keys1);
Expand Down
36 changes: 9 additions & 27 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ const {
isIdentifierChar
} = require('internal/deps/acorn/dist/acorn');
const internalUtil = require('internal/util');
const { isTypedArray } = require('internal/util/types');
const util = require('util');
const utilBinding = process.binding('util');
const { inherits } = util;
Expand All @@ -74,6 +73,13 @@ const {
const { sendInspectorCommand } = require('internal/util/inspector');
const { experimentalREPLAwait } = process.binding('config');
const { isRecoverableError } = require('internal/repl/recoverable');
const {
getOwnNonIndexProperties,
propertyFilter: {
ALL_PROPERTIES,
SKIP_SYMBOLS
}
} = process.binding('util');

// Lazy-loaded.
let processTopLevelAwait;
Expand Down Expand Up @@ -927,34 +933,10 @@ function isIdentifier(str) {
return true;
}

const ARRAY_LENGTH_THRESHOLD = 1e6;

function mayBeLargeObject(obj) {
if (Array.isArray(obj)) {
return obj.length > ARRAY_LENGTH_THRESHOLD ? ['length'] : null;
} else if (isTypedArray(obj)) {
return obj.length > ARRAY_LENGTH_THRESHOLD ? [] : null;
}

return null;
}

function filteredOwnPropertyNames(obj) {
if (!obj) return [];
const fakeProperties = mayBeLargeObject(obj);
if (fakeProperties !== null) {
this.outputStream.write('\r\n');
process.emitWarning(
'The current array, Buffer or TypedArray has too many entries. ' +
'Certain properties may be missing from completion output.',
'REPLWarning',
undefined,
undefined,
true);

return fakeProperties;
}
return Object.getOwnPropertyNames(obj).filter(isIdentifier);
const filter = ALL_PROPERTIES | SKIP_SYMBOLS;
return getOwnNonIndexProperties(obj, filter).filter(isIdentifier);
}

function getGlobalLexicalScopeNames(contextId) {
Expand Down
32 changes: 26 additions & 6 deletions src/node_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,43 @@
namespace node {
namespace util {

using v8::ALL_PROPERTIES;
using v8::Array;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::Integer;
using v8::Local;
using v8::Object;
using v8::ONLY_CONFIGURABLE;
using v8::ONLY_ENUMERABLE;
using v8::ONLY_WRITABLE;
using v8::Private;
using v8::Promise;
using v8::Proxy;
using v8::SKIP_STRINGS;
using v8::SKIP_SYMBOLS;
using v8::String;
using v8::Uint32;
using v8::Value;

static void GetOwnNonIndexProperties(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Local<Context> context = env->context();

if (!args[0]->IsObject())
return;
CHECK(args[0]->IsObject());
CHECK(args[1]->IsUint32());

Local<Object> object = args[0].As<Object>();

v8::Local<v8::Object> object = args[0].As<v8::Object>();
Local<Array> properties;

// Return only non-enumerable properties by default.
v8::Local<v8::Array> properties;
v8::PropertyFilter filter =
static_cast<v8::PropertyFilter>(args[1].As<Uint32>()->Value());

if (!object->GetPropertyNames(
context, v8::KeyCollectionMode::kOwnOnly,
v8::ONLY_ENUMERABLE,
filter,
v8::IndexFilter::kSkipIndices)
.ToLocal(&properties)) {
return;
Expand Down Expand Up @@ -209,6 +218,17 @@ void Initialize(Local<Object> target,
WatchdogHasPendingSigint);

env->SetMethod(target, "safeGetenv", SafeGetenv);

Local<Object> constants = Object::New(env->isolate());
NODE_DEFINE_CONSTANT(constants, ALL_PROPERTIES);
NODE_DEFINE_CONSTANT(constants, ONLY_WRITABLE);
NODE_DEFINE_CONSTANT(constants, ONLY_ENUMERABLE);
NODE_DEFINE_CONSTANT(constants, ONLY_CONFIGURABLE);
NODE_DEFINE_CONSTANT(constants, SKIP_STRINGS);
NODE_DEFINE_CONSTANT(constants, SKIP_SYMBOLS);
target->Set(context,
FIXED_ONE_BYTE_STRING(env->isolate(), "propertyFilter"),
constants).FromJust();
}

} // namespace util
Expand Down
19 changes: 4 additions & 15 deletions test/parallel/test-repl-tab-complete.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,12 +393,6 @@ testMe.complete('obj.', common.mustCall((error, data) => {
assert(data[0].includes('obj.key'));
}));

// tab completion for large buffer
const warningRegEx = new RegExp(
'\\(node:\\d+\\) REPLWarning: The current array, Buffer or TypedArray has ' +
'too many entries\\. Certain properties may be missing from completion ' +
'output\\.');

[
Array,
Buffer,
Expand Down Expand Up @@ -428,11 +422,7 @@ const warningRegEx = new RegExp(
putIn.run([`var ele = new ${type.name}(1e6 + 1); ele.biu = 1;`]);
}

common.hijackStderr(common.mustCall((err) => {
process.nextTick(() => {
assert.ok(warningRegEx.test(err));
});
}));
common.hijackStderr(common.mustNotCall());
testMe.complete('ele.', common.mustCall((err, data) => {
common.restoreStderr();
assert.ifError(err);
Expand All @@ -443,13 +433,12 @@ const warningRegEx = new RegExp(
Buffer.alloc(0) :
new type(0));

assert.strictEqual(data[0].includes('ele.biu'), true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to have tests that specify the behaviour for long arrays better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is specifically for long arrays. Since this value shows up, we can be certain that it works. I would not know what to improve. Do you have a suggestion?


data[0].forEach((key) => {
if (!key) return;
if (!key || key === 'ele.biu') return;
assert.notStrictEqual(ele[key.substr(4)], undefined);
});

// no `biu`
assert.strictEqual(data.includes('ele.biu'), false);
}));
});

Expand Down