0
0
mirror of https://github.com/nodejs/node.git synced 2024-11-24 03:07:54 +01:00

lib: prefer optional chaining

PR-URL: https://github.com/nodejs/node/pull/55045
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
This commit is contained in:
Aviv Keller 2024-09-24 15:48:15 -04:00 committed by GitHub
parent 04750afb1e
commit 574f2dd517
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 228 additions and 121 deletions

View File

@ -313,6 +313,7 @@ export default [
'node-core/no-unescaped-regexp-dot': 'error',
'node-core/no-duplicate-requires': 'error',
'node-core/prefer-proto': 'error',
'node-core/prefer-optional-chaining': 'error',
},
},
// #endregion

View File

@ -128,7 +128,7 @@ function Agent(options) {
}
const requests = this.requests[name];
if (requests && requests.length) {
if (requests?.length) {
const req = requests.shift();
const reqAsyncRes = req[kRequestAsyncResource];
if (reqAsyncRes) {
@ -437,7 +437,7 @@ Agent.prototype.removeSocket = function removeSocket(s, options) {
}
let req;
if (this.requests[name] && this.requests[name].length) {
if (this.requests[name]?.length) {
debug('removeSocket, have a request, make a socket');
req = this.requests[name][0];
} else {
@ -449,7 +449,7 @@ Agent.prototype.removeSocket = function removeSocket(s, options) {
for (let i = 0; i < keys.length; i++) {
const prop = keys[i];
// Check whether this specific origin is already at maxSockets
if (this.sockets[prop] && this.sockets[prop].length) break;
if (this.sockets[prop]?.length) break;
debug('removeSocket, have a request with different origin,' +
' make a socket');
req = this.requests[prop][0];

View File

@ -174,7 +174,7 @@ function ClientRequest(input, options, cb) {
const protocol = options.protocol || defaultAgent.protocol;
let expectedProtocol = defaultAgent.protocol;
if (this.agent && this.agent.protocol)
if (this.agent?.protocol)
expectedProtocol = this.agent.protocol;
if (options.path) {
@ -190,7 +190,7 @@ function ClientRequest(input, options, cb) {
}
const defaultPort = options.defaultPort ||
(this.agent && this.agent.defaultPort);
(this.agent?.defaultPort);
const optsWithoutSignal = { __proto__: null, ...options };
@ -553,7 +553,7 @@ function socketOnData(d) {
socket.destroy();
req.socket._hadError = true;
emitErrorEvent(req, ret);
} else if (parser.incoming && parser.incoming.upgrade) {
} else if (parser.incoming?.upgrade) {
// Upgrade (if status code 101) or CONNECT
const bytesParsed = ret;
const res = parser.incoming;
@ -591,7 +591,7 @@ function socketOnData(d) {
// Requested Upgrade or used CONNECT method, but have no handler.
socket.destroy();
}
} else if (parser.incoming && parser.incoming.complete &&
} else if (parser.incoming?.complete &&
// When the status code is informational (100, 102-199),
// the server will send a final response after this client
// sends a request body, so we must not free the parser.
@ -838,7 +838,7 @@ function tickOnSocket(req, socket) {
if (
req.timeout !== undefined ||
(req.agent && req.agent.options && req.agent.options.timeout)
(req.agent?.options?.timeout)
) {
listenSocketTimeout(req);
}

View File

@ -85,8 +85,7 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
}
// Parser is also used by http client
const ParserIncomingMessage = (socket && socket.server &&
socket.server[kIncomingMessage]) ||
const ParserIncomingMessage = (socket?.server?.[kIncomingMessage]) ||
IncomingMessage;
const incoming = parser.incoming = new ParserIncomingMessage(socket);

View File

@ -242,7 +242,7 @@ IncomingMessage.prototype._destroy = function _destroy(err, cb) {
IncomingMessage.prototype._addHeaderLines = _addHeaderLines;
function _addHeaderLines(headers, n) {
if (headers && headers.length) {
if (headers?.length) {
let dest;
if (this.complete) {
this.rawTrailers = headers;

View File

@ -423,7 +423,7 @@ OutgoingMessage.prototype._send = function _send(data, encoding, callback, byteL
OutgoingMessage.prototype._writeRaw = _writeRaw;
function _writeRaw(data, encoding, callback, size) {
const conn = this[kSocket];
if (conn && conn.destroyed) {
if (conn?.destroyed) {
// The socket was destroyed. If we're still trying to write to it,
// then we haven't gotten the 'close' event yet.
return false;
@ -789,7 +789,7 @@ OutgoingMessage.prototype.getHeader = function getHeader(name) {
return;
const entry = headers[name.toLowerCase()];
return entry && entry[1];
return entry?.[1];
};
@ -1073,7 +1073,7 @@ OutgoingMessage.prototype.addTrailers = function addTrailers(headers) {
};
function onFinish(outmsg) {
if (outmsg && outmsg.socket && outmsg.socket._hadError) return;
if (outmsg?.socket?._hadError) return;
outmsg.emit('finish');
}
@ -1188,7 +1188,7 @@ OutgoingMessage.prototype._finish = function _finish() {
OutgoingMessage.prototype._flush = function _flush() {
const socket = this[kSocket];
if (socket && socket.writable) {
if (socket?.writable) {
// There might be remaining data in this.output; write it out
const ret = this._flushOutput(socket);

View File

@ -738,7 +738,7 @@ function connectionListenerInternal(server, socket) {
socket.setEncoding = socketSetEncoding;
// We only consume the socket if it has never been consumed before.
if (socket._handle && socket._handle.isStreamBase &&
if (socket._handle?.isStreamBase &&
!socket._handle._consumed) {
parser._consumed = true;
socket._handle._consumed = true;
@ -783,7 +783,7 @@ function socketOnDrain(socket, state) {
}
function socketOnTimeout() {
const req = this.parser && this.parser.incoming;
const req = this.parser?.incoming;
const reqTimeout = req && !req.complete && req.emit('timeout', this);
const res = this._httpMessage;
const resTimeout = res && res.emit('timeout', this);
@ -918,7 +918,7 @@ function onParserExecuteCommon(server, socket, parser, state, ret, d) {
prepareError(ret, parser, d);
debug('parse error', ret);
socketOnError.call(socket, ret);
} else if (parser.incoming && parser.incoming.upgrade) {
} else if (parser.incoming?.upgrade) {
// Upgrade or CONNECT
const req = parser.incoming;
debug('SERVER upgrade or connect', req.method);
@ -963,7 +963,7 @@ function onParserExecuteCommon(server, socket, parser, state, ret, d) {
function clearIncoming(req) {
req = req || this;
const parser = req.socket && req.socket.parser;
const parser = req.socket?.parser;
// Reset the .incoming property so that the request object can be gc'ed.
if (parser && parser.incoming === req) {
if (req.readableEnded) {
@ -1180,7 +1180,7 @@ function onSocketResume() {
}
function onSocketPause() {
if (this._handle && this._handle.reading) {
if (this._handle?.reading) {
this._handle.reading = false;
this._handle.readStop();
}

View File

@ -489,7 +489,7 @@ function initRead(tlsSocket, socket) {
return;
// Socket already has some buffered data - emulate receiving it
if (socket && socket.readableLength) {
if (socket?.readableLength) {
let buf;
while ((buf = socket.read()) !== null)
tlsSocket._handle.receive(buf);
@ -1683,7 +1683,7 @@ function onConnectSecure() {
if (!verifyError && !this.isSessionReused()) {
const hostname = options.servername ||
options.host ||
(options.socket && options.socket._host) ||
(options.socket?._host) ||
'localhost';
const cert = this.getPeerCertificate(true);
verifyError = options.checkServerIdentity(hostname, cert);

View File

@ -446,7 +446,7 @@ function expectedException(actual, expected, message, fn) {
message = 'The error is expected to be an instance of ' +
`"${expected.name}". Received `;
if (isError(actual)) {
const name = (actual.constructor && actual.constructor.name) ||
const name = (actual.constructor?.name) ||
actual.name;
if (expected.name === name) {
message += 'an error with identical name but a different prototype.';
@ -569,7 +569,7 @@ function expectsError(stackStartFn, actual, error, message) {
if (actual === NO_EXCEPTION_SENTINEL) {
let details = '';
if (error && error.name) {
if (error?.name) {
details += ` (${error.name})`;
}
details += message ? `: ${message}` : '.';
@ -627,7 +627,7 @@ function expectsNoError(stackStartFn, actual, error, message) {
expected: error,
operator: stackStartFn.name,
message: `Got unwanted ${fnType}${details}\n` +
`Actual message: "${actual && actual.message}"`,
`Actual message: "${actual?.message}"`,
stackStartFn,
});
}

View File

@ -392,8 +392,7 @@ function execFile(file, args, options, callback) {
let stderr;
if (encoding ||
(
child.stdout &&
child.stdout.readableEncoding
child.stdout?.readableEncoding
)) {
stdout = ArrayPrototypeJoin(_stdout, '');
} else {
@ -401,8 +400,7 @@ function execFile(file, args, options, callback) {
}
if (encoding ||
(
child.stderr &&
child.stderr.readableEncoding
child.stderr?.readableEncoding
)) {
stderr = ArrayPrototypeJoin(_stderr, '');
} else {
@ -855,7 +853,7 @@ function spawnSync(file, args, options) {
// We may want to pass data in on any given fd, ensure it is a valid buffer
for (let i = 0; i < options.stdio.length; i++) {
const input = options.stdio[i] && options.stdio[i].input;
const input = options.stdio[i]?.input;
if (input != null) {
const pipe = options.stdio[i] = { ...options.stdio[i] };
if (isArrayBufferView(input)) {

View File

@ -128,8 +128,8 @@ function Socket(type, listener) {
bindState: BIND_STATE_UNBOUND,
connectState: CONNECT_STATE_DISCONNECTED,
queue: undefined,
reuseAddr: options && options.reuseAddr, // Use UV_UDP_REUSEADDR if true.
ipv6Only: options && options.ipv6Only,
reuseAddr: options?.reuseAddr, // Use UV_UDP_REUSEADDR if true.
ipv6Only: options?.ipv6Only,
recvBufferSize,
sendBufferSize,
};

View File

@ -251,7 +251,7 @@ function getStdin() {
// `stdin` starts out life in a paused state, but node doesn't
// know yet. Explicitly to readStop() it to put it in the
// not-reading state.
if (stdin._handle && stdin._handle.readStop) {
if (stdin._handle?.readStop) {
stdin._handle.reading = false;
stdin._readableState.reading = false;
stdin._handle.readStop();

View File

@ -854,7 +854,7 @@ function setupChannel(target, channel, serializationMode) {
if (handle) {
if (!this._handleQueue)
this._handleQueue = [];
if (obj && obj.postSend)
if (obj?.postSend)
obj.postSend(message, handle, options, callback, target);
}
@ -870,7 +870,7 @@ function setupChannel(target, channel, serializationMode) {
}
} else {
// Cleanup handle on error
if (obj && obj.postSend)
if (obj?.postSend)
obj.postSend(message, handle, options, callback);
if (!options.swallowErrors) {
@ -1115,8 +1115,8 @@ function spawnSync(options) {
}
}
result.stdout = result.output && result.output[1];
result.stderr = result.output && result.output[2];
result.stdout = result.output?.[1];
result.stderr = result.output?.[2];
if (result.error) {
result.error = new ErrnoException(result.error, 'spawnSync ' + options.file);

View File

@ -122,7 +122,7 @@ cluster._getServer = function(obj, options, cb) {
cluster.worker.state = 'listening';
const address = obj.address();
message.act = 'listening';
message.port = (address && address.port) || options.port;
message.port = (address?.port) || options.port;
send(message);
});
};

View File

@ -70,7 +70,7 @@ function normalizeHashName(name, context = kHashContextNode) {
if (typeof name !== 'string')
return name;
name = StringPrototypeToLowerCase(name);
const alias = kHashNames[name] && kHashNames[name][context];
const alias = kHashNames[name]?.[context];
return alias || name;
}

View File

@ -694,7 +694,7 @@ function createRepl(inspector) {
function handleBreakpointResolved({ breakpointId, location }) {
const script = knownScripts[location.scriptId];
const scriptUrl = script && script.url;
const scriptUrl = script?.url;
if (scriptUrl) {
ObjectAssign(location, { scriptUrl });
}
@ -733,7 +733,7 @@ function createRepl(inspector) {
function setBreakpoint(script, line, condition, silent) {
function registerBreakpoint({ breakpointId, actualLocation }) {
handleBreakpointResolved({ breakpointId, location: actualLocation });
if (actualLocation && actualLocation.scriptId) {
if (actualLocation?.scriptId) {
if (!silent) return getSourceSnippet(actualLocation, 5);
} else {
print(`Warning: script '${script}' was not loaded yet.`);

View File

@ -67,7 +67,7 @@ function resolver(bindingName) {
req.callback = callback;
req.hostname = name;
req.oncomplete = onresolve;
req.ttl = !!(options && options.ttl);
req.ttl = !!(options?.ttl);
const err = this._handle[bindingName](req, name);
if (err) throw new DNSException(err, bindingName, name);
if (hasObserver('dns')) {

View File

@ -336,7 +336,7 @@ function resolver(bindingName) {
function query(name, options) {
validateString(name, 'name');
const ttl = !!(options && options.ttl);
const ttl = !!(options?.ttl);
return createResolverPromise(this, bindingName, name, ttl);
}

View File

@ -85,7 +85,7 @@ function GetConstructors(object) {
current !== null;
current = ObjectGetPrototypeOf(current)) {
const desc = ObjectGetOwnPropertyDescriptor(current, 'constructor');
if (desc && desc.value) {
if (desc?.value) {
ObjectDefineProperty(constructors, constructors.length, {
__proto__: null,
value: desc.value, enumerable: true,
@ -98,7 +98,7 @@ function GetConstructors(object) {
function GetName(object) {
const desc = ObjectGetOwnPropertyDescriptor(object, 'name');
return desc && desc.value;
return desc?.value;
}
let internalUtilInspect;

View File

@ -393,7 +393,7 @@ class ModuleLoader {
if (cjsModule) {
assert(finalFormat === 'commonjs-sync');
// Check if the ESM initiating import CJS is being required by the same CJS module.
if (cjsModule && cjsModule[kIsExecuting]) {
if (cjsModule?.[kIsExecuting]) {
const parentFilename = urlToFilename(parentURL);
let message = `Cannot import CommonJS Module ${specifier} in a cycle.`;
if (parentFilename) {

View File

@ -375,7 +375,7 @@ translators.set('json', function jsonStrategy(url, source) {
modulePath = isWindows ?
StringPrototypeReplaceAll(pathname, '/', '\\') : pathname;
module = CJSModule._cache[modulePath];
if (module && module.loaded) {
if (module?.loaded) {
const exports = module.exports;
return new ModuleWrap(url, undefined, ['default'], function() {
this.setExport('default', exports);
@ -389,7 +389,7 @@ translators.set('json', function jsonStrategy(url, source) {
// export, we have to check again if the module already exists or not.
// TODO: remove CJS loader from here as well.
module = CJSModule._cache[modulePath];
if (module && module.loaded) {
if (module?.loaded) {
const exports = module.exports;
return new ModuleWrap(url, undefined, ['default'], function() {
this.setExport('default', exports);

View File

@ -17,24 +17,15 @@ let embedderOptions;
// complete so that we don't accidentally include runtime-dependent
// states into a runtime-independent snapshot.
function getCLIOptionsFromBinding() {
if (!optionsDict) {
optionsDict = getCLIOptionsValues();
}
return optionsDict;
return optionsDict ??= getCLIOptionsValues();
}
function getCLIOptionsInfoFromBinding() {
if (!cliInfo) {
cliInfo = getCLIOptionsInfo();
}
return cliInfo;
return cliInfo ??= getCLIOptionsInfo();
}
function getEmbedderOptions() {
if (!embedderOptions) {
embedderOptions = getEmbedderOptionsFromBinding();
}
return embedderOptions;
return embedderOptions ??= getEmbedderOptionsFromBinding();
}
function refreshOptions() {
@ -42,8 +33,7 @@ function refreshOptions() {
}
function getOptionValue(optionName) {
const optionsDict = getCLIOptionsFromBinding();
return optionsDict[optionName];
return getCLIOptionsFromBinding()[optionName];
}
function getAllowUnauthorized() {

View File

@ -261,7 +261,7 @@ function InterfaceConstructor(input, output, completer, terminal) {
function onkeypress(s, key) {
self[kTtyWrite](s, key);
if (key && key.sequence) {
if (key?.sequence) {
// If the key.sequence is half of a surrogate pair
// (>= 0xd800 and <= 0xdfff), refresh the line so
// the character is displayed appropriately.
@ -345,7 +345,7 @@ class Interface extends InterfaceConstructor {
super(input, output, completer, terminal);
}
get columns() {
if (this.output && this.output.columns) return this.output.columns;
if (this.output?.columns) return this.output.columns;
return Infinity;
}

View File

@ -269,7 +269,7 @@ class SourceMap {
ArrayPrototypePush(sources, url);
this.#sources[url] = true;
if (map.sourcesContent && map.sourcesContent[i])
if (map.sourcesContent?.[i])
this.#sourceContentByURL[url] = map.sourcesContent[i];
}

View File

@ -26,10 +26,10 @@ function from(Readable, iterable, opts) {
}
let isAsync;
if (iterable && iterable[SymbolAsyncIterator]) {
if (iterable?.[SymbolAsyncIterator]) {
isAsync = true;
iterator = iterable[SymbolAsyncIterator]();
} else if (iterable && iterable[SymbolIterator]) {
} else if (iterable?.[SymbolIterator]) {
isAsync = false;
iterator = iterable[SymbolIterator]();
} else {

View File

@ -453,7 +453,7 @@ function pipe(src, dst, finish, finishOnlyHandleError, { end }) {
if (
err &&
err.code === 'ERR_STREAM_PREMATURE_CLOSE' &&
(rState && rState.ended && !rState.errored && !rState.errorEmitted)
(rState?.ended && !rState.errored && !rState.errorEmitted)
) {
// Some readable streams will emit 'close' before 'end'. However, since
// this is on the readable side 'end' should still be emitted if the

View File

@ -266,10 +266,10 @@ function ReadableState(options, stream, isDuplex) {
// Object stream flag. Used to make read(n) ignore n and to
// make all the buffer merging and length checks go away.
if (options && options.objectMode)
if (options?.objectMode)
this[kState] |= kObjectMode;
if (isDuplex && options && options.readableObjectMode)
if (isDuplex && options?.readableObjectMode)
this[kState] |= kObjectMode;
// The point at which it stops calling _read() to fill the buffer
@ -305,7 +305,7 @@ function ReadableState(options, stream, isDuplex) {
// type: null | Writable | Set<Writable>.
this.awaitDrainWriters = null;
if (options && options.encoding) {
if (options?.encoding) {
this.decoder = new StringDecoder(options.encoding);
this.encoding = options.encoding;
}
@ -791,7 +791,7 @@ function onEofChunk(stream, state) {
const decoder = (state[kState] & kDecoder) !== 0 ? state[kDecoderValue] : null;
if (decoder) {
const chunk = decoder.end();
if (chunk && chunk.length) {
if (chunk?.length) {
state.buffer.push(chunk);
state.length += (state[kState] & kObjectMode) !== 0 ? 1 : chunk.length;
}
@ -1461,7 +1461,7 @@ ObjectDefineProperties(Readable.prototype, {
__proto__: null,
enumerable: false,
get: function() {
return this._readableState && this._readableState.buffer;
return this._readableState?.buffer;
},
},

View File

@ -290,8 +290,7 @@ function willEmitClose(stream) {
const state = wState || rState;
return (!state && isServerResponse(stream)) || !!(
state &&
state.autoDestroy &&
state?.autoDestroy &&
state.emitClose &&
state.closed === false
);

View File

@ -306,10 +306,10 @@ function WritableState(options, stream, isDuplex) {
// instead of a V8 slot per field.
this[kState] = kSync | kConstructed | kEmitClose | kAutoDestroy;
if (options && options.objectMode)
if (options?.objectMode)
this[kState] |= kObjectMode;
if (isDuplex && options && options.writableObjectMode)
if (isDuplex && options?.writableObjectMode)
this[kState] |= kObjectMode;
// The point at which write() starts returning false
@ -1068,7 +1068,7 @@ ObjectDefineProperties(Writable.prototype, {
__proto__: null,
get() {
const state = this._writableState;
return state && state.highWaterMark;
return state?.highWaterMark;
},
},
@ -1084,7 +1084,7 @@ ObjectDefineProperties(Writable.prototype, {
__proto__: null,
get() {
const state = this._writableState;
return state && state.length;
return state?.length;
},
},

View File

@ -1284,7 +1284,7 @@ function improveStack(stack, constructor, name, tag) {
if (constructor === null) {
const start = RegExpPrototypeExec(/^([A-Z][a-z_ A-Z0-9[\]()-]+)(?::|\n {4}at)/, stack) ||
RegExpPrototypeExec(/^([a-z_A-Z0-9-]*Error)$/, stack);
fallback = (start && start[1]) || '';
fallback = (start?.[1]) || '';
len = fallback.length;
fallback = fallback || 'Error';
}

View File

@ -747,8 +747,7 @@ Socket.prototype.resetAndDestroy = function() {
};
Socket.prototype.pause = function() {
if (this[kBuffer] && !this.connecting && this._handle &&
this._handle.reading) {
if (this[kBuffer] && !this.connecting && this._handle?.reading) {
this._handle.reading = false;
if (!this.destroyed) {
const err = this._handle.readStop();
@ -1218,7 +1217,7 @@ Socket.prototype.connect = function(...args) {
}
// If the parent is already connecting, do not attempt to connect again
if (this._parent && this._parent.connecting) {
if (this._parent?.connecting) {
return this;
}
@ -2184,7 +2183,7 @@ ObjectDefineProperty(Server.prototype, 'listening', {
});
Server.prototype.address = function() {
if (this._handle && this._handle.getsockname) {
if (this._handle?.getsockname) {
const out = {};
const err = this._handle.getsockname(out);
if (err) {

View File

@ -163,7 +163,7 @@ Interface.prototype.question[promisify.custom] = function question(query, option
options = kEmptyObject;
}
if (options.signal && options.signal.aborted) {
if (options.signal?.aborted) {
return PromiseReject(
new AbortError(undefined, { cause: options.signal.reason }));
}

View File

@ -908,8 +908,8 @@ function REPLServer(prompt,
StringPrototypeCharAt(trimmedCmd, 1) !== '.' &&
NumberIsNaN(NumberParseFloat(trimmedCmd))) {
const matches = RegExpPrototypeExec(/^\.([^\s]+)\s*(.*)$/, trimmedCmd);
const keyword = matches && matches[1];
const rest = matches && matches[2];
const keyword = matches?.[1];
const rest = matches?.[2];
if (ReflectApply(_parseREPLKeyword, self, [keyword, rest]) === true) {
return;
}
@ -1253,7 +1253,7 @@ function filteredOwnPropertyNames(obj) {
let isObjectPrototype = false;
if (ObjectGetPrototypeOf(obj) === null) {
const ctorDescriptor = ObjectGetOwnPropertyDescriptor(obj, 'constructor');
if (ctorDescriptor && ctorDescriptor.value) {
if (ctorDescriptor?.value) {
const ctorProto = ObjectGetPrototypeOf(ctorDescriptor.value);
isObjectPrototype = ctorProto && ObjectGetPrototypeOf(ctorProto) === obj;
}

View File

@ -182,7 +182,7 @@ ObjectDefineProperty(setTimeout, customPromisify, {
* @returns {void}
*/
function clearTimeout(timer) {
if (timer && timer._onTimeout) {
if (timer?._onTimeout) {
timer._onTimeout = null;
unenroll(timer);
return;

View File

@ -256,7 +256,7 @@ function ZlibBase(opts, mode, handle, { flush, finishFlush, fullFlush }) {
this._defaultFlushFlag = flush;
this._finishFlushFlag = finishFlush;
this._defaultFullFlushFlag = fullFlush;
this._info = opts && opts.info;
this._info = opts?.info;
this._maxOutputLength = maxOutputLength;
}
ObjectSetPrototypeOf(ZlibBase.prototype, Transform.prototype);

View File

@ -192,28 +192,28 @@ class ActivityCollector {
_before(uid) {
const h = this._getActivity(uid, 'before');
this._stamp(h, 'before');
this._maybeLog(uid, h && h.type, 'before');
this._maybeLog(uid, h?.type, 'before');
this.onbefore(uid);
}
_after(uid) {
const h = this._getActivity(uid, 'after');
this._stamp(h, 'after');
this._maybeLog(uid, h && h.type, 'after');
this._maybeLog(uid, h?.type, 'after');
this.onafter(uid);
}
_destroy(uid) {
const h = this._getActivity(uid, 'destroy');
this._stamp(h, 'destroy');
this._maybeLog(uid, h && h.type, 'destroy');
this._maybeLog(uid, h?.type, 'destroy');
this.ondestroy(uid);
}
_promiseResolve(uid) {
const h = this._getActivity(uid, 'promiseResolve');
this._stamp(h, 'promiseResolve');
this._maybeLog(uid, h && h.type, 'promiseResolve');
this._maybeLog(uid, h?.type, 'promiseResolve');
this.onpromiseResolve(uid);
}

View File

@ -196,11 +196,11 @@ function writeDNSPacket(parsed) {
buffers.push(new Uint16Array([
parsed.id,
parsed.flags === undefined ? kStandardResponseFlags : parsed.flags,
parsed.questions && parsed.questions.length,
parsed.answers && parsed.answers.length,
parsed.authorityAnswers && parsed.authorityAnswers.length,
parsed.additionalRecords && parsed.additionalRecords.length,
parsed.flags ?? kStandardResponseFlags,
parsed.questions?.length,
parsed.answers?.length,
parsed.authorityAnswers?.length,
parsed.additionalRecords?.length,
]));
for (const q of parsed.questions) {

View File

@ -50,8 +50,7 @@ if (cluster.isWorker) {
checks.setupEvent = true;
settings = cluster.settings;
if (settings &&
settings.args && settings.args[0] === 'custom argument' &&
if (settings?.args && settings.args[0] === 'custom argument' &&
settings.silent === true &&
settings.exec === process.argv[1]) {
checks.settingsObject = true;

View File

@ -124,7 +124,7 @@ function checkResults(expected_results, results) {
const expected = expected_results[k];
assert.strictEqual(
actual, expected && expected.length ? expected[0] : expected,
actual, expected?.length ? expected[0] : expected,
`${expected[1] || ''} [expected: ${expected[0]} / actual: ${actual}]`);
}
}

View File

@ -111,7 +111,7 @@ function checkResults(expected_results, results) {
const expected = expected_results[k];
assert.strictEqual(
actual, expected && expected.length ? expected[0] : expected,
actual, expected?.length ? expected[0] : expected,
`${expected[1] || ''} [expected: ${expected[0]} / actual: ${actual}]`);
}
}

View File

@ -92,7 +92,7 @@ function createTestCmdLine(options) {
testCmd += `"${process.argv[0]}"`;
if (options && options.withAbortOnUncaughtException) {
if (options?.withAbortOnUncaughtException) {
testCmd += ' --abort-on-uncaught-exception';
}

View File

@ -0,0 +1,34 @@
'use strict';
const common = require('../common');
if ((!common.hasCrypto) || (!common.hasIntl)) {
common.skip('ESLint tests require crypto and Intl');
}
common.skipIfEslintMissing();
const RuleTester = require('../../tools/eslint/node_modules/eslint').RuleTester;
const rule = require('../../tools/eslint-rules/prefer-optional-chaining');
new RuleTester().run('prefer-optional-chaining', rule, {
valid: [
{
code: 'hello?.world',
options: []
},
],
invalid: [
{
code: 'hello && hello.world',
options: [],
errors: [{ message: 'Prefer optional chaining.' }],
output: 'hello?.world'
},
{
code: 'hello && hello.world && hello.world.foobar',
options: [],
errors: [{ message: 'Prefer optional chaining.' }],
output: 'hello?.world?.foobar'
},
]
});

View File

@ -15,7 +15,7 @@ function testFirstInPath(moduleName, isLocalModule) {
assertFunction(paths[0], '.');
paths = _module._resolveLookupPaths(moduleName, null);
assertFunction(paths && paths[0], '.');
assertFunction(paths?.[0], '.');
}
testFirstInPath('./lodash', true);

View File

@ -44,7 +44,7 @@ class FakeInput extends EventEmitter {
function isWarned(emitter) {
for (const name in emitter) {
const listeners = emitter[name];
if (listeners && listeners.warned) return true;
if (listeners?.warned) return true;
}
return false;
}

View File

@ -22,7 +22,7 @@ class FakeInput extends EventEmitter {
function isWarned(emitter) {
for (const name in emitter) {
const listeners = emitter[name];
if (listeners && listeners.warned) return true;
if (listeners?.warned) return true;
}
return false;
}

View File

@ -45,11 +45,11 @@ r.on('readable', function() {
do {
console.error(` > read(${READSIZE})`);
ret = r.read(READSIZE);
console.error(` < ${ret && ret.length} (${rs.length} remain)`);
console.error(` < ${ret?.length} (${rs.length} remain)`);
} while (ret && ret.length === READSIZE);
console.error('<< after read()',
ret && ret.length,
ret?.length,
rs.needReadable,
rs.length);
});

View File

@ -331,8 +331,8 @@ const tests = [
tests.forEach(function(test, i) {
const err = tls.checkServerIdentity(test.host, test.cert);
assert.strictEqual(err && err.reason,
assert.strictEqual(err?.reason,
test.error,
`Test# ${i} failed: ${util.inspect(test)} \n` +
`${test.error} != ${(err && err.reason)}`);
`${test.error} != ${(err?.reason)}`);
});

View File

@ -38,7 +38,7 @@ async function test() {
const events = [];
let tracingComplete = false;
session.on('NodeTracing.dataCollected', (n) => {
assert.ok(n && n.params && n.params.value);
assert.ok(n?.params?.value);
events.push(...n.params.value); // append the events.
});
session.on('NodeTracing.tracingComplete', () => tracingComplete = true);

View File

@ -15,7 +15,7 @@ export function replaceLinks({ filename, linksMapper }) {
}
});
visit(tree, 'definition', (node) => {
const htmlUrl = fileHtmlUrls && fileHtmlUrls[node.identifier];
const htmlUrl = fileHtmlUrls?.[node.identifier];
if (htmlUrl && typeof htmlUrl === 'string') {
node.url = htmlUrl;

View File

@ -93,8 +93,7 @@ module.exports = {
}
function checkLiteral(node) {
const isTemplate = (node.type === 'TemplateLiteral' && node.quasis &&
node.quasis.length);
const isTemplate = (node.type === 'TemplateLiteral' && node.quasis?.length);
if (inRegExp &&
(isTemplate || (typeof node.value === 'string' && node.value.length))) {
let p = node.parent;
@ -108,7 +107,7 @@ module.exports = {
const quasis = node.quasis;
for (let i = 0; i < quasis.length; ++i) {
const el = quasis[i];
if (el.type === 'TemplateElement' && el.value && el.value.cooked)
if (el.type === 'TemplateElement' && el.value?.cooked)
regexpBuffer.push([el, el.value.cooked]);
}
} else {

View File

@ -0,0 +1,93 @@
'use strict';
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prefer optional chaining',
category: 'suggestion',
},
fixable: 'code',
schema: [],
},
create(context) {
const sourceCode = context.getSourceCode();
// Helper function: Checks if two nodes have identical tokens
function equalTokens(left, right) {
const leftTokens = sourceCode.getTokens(left);
const rightTokens = sourceCode.getTokens(right);
return (
leftTokens.length === rightTokens.length &&
leftTokens.every((tokenL, i) => tokenL.type === rightTokens[i].type && tokenL.value === rightTokens[i].value)
);
}
// Check if a sequence of two nodes forms a valid member expression chain
function isValidMemberExpressionPair(left, right) {
return (
right.type === 'MemberExpression' &&
equalTokens(left, right.object)
);
}
// Generate the optional chaining expression
function generateOptionalChaining(ops, first, last) {
return ops.slice(first, last + 1).reduce((chain, node, i) => {
const property = node.computed ?
`[${sourceCode.getText(node.property)}]` :
sourceCode.getText(node.property);
return i === 0 ? sourceCode.getText(node) : `${chain}?.${property}`;
}, '');
}
return {
'LogicalExpression[operator=&&]:exit'(node) {
// Early return if part of a larger `&&` chain
if (node.parent.type === 'LogicalExpression' && node.parent.operator === '&&') {
return;
}
const ops = [];
let current = node;
// Collect `&&` expressions into the ops array
while (current.type === 'LogicalExpression' && current.operator === '&&') {
ops.unshift(current.right); // Add right operand
current = current.left;
}
ops.unshift(current); // Add the leftmost operand
// Find the first valid member expression sequence
let first = 0;
while (first < ops.length - 1 && !isValidMemberExpressionPair(ops[first], ops[first + 1])) {
first++;
}
// No valid sequence found
if (first === ops.length - 1) return;
context.report({
node,
message: 'Prefer optional chaining.',
fix(fixer) {
// Find the last valid member expression sequence
let last = first;
while (last < ops.length - 1 && isValidMemberExpressionPair(ops[last], ops[last + 1])) {
last++;
}
return fixer.replaceTextRange(
[ops[first].range[0], ops[last].range[1]],
generateOptionalChaining(ops, first, last),
);
},
});
},
};
},
};

View File

@ -100,9 +100,5 @@ module.exports.inSkipBlock = function(node) {
};
function hasSkip(expression) {
return expression &&
expression.callee &&
(expression.callee.name === 'skip' ||
expression.callee.property &&
expression.callee.property.name === 'skip');
return expression?.callee?.name === 'skip' || expression?.callee?.property?.name === 'skip';
}