From 31e4a0d7ace1621b9f3bcb94f8e1da7de9aea67d Mon Sep 17 00:00:00 2001 From: Harshitha KP Date: Tue, 25 Feb 2020 02:46:16 -0500 Subject: [PATCH 001/108] src: Handle bad callback in asyc_wrap Align with the MaybeLocal<> API contract PR-URL: https://github.com/nodejs/node/pull/31946 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell --- src/async_wrap-inl.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/async_wrap-inl.h b/src/async_wrap-inl.h index e3e48666e4fbd6..03745081f3b09b 100644 --- a/src/async_wrap-inl.h +++ b/src/async_wrap-inl.h @@ -74,9 +74,8 @@ inline v8::MaybeLocal AsyncWrap::MakeCallback( if (!object()->Get(env()->context(), symbol).ToLocal(&cb_v)) return v8::MaybeLocal(); if (!cb_v->IsFunction()) { - // TODO(addaleax): We should throw an error here to fulfill the - // `MaybeLocal<>` API contract. - return v8::MaybeLocal(); + v8::Isolate* isolate = env()->isolate(); + return Undefined(isolate); } return MakeCallback(cb_v.As(), argc, argv); } From 89987b3a9f253f10d0edcf0100d23628a69a30cb Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 29 Feb 2020 15:14:38 -0800 Subject: [PATCH 002/108] test: remove common.expectsInternalAssertion Remove convenience function for internal assertions. It is only used once. Signed-off-by: Rich Trott PR-URL: https://github.com/nodejs/node/pull/32057 Reviewed-By: Luigi Pinca Reviewed-By: Anto Aravinth --- test/common/index.js | 14 -------------- test/parallel/test-internal-errors.js | 11 +++++++---- test/sequential/test-fs-watch.js | 8 ++++++-- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/test/common/index.js b/test/common/index.js index 653de4685ca7a0..28ce841c48cc3f 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -547,19 +547,6 @@ function expectsError(validator, exact) { }, exact); } -const suffix = 'This is caused by either a bug in Node.js ' + - 'or incorrect usage of Node.js internals.\n' + - 'Please open an issue with this stack trace at ' + - 'https://github.com/nodejs/node/issues\n'; - -function expectsInternalAssertion(fn, message) { - assert.throws(fn, { - message: `${message}\n${suffix}`, - name: 'Error', - code: 'ERR_INTERNAL_ASSERTION' - }); -} - function skipIfInspectorDisabled() { if (!process.features.inspector) { skip('V8 inspector is disabled'); @@ -680,7 +667,6 @@ const common = { createZeroFilledFile, disableCrashOnUnhandledRejection, expectsError, - expectsInternalAssertion, expectWarning, getArrayBufferViews, getBufferSources, diff --git a/test/parallel/test-internal-errors.js b/test/parallel/test-internal-errors.js index fbb8a0a86a3852..7bcc7dcc330c2c 100644 --- a/test/parallel/test-internal-errors.js +++ b/test/parallel/test-internal-errors.js @@ -1,6 +1,6 @@ // Flags: --expose-internals 'use strict'; -const common = require('../common'); +require('../common'); const { hijackStdout, restoreStdout, @@ -50,10 +50,13 @@ errors.E('TEST_ERROR_2', (a, b) => `${a} ${b}`, Error); } { - common.expectsInternalAssertion( + assert.throws( () => new errors.codes.TEST_ERROR_1(), - 'Code: TEST_ERROR_1; The provided arguments ' + - 'length (0) does not match the required ones (1).' + { + message: /^Code: TEST_ERROR_1; The provided arguments length \(0\) does not match the required ones \(1\)\./, + name: 'Error', + code: 'ERR_INTERNAL_ASSERTION' + } ); } diff --git a/test/sequential/test-fs-watch.js b/test/sequential/test-fs-watch.js index 031e92c61c0b3c..ff0a52c89c6790 100644 --- a/test/sequential/test-fs-watch.js +++ b/test/sequential/test-fs-watch.js @@ -117,14 +117,18 @@ tmpdir.refresh(); // https://github.com/joyent/node/issues/6690 { let oldhandle; - common.expectsInternalAssertion( + assert.throws( () => { const w = fs.watch(__filename, common.mustNotCall()); oldhandle = w._handle; w._handle = { close: w._handle.close }; w.close(); }, - 'handle must be a FSEvent' + { + message: /^handle must be a FSEvent/, + name: 'Error', + code: 'ERR_INTERNAL_ASSERTION', + } ); oldhandle.close(); // clean up } From 83e9a3ea59123d2d13f0884ba8c5f2c9f338f0d2 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 29 Feb 2020 16:19:17 -0800 Subject: [PATCH 003/108] test: add coverage for FSWatcher exception Cover an previously uncovered exception possible in the internal start function for FSWatcher. Signed-off-by: Rich Trott PR-URL: https://github.com/nodejs/node/pull/32057 Reviewed-By: Luigi Pinca Reviewed-By: Anto Aravinth --- test/sequential/test-fs-watch.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/sequential/test-fs-watch.js b/test/sequential/test-fs-watch.js index ff0a52c89c6790..8c543a2a17290a 100644 --- a/test/sequential/test-fs-watch.js +++ b/test/sequential/test-fs-watch.js @@ -125,9 +125,31 @@ tmpdir.refresh(); w.close(); }, { + name: 'Error', + code: 'ERR_INTERNAL_ASSERTION', message: /^handle must be a FSEvent/, + } + ); + oldhandle.close(); // clean up +} + +{ + let oldhandle; + assert.throws( + () => { + const w = fs.watch(__filename, common.mustNotCall()); + oldhandle = w._handle; + const protoSymbols = + Object.getOwnPropertySymbols(Object.getPrototypeOf(w)); + const kFSWatchStart = + protoSymbols.find((val) => val.toString() === 'Symbol(kFSWatchStart)'); + w._handle = {}; + w[kFSWatchStart](); + }, + { name: 'Error', code: 'ERR_INTERNAL_ASSERTION', + message: /^handle must be a FSEvent/, } ); oldhandle.close(); // clean up From a727b133438f8e76febf68f4c6d73414b7945f13 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Thu, 13 Feb 2020 12:05:18 +0100 Subject: [PATCH 004/108] crypto: make update(buf, enc) ignore encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the cipher/decipher/hash/hmac update() methods ignore the input encoding when the input is a buffer. This is the documented behavior but some inputs were rejected, notably when the specified encoding is 'hex' and the buffer has an odd length (because a _string_ with an odd length is never a valid hex string.) The sign/verify update() methods work okay because they use different validation logic. Fixes: https://github.com/nodejs/node/issues/31751 PR-URL: https://github.com/nodejs/node/pull/31766 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Richard Lau Reviewed-By: Luigi Pinca Reviewed-By: Tobias Nießen --- lib/internal/crypto/cipher.js | 6 +++--- lib/internal/crypto/hash.js | 14 +++++-------- test/parallel/test-crypto-update-encoding.js | 22 ++++++++++++++++++++ 3 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 test/parallel/test-crypto-update-encoding.js diff --git a/lib/internal/crypto/cipher.js b/lib/internal/crypto/cipher.js index add56eae680ced..80b0c0e9dab382 100644 --- a/lib/internal/crypto/cipher.js +++ b/lib/internal/crypto/cipher.js @@ -151,13 +151,13 @@ Cipher.prototype.update = function update(data, inputEncoding, outputEncoding) { inputEncoding = inputEncoding || encoding; outputEncoding = outputEncoding || encoding; - if (typeof data !== 'string' && !isArrayBufferView(data)) { + if (typeof data === 'string') { + validateEncoding(data, inputEncoding); + } else if (!isArrayBufferView(data)) { throw new ERR_INVALID_ARG_TYPE( 'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data); } - validateEncoding(data, inputEncoding); - const ret = this[kHandle].update(data, inputEncoding); if (outputEncoding && outputEncoding !== 'buffer') { diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js index dca0ba767f6e29..1cf0188da2f35e 100644 --- a/lib/internal/crypto/hash.js +++ b/lib/internal/crypto/hash.js @@ -78,17 +78,13 @@ Hash.prototype.update = function update(data, encoding) { if (state[kFinalized]) throw new ERR_CRYPTO_HASH_FINALIZED(); - if (typeof data !== 'string' && !isArrayBufferView(data)) { - throw new ERR_INVALID_ARG_TYPE('data', - ['string', - 'Buffer', - 'TypedArray', - 'DataView'], - data); + if (typeof data === 'string') { + validateEncoding(data, encoding); + } else if (!isArrayBufferView(data)) { + throw new ERR_INVALID_ARG_TYPE( + 'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data); } - validateEncoding(data, encoding); - if (!this[kHandle].update(data, encoding)) throw new ERR_CRYPTO_HASH_UPDATE_FAILED(); return this; diff --git a/test/parallel/test-crypto-update-encoding.js b/test/parallel/test-crypto-update-encoding.js new file mode 100644 index 00000000000000..e1e6d029aa5e30 --- /dev/null +++ b/test/parallel/test-crypto-update-encoding.js @@ -0,0 +1,22 @@ +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const crypto = require('crypto'); + +const zeros = Buffer.alloc; +const key = zeros(16); +const iv = zeros(16); + +const cipher = () => crypto.createCipheriv('aes-128-cbc', key, iv); +const decipher = () => crypto.createDecipheriv('aes-128-cbc', key, iv); +const hash = () => crypto.createSign('sha256'); +const hmac = () => crypto.createHmac('sha256', key); +const sign = () => crypto.createSign('sha256'); +const verify = () => crypto.createVerify('sha256'); + +for (const f of [cipher, decipher, hash, hmac, sign, verify]) + for (const n of [15, 16]) + f().update(zeros(n), 'hex'); // Should ignore inputEncoding. From 4ac1ce1071ede979956ab4654d40d31291b99f3d Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 3 Mar 2020 11:18:45 -0800 Subject: [PATCH 005/108] src: introduce node_sockaddr Introduce the SocketAddress utility class. The QUIC implementation makes extensive use of this for handling of socket addresses. It was separated out to make it generically reusable throughout core Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/32070 Reviewed-By: David Carlier Reviewed-By: Richard Lau Reviewed-By: Anna Henningsen Reviewed-By: Denys Otrishko --- node.gyp | 4 + src/node_sockaddr-inl.h | 170 +++++++++++++++++++++++++++++++++++ src/node_sockaddr.cc | 95 ++++++++++++++++++++ src/node_sockaddr.h | 122 +++++++++++++++++++++++++ src/udp_wrap.cc | 9 +- src/udp_wrap.h | 14 +-- test/cctest/test_sockaddr.cc | 57 ++++++++++++ 7 files changed, 460 insertions(+), 11 deletions(-) create mode 100644 src/node_sockaddr-inl.h create mode 100644 src/node_sockaddr.cc create mode 100644 src/node_sockaddr.h create mode 100644 test/cctest/test_sockaddr.cc diff --git a/node.gyp b/node.gyp index 9c4057aa417a02..d540903c777c69 100644 --- a/node.gyp +++ b/node.gyp @@ -597,6 +597,7 @@ 'src/node_process_methods.cc', 'src/node_process_object.cc', 'src/node_serdes.cc', + 'src/node_sockaddr.cc', 'src/node_stat_watcher.cc', 'src/node_symbols.cc', 'src/node_task_queue.cc', @@ -684,6 +685,8 @@ 'src/node_process.h', 'src/node_revert.h', 'src/node_root_certs.h', + 'src/node_sockaddr.h', + 'src/node_sockaddr-inl.h', 'src/node_stat_watcher.h', 'src/node_union_bytes.h', 'src/node_url.h', @@ -1150,6 +1153,7 @@ 'test/cctest/test_linked_binding.cc', 'test/cctest/test_per_process.cc', 'test/cctest/test_platform.cc', + 'test/cctest/test_sockaddr.cc', 'test/cctest/test_traced_value.cc', 'test/cctest/test_util.cc', 'test/cctest/test_url.cc', diff --git a/src/node_sockaddr-inl.h b/src/node_sockaddr-inl.h new file mode 100644 index 00000000000000..a9d0ed061a126c --- /dev/null +++ b/src/node_sockaddr-inl.h @@ -0,0 +1,170 @@ +#ifndef SRC_NODE_SOCKADDR_INL_H_ +#define SRC_NODE_SOCKADDR_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node.h" +#include "node_internals.h" +#include "node_sockaddr.h" +#include "util-inl.h" + +#include + +namespace node { + +static constexpr uint32_t kLabelMask = 0xFFFFF; + +inline void hash_combine(size_t* seed) { } + +template +inline void hash_combine(size_t* seed, const T& value, Args... rest) { + *seed ^= std::hash{}(value) + 0x9e3779b9 + (*seed << 6) + (*seed >> 2); + hash_combine(seed, rest...); +} + +bool SocketAddress::is_numeric_host(const char* hostname) { + return is_numeric_host(hostname, AF_INET) || + is_numeric_host(hostname, AF_INET6); +} + +bool SocketAddress::is_numeric_host(const char* hostname, int family) { + in6_addr dst; + return inet_pton(family, hostname, &dst) == 1; +} + +int SocketAddress::GetPort(const sockaddr* addr) { + CHECK(addr->sa_family == AF_INET || addr->sa_family == AF_INET6); + return ntohs(addr->sa_family == AF_INET ? + reinterpret_cast(addr)->sin_port : + reinterpret_cast(addr)->sin6_port); +} + +int SocketAddress::GetPort(const sockaddr_storage* addr) { + return GetPort(reinterpret_cast(addr)); +} + +std::string SocketAddress::GetAddress(const sockaddr* addr) { + CHECK(addr->sa_family == AF_INET || addr->sa_family == AF_INET6); + char host[INET6_ADDRSTRLEN]; + const void* src = addr->sa_family == AF_INET ? + static_cast( + &(reinterpret_cast(addr)->sin_addr)) : + static_cast( + &(reinterpret_cast(addr)->sin6_addr)); + uv_inet_ntop(addr->sa_family, src, host, INET6_ADDRSTRLEN); + return std::string(host); +} + +std::string SocketAddress::GetAddress(const sockaddr_storage* addr) { + return GetAddress(reinterpret_cast(addr)); +} + +size_t SocketAddress::GetLength(const sockaddr* addr) { + return addr->sa_family == AF_INET ? + sizeof(sockaddr_in) : sizeof(sockaddr_in6); +} + +size_t SocketAddress::GetLength(const sockaddr_storage* addr) { + return GetLength(reinterpret_cast(addr)); +} + +SocketAddress::SocketAddress(const sockaddr* addr) { + memcpy(&address_, addr, GetLength(addr)); +} + +SocketAddress::SocketAddress(const SocketAddress& addr) { + memcpy(&address_, &addr.address_, addr.length()); +} + +SocketAddress& SocketAddress::operator=(const sockaddr* addr) { + memcpy(&address_, addr, GetLength(addr)); + return *this; +} + +SocketAddress& SocketAddress::operator=(const SocketAddress& addr) { + memcpy(&address_, &addr.address_, addr.length()); + return *this; +} + +const sockaddr& SocketAddress::operator*() const { + return *this->data(); +} + +const sockaddr* SocketAddress::operator->() const { + return this->data(); +} + +size_t SocketAddress::length() const { + return GetLength(&address_); +} + +const sockaddr* SocketAddress::data() const { + return reinterpret_cast(&address_); +} + +const uint8_t* SocketAddress::raw() const { + return reinterpret_cast(&address_); +} + +sockaddr* SocketAddress::storage() { + return reinterpret_cast(&address_); +} + +int SocketAddress::family() const { + return address_.ss_family; +} + +std::string SocketAddress::address() const { + return GetAddress(&address_); +} + +int SocketAddress::port() const { + return GetPort(&address_); +} + +uint32_t SocketAddress::flow_label() const { + if (family() != AF_INET6) + return 0; + const sockaddr_in6* in = reinterpret_cast(data()); + return in->sin6_flowinfo; +} + +void SocketAddress::set_flow_label(uint32_t label) { + if (family() != AF_INET6) + return; + CHECK_LE(label, kLabelMask); + sockaddr_in6* in = reinterpret_cast(&address_); + in->sin6_flowinfo = label; +} + +std::string SocketAddress::ToString() const { + if (family() != AF_INET && family() != AF_INET6) return ""; + return (family() == AF_INET6 ? + std::string("[") + address() + "]:" : + address() + ":") + + std::to_string(port()); +} + +void SocketAddress::Update(uint8_t* data, size_t len) { + CHECK_LE(len, sizeof(address_)); + memcpy(&address_, data, len); +} + +v8::Local SocketAddress::ToJS( + Environment* env, + v8::Local info) const { + return AddressToJS(env, data(), info); +} + +bool SocketAddress::operator==(const SocketAddress& other) const { + if (family() != other.family()) return false; + return memcmp(raw(), other.raw(), length()) == 0; +} + +bool SocketAddress::operator!=(const SocketAddress& other) const { + return !(*this == other); +} +} // namespace node + +#endif // NODE_WANT_INTERNALS +#endif // SRC_NODE_SOCKADDR_INL_H_ diff --git a/src/node_sockaddr.cc b/src/node_sockaddr.cc new file mode 100644 index 00000000000000..74fe123529abbd --- /dev/null +++ b/src/node_sockaddr.cc @@ -0,0 +1,95 @@ +#include "node_sockaddr-inl.h" // NOLINT(build/include) +#include "uv.h" + +namespace node { + +namespace { +template +SocketAddress FromUVHandle(F fn, const T& handle) { + SocketAddress addr; + int len = sizeof(sockaddr_storage); + if (fn(&handle, addr.storage(), &len) == 0) + CHECK_EQ(static_cast(len), addr.length()); + else + addr.storage()->sa_family = 0; + return addr; +} +} // namespace + +bool SocketAddress::ToSockAddr( + int32_t family, + const char* host, + uint32_t port, + sockaddr_storage* addr) { + switch (family) { + case AF_INET: + return uv_ip4_addr( + host, + port, + reinterpret_cast(addr)) == 0; + case AF_INET6: + return uv_ip6_addr( + host, + port, + reinterpret_cast(addr)) == 0; + default: + UNREACHABLE(); + } +} + +bool SocketAddress::New( + const char* host, + uint32_t port, + SocketAddress* addr) { + return New(AF_INET, host, port, addr) || New(AF_INET6, host, port, addr); +} + +bool SocketAddress::New( + int32_t family, + const char* host, + uint32_t port, + SocketAddress* addr) { + return ToSockAddr(family, host, port, + reinterpret_cast(addr->storage())); +} + +size_t SocketAddress::Hash::operator()(const SocketAddress& addr) const { + size_t hash = 0; + switch (addr.family()) { + case AF_INET: { + const sockaddr_in* ipv4 = + reinterpret_cast(addr.raw()); + hash_combine(&hash, ipv4->sin_port, ipv4->sin_addr.s_addr); + break; + } + case AF_INET6: { + const sockaddr_in6* ipv6 = + reinterpret_cast(addr.raw()); + const uint64_t* a = + reinterpret_cast(&ipv6->sin6_addr); + hash_combine(&hash, ipv6->sin6_port, a[0], a[1]); + break; + } + default: + UNREACHABLE(); + } + return hash; +} + +SocketAddress SocketAddress::FromSockName(const uv_tcp_t& handle) { + return FromUVHandle(uv_tcp_getsockname, handle); +} + +SocketAddress SocketAddress::FromSockName(const uv_udp_t& handle) { + return FromUVHandle(uv_udp_getsockname, handle); +} + +SocketAddress SocketAddress::FromPeerName(const uv_tcp_t& handle) { + return FromUVHandle(uv_tcp_getpeername, handle); +} + +SocketAddress SocketAddress::FromPeerName(const uv_udp_t& handle) { + return FromUVHandle(uv_udp_getpeername, handle); +} + +} // namespace node diff --git a/src/node_sockaddr.h b/src/node_sockaddr.h new file mode 100644 index 00000000000000..2e3ae09ce3bb8d --- /dev/null +++ b/src/node_sockaddr.h @@ -0,0 +1,122 @@ +#ifndef SRC_NODE_SOCKADDR_H_ +#define SRC_NODE_SOCKADDR_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "env.h" +#include "memory_tracker.h" +#include "node.h" +#include "uv.h" +#include "v8.h" + +#include +#include + +namespace node { + +class SocketAddress : public MemoryRetainer { + public: + struct Hash { + size_t operator()(const SocketAddress& addr) const; + }; + + inline bool operator==(const SocketAddress& other) const; + inline bool operator!=(const SocketAddress& other) const; + + inline static bool is_numeric_host(const char* hostname); + inline static bool is_numeric_host(const char* hostname, int family); + + // Returns true if converting {family, host, port} to *addr succeeded. + static bool ToSockAddr( + int32_t family, + const char* host, + uint32_t port, + sockaddr_storage* addr); + + // Returns true if converting {family, host, port} to *addr succeeded. + static bool New( + int32_t family, + const char* host, + uint32_t port, + SocketAddress* addr); + + static bool New( + const char* host, + uint32_t port, + SocketAddress* addr); + + // Returns the port for an IPv4 or IPv6 address. + inline static int GetPort(const sockaddr* addr); + inline static int GetPort(const sockaddr_storage* addr); + + // Returns the numeric host as a string for an IPv4 or IPv6 address. + inline static std::string GetAddress(const sockaddr* addr); + inline static std::string GetAddress(const sockaddr_storage* addr); + + // Returns the struct length for an IPv4, IPv6 or UNIX domain. + inline static size_t GetLength(const sockaddr* addr); + inline static size_t GetLength(const sockaddr_storage* addr); + + SocketAddress() = default; + + inline explicit SocketAddress(const sockaddr* addr); + inline SocketAddress(const SocketAddress& addr); + inline SocketAddress& operator=(const sockaddr* other); + inline SocketAddress& operator=(const SocketAddress& other); + + inline const sockaddr& operator*() const; + inline const sockaddr* operator->() const; + + inline const sockaddr* data() const; + inline const uint8_t* raw() const; + inline sockaddr* storage(); + inline size_t length() const; + + inline int family() const; + inline std::string address() const; + inline int port() const; + + // If the SocketAddress is an IPv6 address, returns the + // current value of the IPv6 flow label, if set. Otherwise + // returns 0. + inline uint32_t flow_label() const; + + // If the SocketAddress is an IPv6 address, sets the + // current value of the IPv6 flow label. If not an + // IPv6 address, set_flow_label is a non-op. It + // is important to note that the flow label, + // while represented as an uint32_t, the flow + // label is strictly limited to 20 bits, and + // this will assert if any value larger than + // 20-bits is specified. + inline void set_flow_label(uint32_t label = 0); + + inline void Update(uint8_t* data, size_t len); + + static SocketAddress FromSockName(const uv_udp_t& handle); + static SocketAddress FromSockName(const uv_tcp_t& handle); + static SocketAddress FromPeerName(const uv_udp_t& handle); + static SocketAddress FromPeerName(const uv_tcp_t& handle); + + inline v8::Local ToJS( + Environment* env, + v8::Local obj = v8::Local()) const; + + inline std::string ToString() const; + + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(SocketAddress) + SET_SELF_SIZE(SocketAddress) + + template + using Map = std::unordered_map; + + private: + sockaddr_storage address_; +}; + +} // namespace node + +#endif // NOE_WANT_INTERNALS + +#endif // SRC_NODE_SOCKADDR_H_ diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index 702449daae90a8..277eb6b81bae32 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -22,6 +22,7 @@ #include "udp_wrap.h" #include "env-inl.h" #include "node_buffer.h" +#include "node_sockaddr-inl.h" #include "handle_wrap.h" #include "req_wrap-inl.h" #include "util-inl.h" @@ -628,12 +629,12 @@ AsyncWrap* UDPWrap::GetAsyncWrap() { return this; } -int UDPWrap::GetPeerName(sockaddr* name, int* namelen) { - return uv_udp_getpeername(&handle_, name, namelen); +SocketAddress UDPWrap::GetPeerName() { + return SocketAddress::FromPeerName(handle_); } -int UDPWrap::GetSockName(sockaddr* name, int* namelen) { - return uv_udp_getsockname(&handle_, name, namelen); +SocketAddress UDPWrap::GetSockName() { + return SocketAddress::FromSockName(handle_); } void UDPWrapBase::RecvStart(const FunctionCallbackInfo& args) { diff --git a/src/udp_wrap.h b/src/udp_wrap.h index 7afd9784b0ac9e..6fed1d2dfea810 100644 --- a/src/udp_wrap.h +++ b/src/udp_wrap.h @@ -26,6 +26,7 @@ #include "handle_wrap.h" #include "req_wrap.h" +#include "node_sockaddr.h" #include "uv.h" #include "v8.h" @@ -95,11 +96,8 @@ class UDPWrapBase { size_t nbufs, const sockaddr* addr) = 0; - // Stores the sockaddr for the peer in `name`. - virtual int GetPeerName(sockaddr* name, int* namelen) = 0; - - // Stores the sockaddr for the local socket in `name`. - virtual int GetSockName(sockaddr* name, int* namelen) = 0; + virtual SocketAddress GetPeerName() = 0; + virtual SocketAddress GetSockName() = 0; // Returns an AsyncWrap object with the same lifetime as this object. virtual AsyncWrap* GetAsyncWrap() = 0; @@ -168,8 +166,10 @@ class UDPWrap final : public HandleWrap, ssize_t Send(uv_buf_t* bufs, size_t nbufs, const sockaddr* addr) override; - int GetPeerName(sockaddr* name, int* namelen) override; - int GetSockName(sockaddr* name, int* namelen) override; + + SocketAddress GetPeerName() override; + SocketAddress GetSockName() override; + AsyncWrap* GetAsyncWrap() override; static v8::MaybeLocal Instantiate(Environment* env, diff --git a/test/cctest/test_sockaddr.cc b/test/cctest/test_sockaddr.cc new file mode 100644 index 00000000000000..8c23463f11d2d1 --- /dev/null +++ b/test/cctest/test_sockaddr.cc @@ -0,0 +1,57 @@ +#include "node_sockaddr-inl.h" +#include "gtest/gtest.h" + +using node::SocketAddress; + +TEST(SocketAddress, SocketAddress) { + CHECK(SocketAddress::is_numeric_host("123.123.123.123")); + CHECK(!SocketAddress::is_numeric_host("localhost")); + + sockaddr_storage storage; + sockaddr_storage storage2; + SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage); + SocketAddress::ToSockAddr(AF_INET, "1.1.1.1", 80, &storage2); + + SocketAddress addr(reinterpret_cast(&storage)); + SocketAddress addr2(reinterpret_cast(&storage2)); + + CHECK_EQ(addr.length(), sizeof(sockaddr_in)); + CHECK_EQ(addr.family(), AF_INET); + CHECK_EQ(addr.address(), "123.123.123.123"); + CHECK_EQ(addr.port(), 443); + + addr.set_flow_label(12345); + CHECK_EQ(addr.flow_label(), 0); + + CHECK_NE(addr, addr2); + CHECK_EQ(addr, addr); + + CHECK_EQ(SocketAddress::Hash()(addr), SocketAddress::Hash()(addr)); + CHECK_NE(SocketAddress::Hash()(addr), SocketAddress::Hash()(addr2)); + + addr.Update(reinterpret_cast(&storage2), sizeof(sockaddr_in)); + CHECK_EQ(addr.length(), sizeof(sockaddr_in)); + CHECK_EQ(addr.family(), AF_INET); + CHECK_EQ(addr.address(), "1.1.1.1"); + CHECK_EQ(addr.port(), 80); + + SocketAddress::Map map; + map[addr]++; + map[addr]++; + CHECK_EQ(map[addr], 2); +} + +TEST(SocketAddress, SocketAddressIPv6) { + sockaddr_storage storage; + SocketAddress::ToSockAddr(AF_INET6, "::1", 443, &storage); + + SocketAddress addr(reinterpret_cast(&storage)); + + CHECK_EQ(addr.length(), sizeof(sockaddr_in6)); + CHECK_EQ(addr.family(), AF_INET6); + CHECK_EQ(addr.address(), "::1"); + CHECK_EQ(addr.port(), 443); + + addr.set_flow_label(12345); + CHECK_EQ(addr.flow_label(), 12345); +} From 5f12595e00772da0c1a4877c7a3c65b5f26374c7 Mon Sep 17 00:00:00 2001 From: Yael Hermon Date: Sat, 29 Feb 2020 20:27:18 +0200 Subject: [PATCH 006/108] doc: update email address in authors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit update my personal email PR-URL: https://github.com/nodejs/node/pull/32026 Reviewed-By: Rich Trott Reviewed-By: Michaël Zasso Reviewed-By: Matheus Marchini Reviewed-By: Anna Henningsen Reviewed-By: Ruben Bridgewater Reviewed-By: Michael Dawson Reviewed-By: Luigi Pinca --- .mailmap | 1 + AUTHORS | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index e140392b723514..15962df50bdb94 100644 --- a/.mailmap +++ b/.mailmap @@ -423,6 +423,7 @@ Wilson Lin Wyatt Preul geek Xavier J Ortiz xiaoyu <306766053@qq.com> Poker <306766053@qq.com> +Yael Hermon Yazhong Liu Yazhong Liu Yazhong Liu Yorkie Yazhong Liu Yorkie diff --git a/AUTHORS b/AUTHORS index adb8aef88a44d5..539f311a0dcb9f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -2636,7 +2636,7 @@ Charles Samborski zhmushan yoshimoto koki Ilarion Halushka -Yael Hermon +Yael Hermon Mitch Hankins Mikko Rantanen wenjun ye <1728914873@qq.com> From c0ba6ec5603b6415300e06b6959b04d8cb0ab229 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Mon, 2 Mar 2020 22:43:02 -0800 Subject: [PATCH 007/108] meta: move thefourtheye to TSC Emeritus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit thefourtheye has a considerable history of contributions to Node.js. They have not been active much of late, and the TSC Charter has a section about activity indicating that moving to Emeritus at this time is the thing to do. Thanks for all you've done to make Node.js fantastic, thefourtheye, and hope to see you around again soon! PR-URL: https://github.com/nodejs/node/pull/32059 Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina Reviewed-By: Colin Ihrig Reviewed-By: Anatoli Papirovski Reviewed-By: Joyee Cheung Reviewed-By: Luigi Pinca Reviewed-By: Michael Dawson Reviewed-By: Tobias Nießen Reviewed-By: Сковорода Никита Андреевич --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0576cd5132e6a8..b073c034b7eb3a 100644 --- a/README.md +++ b/README.md @@ -181,8 +181,6 @@ For information about the governance of the Node.js project, see **Sam Roberts** <vieuxtech@gmail.com> * [targos](https://github.com/targos) - **Michaël Zasso** <targos@protonmail.com> (he/him) -* [thefourtheye](https://github.com/thefourtheye) - -**Sakthipriyan Vairamani** <thechargingvolcano@gmail.com> (he/him) * [tniessen](https://github.com/tniessen) - **Tobias Nießen** <tniessen@tnie.de> * [Trott](https://github.com/Trott) - @@ -222,6 +220,8 @@ For information about the governance of the Node.js project, see **Rod Vagg** <r@va.gg> * [shigeki](https://github.com/shigeki) - **Shigeki Ohtsu** <ohtsu@ohtsu.org> (he/him) +* [thefourtheye](https://github.com/thefourtheye) - +**Sakthipriyan Vairamani** <thechargingvolcano@gmail.com> (he/him) * [TimothyGu](https://github.com/TimothyGu) - **Tiancheng "Timothy" Gu** <timothygu99@gmail.com> (he/him) * [trevnorris](https://github.com/trevnorris) - From 31ec44302a8367103f274e193dd4573b363f88d8 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 16 Feb 2020 03:50:01 -0500 Subject: [PATCH 008/108] benchmark: remove problematic tls params These very small values can cause crashes/exceptions to occur on some systems because most time is spent in V8 GC or in parts of node core that are not being tested (e.g. streams). PR-URL: https://github.com/nodejs/node/pull/31816 Reviewed-By: James M Snell --- benchmark/tls/secure-pair.js | 2 +- benchmark/tls/throughput.js | 2 +- test/benchmark/test-benchmark-tls.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/benchmark/tls/secure-pair.js b/benchmark/tls/secure-pair.js index c52f4cbf918a1d..bb7933d837f999 100644 --- a/benchmark/tls/secure-pair.js +++ b/benchmark/tls/secure-pair.js @@ -3,7 +3,7 @@ const common = require('../common.js'); const bench = common.createBenchmark(main, { dur: [5], securing: ['SecurePair', 'TLSSocket', 'clear'], - size: [2, 100, 1024, 1024 * 1024] + size: [100, 1024, 1024 * 1024] }); const fixtures = require('../../test/common/fixtures'); diff --git a/benchmark/tls/throughput.js b/benchmark/tls/throughput.js index 727d20e460008d..3ea84aa84ef453 100644 --- a/benchmark/tls/throughput.js +++ b/benchmark/tls/throughput.js @@ -3,7 +3,7 @@ const common = require('../common.js'); const bench = common.createBenchmark(main, { dur: [5], type: ['buf', 'asc', 'utf'], - size: [2, 1024, 1024 * 1024, 4 * 1024 * 1024, 16 * 1024 * 1024] + size: [100, 1024, 1024 * 1024, 4 * 1024 * 1024, 16 * 1024 * 1024] }); const fixtures = require('../../test/common/fixtures'); diff --git a/test/benchmark/test-benchmark-tls.js b/test/benchmark/test-benchmark-tls.js index 40c14af8302bdb..264fa08d396be7 100644 --- a/test/benchmark/test-benchmark-tls.js +++ b/test/benchmark/test-benchmark-tls.js @@ -19,9 +19,9 @@ runBenchmark('tls', 'concurrency=1', 'dur=0.1', 'n=1', - 'size=2', + 'size=1024', 'securing=SecurePair', - 'type=asc' + 'type=buf' ], { NODEJS_BENCHMARK_ZERO_ALLOWED: 1, From 50094de274309b417cfdd4b1b3bdb550a6b60594 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Tue, 3 Mar 2020 21:23:59 -0800 Subject: [PATCH 009/108] doc: remove em dashes Our documentation uses em dashes inconsistently. They are treated inconsistently typographically too. (For example, they are sometimes surrounded by spaces and sometimes not.) They are also often confused with ordinary hyphens such as in the CHANGELOG, where they are inadvertently mixed together in a single list. The difference is not obvious in the raw markdown but is very noticeable when rendered, appearing to be a typographical error (which it in fact is). The em dash is never needed. There are always alternatives. Remove em dashes entirely. PR-URL: https://github.com/nodejs/node/pull/32080 Reviewed-By: Richard Lau Reviewed-By: James M Snell Reviewed-By: Luigi Pinca --- CHANGELOG.md | 26 +++++++++++----------- doc/api/addons.md | 2 +- doc/api/errors.md | 6 ++--- doc/api/http.md | 6 ++--- doc/api/http2.md | 2 +- doc/api/modules.md | 2 +- doc/api/net.md | 2 +- doc/api/path.md | 4 ++-- doc/api/process.md | 2 +- doc/api/url.md | 2 +- doc/guides/backporting-to-release-lines.md | 2 +- doc/guides/contributing/issues.md | 4 ++-- doc/guides/doc-style-guide.md | 8 ++----- doc/guides/maintaining-icu.md | 2 +- tools/doc/versions.js | 2 +- 15 files changed, 34 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd5600535009ec..d564422733ed06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,19 +2,19 @@ Select a Node.js version below to view the changelog history: -* [Node.js 13](doc/changelogs/CHANGELOG_V13.md) - **Current** -* [Node.js 12](doc/changelogs/CHANGELOG_V12.md) - **Long Term Support** -* [Node.js 11](doc/changelogs/CHANGELOG_V11.md) - End-of-Life -* [Node.js 10](doc/changelogs/CHANGELOG_V10.md) — Long Term Support -* [Node.js 9](doc/changelogs/CHANGELOG_V9.md) — End-of-Life -* [Node.js 8](doc/changelogs/CHANGELOG_V8.md) — Long Term Support -* [Node.js 7](doc/changelogs/CHANGELOG_V7.md) — End-of-Life -* [Node.js 6](doc/changelogs/CHANGELOG_V6.md) — End-of-Life -* [Node.js 5](doc/changelogs/CHANGELOG_V5.md) — End-of-Life -* [Node.js 4](doc/changelogs/CHANGELOG_V4.md) — End-of-Life -* [io.js](doc/changelogs/CHANGELOG_IOJS.md) — End-of-Life -* [Node.js 0.12](doc/changelogs/CHANGELOG_V012.md) — End-of-Life -* [Node.js 0.10](doc/changelogs/CHANGELOG_V010.md) — End-of-Life +* [Node.js 13](doc/changelogs/CHANGELOG_V13.md) **Current** +* [Node.js 12](doc/changelogs/CHANGELOG_V12.md) **Long Term Support** +* [Node.js 11](doc/changelogs/CHANGELOG_V11.md) End-of-Life +* [Node.js 10](doc/changelogs/CHANGELOG_V10.md) Long Term Support +* [Node.js 9](doc/changelogs/CHANGELOG_V9.md) End-of-Life +* [Node.js 8](doc/changelogs/CHANGELOG_V8.md) End-of-Life +* [Node.js 7](doc/changelogs/CHANGELOG_V7.md) End-of-Life +* [Node.js 6](doc/changelogs/CHANGELOG_V6.md) End-of-Life +* [Node.js 5](doc/changelogs/CHANGELOG_V5.md) End-of-Life +* [Node.js 4](doc/changelogs/CHANGELOG_V4.md) End-of-Life +* [io.js](doc/changelogs/CHANGELOG_IOJS.md) End-of-Life +* [Node.js 0.12](doc/changelogs/CHANGELOG_V012.md) End-of-Life +* [Node.js 0.10](doc/changelogs/CHANGELOG_V010.md) End-of-Life * [Archive](doc/changelogs/CHANGELOG_ARCHIVE.md) Please use the following table to find the changelog for a specific Node.js diff --git a/doc/api/addons.md b/doc/api/addons.md index 6b77338348ed96..93f99afbeee7e7 100644 --- a/doc/api/addons.md +++ b/doc/api/addons.md @@ -313,7 +313,7 @@ require('./build/Release/addon'); Once the source code has been written, it must be compiled into the binary `addon.node` file. To do so, create a file called `binding.gyp` in the top-level of the project describing the build configuration of the module -using a JSON-like format. This file is used by [node-gyp][] — a tool written +using a JSON-like format. This file is used by [node-gyp][], a tool written specifically to compile Node.js Addons. ```json diff --git a/doc/api/errors.md b/doc/api/errors.md index 93d9f909193f2b..204a8a26989b16 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -533,14 +533,14 @@ program. For a comprehensive list, see the [`errno`(3) man page][]. `ulimit -n 2048` in the same shell that will run the Node.js process. * `ENOENT` (No such file or directory): Commonly raised by [`fs`][] operations - to indicate that a component of the specified pathname does not exist — no + to indicate that a component of the specified pathname does not exist. No entity (file or directory) could be found by the given path. * `ENOTDIR` (Not a directory): A component of the given pathname existed, but was not a directory as expected. Commonly raised by [`fs.readdir`][]. * `ENOTEMPTY` (Directory not empty): A directory with entries was the target - of an operation that requires an empty directory — usually [`fs.unlink`][]. + of an operation that requires an empty directory, usually [`fs.unlink`][]. * `ENOTFOUND` (DNS lookup failed): Indicates a DNS failure of either `EAI_NODATA` or `EAI_NONAME`. This is not a standard POSIX error. @@ -555,7 +555,7 @@ program. For a comprehensive list, see the [`errno`(3) man page][]. * `ETIMEDOUT` (Operation timed out): A connect or send request failed because the connected party did not properly respond after a period of time. Usually - encountered by [`http`][] or [`net`][] — often a sign that a `socket.end()` + encountered by [`http`][] or [`net`][]. Often a sign that a `socket.end()` was not properly called. ## Class: `TypeError` diff --git a/doc/api/http.md b/doc/api/http.md index cf1e5d76bf42bc..f6aa93ecedc2eb 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -9,7 +9,7 @@ To use the HTTP server and client one must `require('http')`. The HTTP interfaces in Node.js are designed to support many features of the protocol which have been traditionally difficult to use. In particular, large, possibly chunk-encoded, messages. The interface is -careful to never buffer entire requests or responses — the +careful to never buffer entire requests or responses, so the user is able to stream data. HTTP message headers are represented by an object like this: @@ -882,7 +882,7 @@ added: v0.1.29 Sends a chunk of the body. By calling this method many times, a request body can be sent to a -server — in that case it is suggested to use the +server. In that case, it is suggested to use the `['Transfer-Encoding', 'chunked']` header line when creating the request. @@ -1214,7 +1214,7 @@ added: v0.1.17 * Extends: {Stream} -This object is created internally by an HTTP server — not by the user. It is +This object is created internally by an HTTP server, not by the user. It is passed as the second parameter to the [`'request'`][] event. ### Event: `'close'` diff --git a/doc/api/http2.md b/doc/api/http2.md index 11f41224c4e1d3..69252121f71daf 100644 --- a/doc/api/http2.md +++ b/doc/api/http2.md @@ -3008,7 +3008,7 @@ added: v8.4.0 * Extends: {Stream} -This object is created internally by an HTTP server — not by the user. It is +This object is created internally by an HTTP server, not by the user. It is passed as the second parameter to the [`'request'`][] event. #### Event: `'close'` diff --git a/doc/api/modules.md b/doc/api/modules.md index d69a2a2f1516d7..4c91c27f52bc33 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -945,7 +945,7 @@ added: v0.3.7 * {Object} Provides general utility methods when interacting with instances of -`Module` — the `module` variable often seen in file modules. Accessed +`Module`, the `module` variable often seen in file modules. Accessed via `require('module')`. ### `module.builtinModules` diff --git a/doc/api/net.md b/doc/api/net.md index 84bda5e4e9bfed..ea0b9139fd33f1 100644 --- a/doc/api/net.md +++ b/doc/api/net.md @@ -939,7 +939,7 @@ added: v0.1.90 * Returns: {boolean} Sends data on the socket. The second parameter specifies the encoding in the -case of a string — it defaults to UTF8 encoding. +case of a string. It defaults to UTF8 encoding. Returns `true` if the entire data was flushed successfully to the kernel buffer. Returns `false` if all or part of the data was queued in user memory. diff --git a/doc/api/path.md b/doc/api/path.md index 477ef2cab09e82..c05a5c29efb12f 100644 --- a/doc/api/path.md +++ b/doc/api/path.md @@ -389,7 +389,7 @@ path.parse('/home/user/dir/file.txt'); │ root │ │ name │ ext │ " / home/user/dir / file .txt " └──────┴──────────────┴──────┴─────┘ -(all spaces in the "" line should be ignored — they are purely for formatting) +(All spaces in the "" line should be ignored. They are purely for formatting.) ``` On Windows: @@ -411,7 +411,7 @@ path.parse('C:\\path\\dir\\file.txt'); │ root │ │ name │ ext │ " C:\ path\dir \ file .txt " └──────┴──────────────┴──────┴─────┘ -(all spaces in the "" line should be ignored — they are purely for formatting) +(All spaces in the "" line should be ignored. They are purely for formatting.) ``` A [`TypeError`][] is thrown if `path` is not a string. diff --git a/doc/api/process.md b/doc/api/process.md index 9850711d596e6f..8d4d9c428d2f0f 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -190,7 +190,7 @@ rejection handler. There is no notion of a top level for a `Promise` chain at which rejections can always be handled. Being inherently asynchronous in nature, a `Promise` -rejection can be handled at a future point in time — possibly much later than +rejection can be handled at a future point in time, possibly much later than the event loop turn it takes for the `'unhandledRejection'` event to be emitted. Another way of stating this is that, unlike in synchronous code where there is diff --git a/doc/api/url.md b/doc/api/url.md index a198b699cd31d8..49f56509fe41e9 100644 --- a/doc/api/url.md +++ b/doc/api/url.md @@ -47,7 +47,7 @@ WHATWG URL's `origin` property includes `protocol` and `host`, but not ├─────────────┴─────────────────────┴────────────────────────┴──────────┴────────────────┴───────┤ │ href │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ -(all spaces in the "" line should be ignored — they are purely for formatting) +(All spaces in the "" line should be ignored. They are purely for formatting.) ``` Parsing the URL string using the WHATWG API: diff --git a/doc/guides/backporting-to-release-lines.md b/doc/guides/backporting-to-release-lines.md index 4a4657d0815a21..55fbcb7e5bc344 100644 --- a/doc/guides/backporting-to-release-lines.md +++ b/doc/guides/backporting-to-release-lines.md @@ -75,7 +75,7 @@ replace that with the staging branch for the targeted release line. 9. Open a pull request: 1. Be sure to target the `v10.x-staging` branch in the pull request. 1. Include the backport target in the pull request title in the following - format — `[v10.x backport] `. + format: `[v10.x backport] `. Example: `[v10.x backport] process: improve performance of nextTick` 1. Check the checkbox labeled "Allow edits from maintainers". 1. In the description add a reference to the original PR. diff --git a/doc/guides/contributing/issues.md b/doc/guides/contributing/issues.md index 054bbd7b2775f9..31a47c1cd33c16 100644 --- a/doc/guides/contributing/issues.md +++ b/doc/guides/contributing/issues.md @@ -89,8 +89,8 @@ around it. Some contributors may have differing opinions about the issue, including whether the behavior being seen is a bug or a feature. This discussion is part of the process and should be kept focused, helpful, and professional. -Short, clipped responses—that provide neither additional context nor supporting -detail—are not helpful or professional. To many, such responses are simply +Short, clipped responses that provide neither additional context nor supporting +detail are not helpful or professional. To many, such responses are simply annoying and unfriendly. Contributors are encouraged to help one another make forward progress as much diff --git a/doc/guides/doc-style-guide.md b/doc/guides/doc-style-guide.md index f0221991b79be2..7ee2cac4ec366f 100644 --- a/doc/guides/doc-style-guide.md +++ b/doc/guides/doc-style-guide.md @@ -25,12 +25,10 @@ * Outside of the wrapping element if the wrapping element contains only a fragment of a clause. * Documents must start with a level-one heading. -* Prefer affixing links to inlining links — prefer `[a link][]` to - `[a link](http://example.com)`. +* Prefer affixing links (`[a link][]`) to inlining links + (`[a link](http://example.com)`). * When documenting APIs, update the YAML comment associated with the API as appropriate. This is especially true when introducing or deprecating an API. -* Use [Em dashes][] ("—" or `Option+Shift+"-"` on macOS) surrounded by spaces, - as per [The New York Times Manual of Style and Usage][]. * For code blocks: * Use language aware fences. ("```js") * Code need not be complete. Treat code blocks as an illustration or aid to @@ -67,9 +65,7 @@ See also API documentation structure overview in [doctools README][]. -[Em dashes]: https://en.wikipedia.org/wiki/Dash#Em_dash [Javascript type]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Data_structures_and_types [serial commas]: https://en.wikipedia.org/wiki/Serial_comma -[The New York Times Manual of Style and Usage]: https://en.wikipedia.org/wiki/The_New_York_Times_Manual_of_Style_and_Usage [plugin]: https://editorconfig.org/#download [doctools README]: ../tools/doc/README.md diff --git a/doc/guides/maintaining-icu.md b/doc/guides/maintaining-icu.md index 4add40b7fa2046..85f13c91298423 100644 --- a/doc/guides/maintaining-icu.md +++ b/doc/guides/maintaining-icu.md @@ -218,7 +218,7 @@ following the steps above in the prior section of this document ought to be repeatable without concern for overriding a patch. 2. **Verifiability.** Given the number of files modified in an ICU PR, -a floating patch could easily be missed — or dropped altogether next time +a floating patch could easily be missed or dropped altogether next time something is landed. 3. **Compatibility.** There are a number of ways that ICU can be loaded into diff --git a/tools/doc/versions.js b/tools/doc/versions.js index 782ce90ee2c616..bff6ac3617fbde 100644 --- a/tools/doc/versions.js +++ b/tools/doc/versions.js @@ -61,7 +61,7 @@ module.exports = { } } const ltsRE = /Long Term Support/i; - const versionRE = /\* \[Node\.js ([0-9.]+)\][^-—]+[-—]\s*(.*)\r?\n/g; + const versionRE = /\* \[Node\.js ([0-9.]+)\]\S+ (.*)\r?\n/g; _versions = []; let match; while ((match = versionRE.exec(changelog)) != null) { From a03777096ea495821f1c234c947fd52b4cdba50c Mon Sep 17 00:00:00 2001 From: James M Snell Date: Mon, 2 Mar 2020 17:27:06 -0800 Subject: [PATCH 010/108] src,http2: introduce node_http_common The nghttp2 and nghttp3 (used in the QUIC implementation) share nearly identical structs for header handling. However, they differ enough that they need to be handled slightly different in each case. This PR includes some elements introduced in the QUIC PR separated out to make them independently reviewable, and updates the http2 implementation to use the shared utilities. Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/32069 Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina --- node.gyp | 2 + src/env.h | 2 +- src/node_http2.cc | 235 +++++-------- src/node_http2.h | 378 +++----------------- src/node_http_common-inl.h | 181 ++++++++++ src/node_http_common.h | 527 ++++++++++++++++++++++++++++ test/parallel/test-http2-binding.js | 11 +- 7 files changed, 862 insertions(+), 474 deletions(-) create mode 100644 src/node_http_common-inl.h create mode 100644 src/node_http_common.h diff --git a/node.gyp b/node.gyp index d540903c777c69..86c953ca9a1736 100644 --- a/node.gyp +++ b/node.gyp @@ -664,6 +664,8 @@ 'src/node_errors.h', 'src/node_file.h', 'src/node_file-inl.h', + 'src/node_http_common.h', + 'src/node_http_common-inl.h', 'src/node_http2.h', 'src/node_http2_state.h', 'src/node_i18n.h', diff --git a/src/env.h b/src/env.h index 3c6aeb0f4ce6eb..0693c93161eb92 100644 --- a/src/env.h +++ b/src/env.h @@ -510,7 +510,7 @@ class IsolateData : public MemoryRetainer { #undef VS #undef VP - std::unordered_map> http2_static_strs; + std::unordered_map> http_static_strs; inline v8::Isolate* isolate() const; IsolateData(const IsolateData&) = delete; IsolateData& operator=(const IsolateData&) = delete; diff --git a/src/node_http2.cc b/src/node_http2.cc index 3de43f5f3af2cb..b8eaf9af8ab0cd 100644 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -5,6 +5,7 @@ #include "node_buffer.h" #include "node_http2.h" #include "node_http2_state.h" +#include "node_http_common-inl.h" #include "node_mem-inl.h" #include "node_perf.h" #include "node_revert.h" @@ -355,66 +356,6 @@ const char* Http2Session::TypeName() const { } } -// The Headers class initializes a proper array of nghttp2_nv structs -// containing the header name value pairs. -Headers::Headers(Isolate* isolate, - Local context, - Local headers) { - Local header_string = headers->Get(context, 0).ToLocalChecked(); - Local header_count = headers->Get(context, 1).ToLocalChecked(); - count_ = header_count.As()->Value(); - int header_string_len = header_string.As()->Length(); - - if (count_ == 0) { - CHECK_EQ(header_string_len, 0); - return; - } - - // Allocate a single buffer with count_ nghttp2_nv structs, followed - // by the raw header data as passed from JS. This looks like: - // | possible padding | nghttp2_nv | nghttp2_nv | ... | header contents | - buf_.AllocateSufficientStorage((alignof(nghttp2_nv) - 1) + - count_ * sizeof(nghttp2_nv) + - header_string_len); - // Make sure the start address is aligned appropriately for an nghttp2_nv*. - char* start = reinterpret_cast( - RoundUp(reinterpret_cast(*buf_), alignof(nghttp2_nv))); - char* header_contents = start + (count_ * sizeof(nghttp2_nv)); - nghttp2_nv* const nva = reinterpret_cast(start); - - CHECK_LE(header_contents + header_string_len, *buf_ + buf_.length()); - CHECK_EQ(header_string.As()->WriteOneByte( - isolate, - reinterpret_cast(header_contents), - 0, - header_string_len, - String::NO_NULL_TERMINATION), - header_string_len); - - size_t n = 0; - char* p; - for (p = header_contents; p < header_contents + header_string_len; n++) { - if (n >= count_) { - // This can happen if a passed header contained a null byte. In that - // case, just provide nghttp2 with an invalid header to make it reject - // the headers list. - static uint8_t zero = '\0'; - nva[0].name = nva[0].value = &zero; - nva[0].namelen = nva[0].valuelen = 1; - count_ = 1; - return; - } - - nva[n].flags = NGHTTP2_NV_FLAG_NONE; - nva[n].name = reinterpret_cast(p); - nva[n].namelen = strlen(p); - p += nva[n].namelen + 1; - nva[n].value = reinterpret_cast(p); - nva[n].valuelen = strlen(p); - p += nva[n].valuelen + 1; - } -} - Origins::Origins(Isolate* isolate, Local context, Local origin_string, @@ -537,8 +478,8 @@ Http2Session::Http2Session(Environment* env, uint32_t maxHeaderPairs = opts.GetMaxHeaderPairs(); max_header_pairs_ = type == NGHTTP2_SESSION_SERVER - ? std::max(maxHeaderPairs, 4U) // minimum # of request headers - : std::max(maxHeaderPairs, 1U); // minimum # of response headers + ? GetServerMaxHeaderPairs(maxHeaderPairs) + : GetClientMaxHeaderPairs(maxHeaderPairs); max_outstanding_pings_ = opts.GetMaxOutstandingPings(); max_outstanding_settings_ = opts.GetMaxOutstandingSettings(); @@ -1248,34 +1189,30 @@ void Http2Session::HandleHeadersFrame(const nghttp2_frame* frame) { if (stream->IsDestroyed()) return; - std::vector headers(stream->move_headers()); - DecrementCurrentSessionMemory(stream->current_headers_length_); - stream->current_headers_length_ = 0; - - // The headers are passed in above as a queue of nghttp2_header structs. + // The headers are stored as a vector of Http2Header instances. // The following converts that into a JS array with the structure: // [name1, value1, name2, value2, name3, value3, name3, value4] and so on. // That array is passed up to the JS layer and converted into an Object form // like {name1: value1, name2: value2, name3: [value3, value4]}. We do it // this way for performance reasons (it's faster to generate and pass an // array than it is to generate and pass the object). - size_t headers_size = headers.size(); - std::vector> headers_v(headers_size * 2); - for (size_t i = 0; i < headers_size; ++i) { - const nghttp2_header& item = headers[i]; - // The header name and value are passed as external one-byte strings - headers_v[i * 2] = - ExternalHeader::New(this, item.name).ToLocalChecked(); - headers_v[i * 2 + 1] = - ExternalHeader::New(this, item.value).ToLocalChecked(); - } + + std::vector> headers_v(stream->headers_count() * 2); + stream->TransferHeaders([&](const Http2Header& header, size_t i) { + headers_v[i * 2] = header.GetName(this).ToLocalChecked(); + headers_v[i * 2 + 1] = header.GetValue(this).ToLocalChecked(); + }); + CHECK_EQ(stream->headers_count(), 0); + + DecrementCurrentSessionMemory(stream->current_headers_length_); + stream->current_headers_length_ = 0; Local args[5] = { stream->object(), Integer::New(isolate, id), Integer::New(isolate, stream->headers_category()), Integer::New(isolate, frame->hd.flags), - Array::New(isolate, headers_v.data(), headers_size * 2)}; + Array::New(isolate, headers_v.data(), headers_v.size())}; MakeCallback(env()->http2session_on_headers_function(), arraysize(args), args); } @@ -1760,15 +1697,20 @@ int Http2Session::OnSendData( // Creates a new Http2Stream and submits a new http2 request. Http2Stream* Http2Session::SubmitRequest( nghttp2_priority_spec* prispec, - nghttp2_nv* nva, - size_t len, + const Http2Headers& headers, int32_t* ret, int options) { Debug(this, "submitting request"); Http2Scope h2scope(this); Http2Stream* stream = nullptr; Http2Stream::Provider::Stream prov(options); - *ret = nghttp2_submit_request(session_, prispec, nva, len, *prov, nullptr); + *ret = nghttp2_submit_request( + session_, + prispec, + headers.data(), + headers.length(), + *prov, + nullptr); CHECK_NE(*ret, NGHTTP2_ERR_NOMEM); if (LIKELY(*ret > 0)) stream = Http2Stream::New(this, *ret, NGHTTP2_HCAT_HEADERS, options); @@ -1917,13 +1859,7 @@ Http2Stream::Http2Stream(Http2Session* session, session->AddStream(this); } - Http2Stream::~Http2Stream() { - for (nghttp2_header& header : current_headers_) { - nghttp2_rcbuf_decref(header.name); - nghttp2_rcbuf_decref(header.value); - } - if (!session_) return; Debug(this, "tearing down stream"); @@ -2025,7 +1961,7 @@ void Http2Stream::Destroy() { // Initiates a response on the Http2Stream using data provided via the // StreamBase Streams API. -int Http2Stream::SubmitResponse(nghttp2_nv* nva, size_t len, int options) { +int Http2Stream::SubmitResponse(const Http2Headers& headers, int options) { CHECK(!this->IsDestroyed()); Http2Scope h2scope(this); Debug(this, "submitting response"); @@ -2036,21 +1972,30 @@ int Http2Stream::SubmitResponse(nghttp2_nv* nva, size_t len, int options) { options |= STREAM_OPTION_EMPTY_PAYLOAD; Http2Stream::Provider::Stream prov(this, options); - int ret = nghttp2_submit_response(**session_, id_, nva, len, *prov); + int ret = nghttp2_submit_response( + **session_, + id_, + headers.data(), + headers.length(), + *prov); CHECK_NE(ret, NGHTTP2_ERR_NOMEM); return ret; } // Submit informational headers for a stream. -int Http2Stream::SubmitInfo(nghttp2_nv* nva, size_t len) { +int Http2Stream::SubmitInfo(const Http2Headers& headers) { CHECK(!this->IsDestroyed()); Http2Scope h2scope(this); - Debug(this, "sending %d informational headers", len); - int ret = nghttp2_submit_headers(**session_, - NGHTTP2_FLAG_NONE, - id_, nullptr, - nva, len, nullptr); + Debug(this, "sending %d informational headers", headers.length()); + int ret = nghttp2_submit_headers( + **session_, + NGHTTP2_FLAG_NONE, + id_, + nullptr, + headers.data(), + headers.length(), + nullptr); CHECK_NE(ret, NGHTTP2_ERR_NOMEM); return ret; } @@ -2067,19 +2012,23 @@ void Http2Stream::OnTrailers() { } // Submit informational headers for a stream. -int Http2Stream::SubmitTrailers(nghttp2_nv* nva, size_t len) { +int Http2Stream::SubmitTrailers(const Http2Headers& headers) { CHECK(!this->IsDestroyed()); Http2Scope h2scope(this); - Debug(this, "sending %d trailers", len); + Debug(this, "sending %d trailers", headers.length()); int ret; // Sending an empty trailers frame poses problems in Safari, Edge & IE. // Instead we can just send an empty data frame with NGHTTP2_FLAG_END_STREAM // to indicate that the stream is ready to be closed. - if (len == 0) { + if (headers.length() == 0) { Http2Stream::Provider::Stream prov(this, 0); ret = nghttp2_submit_data(**session_, NGHTTP2_FLAG_END_STREAM, id_, *prov); } else { - ret = nghttp2_submit_trailer(**session_, id_, nva, len); + ret = nghttp2_submit_trailer( + **session_, + id_, + headers.data(), + headers.length()); } CHECK_NE(ret, NGHTTP2_ERR_NOMEM); return ret; @@ -2128,15 +2077,19 @@ void Http2Stream::FlushRstStream() { // Submit a push promise and create the associated Http2Stream if successful. -Http2Stream* Http2Stream::SubmitPushPromise(nghttp2_nv* nva, - size_t len, +Http2Stream* Http2Stream::SubmitPushPromise(const Http2Headers& headers, int32_t* ret, int options) { CHECK(!this->IsDestroyed()); Http2Scope h2scope(this); Debug(this, "sending push promise"); - *ret = nghttp2_submit_push_promise(**session_, NGHTTP2_FLAG_NONE, - id_, nva, len, nullptr); + *ret = nghttp2_submit_push_promise( + **session_, + NGHTTP2_FLAG_NONE, + id_, + headers.data(), + headers.length(), + nullptr); CHECK_NE(*ret, NGHTTP2_ERR_NOMEM); Http2Stream* stream = nullptr; if (*ret > 0) { @@ -2220,12 +2173,12 @@ bool Http2Stream::AddHeader(nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags) { CHECK(!this->IsDestroyed()); - if (this->statistics_.first_header == 0) - this->statistics_.first_header = uv_hrtime(); - size_t name_len = nghttp2_rcbuf_get_buf(name).len; - if (name_len == 0) return true; // Ignore headers with empty names. - size_t value_len = nghttp2_rcbuf_get_buf(value).len; - size_t length = name_len + value_len + 32; + + if (Http2RcBufferPointer::IsZeroLength(name)) + return true; // Ignore empty headers. + + Http2Header header(env(), name, value, flags); + size_t length = header.length() + 32; // A header can only be added if we have not exceeded the maximum number // of headers and the session has memory available for it. if (!session_->IsAvailableSessionMemory(length) || @@ -2233,13 +2186,12 @@ bool Http2Stream::AddHeader(nghttp2_rcbuf* name, current_headers_length_ + length > max_header_length_) { return false; } - nghttp2_header header; - header.name = name; - header.value = value; - header.flags = flags; - current_headers_.push_back(header); - nghttp2_rcbuf_incref(name); - nghttp2_rcbuf_incref(value); + + if (statistics_.first_header == 0) + statistics_.first_header = uv_hrtime(); + + current_headers_.push_back(std::move(header)); + current_headers_length_ += length; session_->IncrementCurrentSessionMemory(length); return true; @@ -2486,21 +2438,20 @@ void Http2Session::Request(const FunctionCallbackInfo& args) { Http2Session* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); Environment* env = session->env(); - Local context = env->context(); - Isolate* isolate = env->isolate(); Local headers = args[0].As(); - int options = args[1]->IntegerValue(context).ToChecked(); + int options = args[1]->IntegerValue(env->context()).ToChecked(); Http2Priority priority(env, args[2], args[3], args[4]); - Headers list(isolate, context, headers); - Debug(session, "request submitted"); int32_t ret = 0; Http2Stream* stream = - session->Http2Session::SubmitRequest(*priority, *list, list.length(), - &ret, options); + session->Http2Session::SubmitRequest( + *priority, + Http2Headers(env, headers), + &ret, + options); if (ret <= 0 || stream == nullptr) { Debug(session, "could not submit request: %s", nghttp2_strerror(ret)); @@ -2585,18 +2536,14 @@ void Http2Stream::RstStream(const FunctionCallbackInfo& args) { // outbound DATA frames. void Http2Stream::Respond(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Local context = env->context(); - Isolate* isolate = env->isolate(); Http2Stream* stream; ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); Local headers = args[0].As(); - int options = args[1]->IntegerValue(context).ToChecked(); - - Headers list(isolate, context, headers); + int options = args[1]->IntegerValue(env->context()).ToChecked(); args.GetReturnValue().Set( - stream->SubmitResponse(*list, list.length(), options)); + stream->SubmitResponse(Http2Headers(env, headers), options)); Debug(stream, "response submitted"); } @@ -2604,31 +2551,24 @@ void Http2Stream::Respond(const FunctionCallbackInfo& args) { // Submits informational headers on the Http2Stream void Http2Stream::Info(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Local context = env->context(); - Isolate* isolate = env->isolate(); Http2Stream* stream; ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); Local headers = args[0].As(); - Headers list(isolate, context, headers); - args.GetReturnValue().Set(stream->SubmitInfo(*list, list.length())); - Debug(stream, "%d informational headers sent", list.length()); + args.GetReturnValue().Set(stream->SubmitInfo(Http2Headers(env, headers))); } // Submits trailing headers on the Http2Stream void Http2Stream::Trailers(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Local context = env->context(); - Isolate* isolate = env->isolate(); Http2Stream* stream; ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); Local headers = args[0].As(); - Headers list(isolate, context, headers); - args.GetReturnValue().Set(stream->SubmitTrailers(*list, list.length())); - Debug(stream, "%d trailing headers sent", list.length()); + args.GetReturnValue().Set( + stream->SubmitTrailers(Http2Headers(env, headers))); } // Grab the numeric id of the Http2Stream @@ -2649,21 +2589,18 @@ void Http2Stream::Destroy(const FunctionCallbackInfo& args) { // Initiate a Push Promise and create the associated Http2Stream void Http2Stream::PushPromise(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Local context = env->context(); - Isolate* isolate = env->isolate(); Http2Stream* parent; ASSIGN_OR_RETURN_UNWRAP(&parent, args.Holder()); Local headers = args[0].As(); - int options = args[1]->IntegerValue(context).ToChecked(); - - Headers list(isolate, context, headers); + int options = args[1]->IntegerValue(env->context()).ToChecked(); Debug(parent, "creating push promise"); int32_t ret = 0; - Http2Stream* stream = parent->SubmitPushPromise(*list, list.length(), - &ret, options); + Http2Stream* stream = + parent->SubmitPushPromise(Http2Headers(env, headers), &ret, options); + if (ret <= 0 || stream == nullptr) { Debug(parent, "failed to create push stream: %d", ret); return args.GetReturnValue().Set(ret); @@ -2939,12 +2876,6 @@ void nghttp2_stream_write::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("buf", buf); } - -void nghttp2_header::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackFieldWithSize("name", nghttp2_rcbuf_get_buf(name).len); - tracker->TrackFieldWithSize("value", nghttp2_rcbuf_get_buf(value).len); -} - void SetCallbackFunctions(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 11); diff --git a/src/node_http2.h b/src/node_http2.h index d3e66dfba6894f..690a97027848c8 100644 --- a/src/node_http2.h +++ b/src/node_http2.h @@ -8,6 +8,7 @@ #include "nghttp2/nghttp2.h" #include "node_http2_state.h" +#include "node_http_common.h" #include "node_mem.h" #include "node_perf.h" #include "stream_base-inl.h" @@ -50,8 +51,36 @@ using performance::PerformanceEntry; #define MIN_MAX_FRAME_SIZE DEFAULT_SETTINGS_MAX_FRAME_SIZE #define MAX_INITIAL_WINDOW_SIZE 2147483647 -#define MAX_MAX_HEADER_LIST_SIZE 16777215u -#define DEFAULT_MAX_HEADER_LIST_PAIRS 128u +struct Http2HeadersTraits { + typedef nghttp2_nv nv_t; + static const uint8_t kNoneFlag = NGHTTP2_NV_FLAG_NONE; +}; + +struct Http2RcBufferPointerTraits { + typedef nghttp2_rcbuf rcbuf_t; + typedef nghttp2_vec vector_t; + + static void inc(rcbuf_t* buf) { + CHECK_NOT_NULL(buf); + nghttp2_rcbuf_incref(buf); + } + static void dec(rcbuf_t* buf) { + CHECK_NOT_NULL(buf); + nghttp2_rcbuf_decref(buf); + } + static vector_t get_vec(rcbuf_t* buf) { + CHECK_NOT_NULL(buf); + return nghttp2_rcbuf_get_buf(buf); + } + static bool is_static(const rcbuf_t* buf) { + CHECK_NOT_NULL(buf); + return nghttp2_rcbuf_is_static(buf); + } +}; + +using Http2Headers = NgHeaders; +using Http2RcBufferPointer = NgRcBufPointer; + enum nghttp2_session_type { NGHTTP2_SESSION_SERVER, @@ -96,224 +125,6 @@ struct nghttp2_stream_write : public MemoryRetainer { SET_SELF_SIZE(nghttp2_stream_write) }; -struct nghttp2_header : public MemoryRetainer { - nghttp2_rcbuf* name = nullptr; - nghttp2_rcbuf* value = nullptr; - uint8_t flags = 0; - - void MemoryInfo(MemoryTracker* tracker) const override; - SET_MEMORY_INFO_NAME(nghttp2_header) - SET_SELF_SIZE(nghttp2_header) -}; - - -// Unlike the HTTP/1 implementation, the HTTP/2 implementation is not limited -// to a fixed number of known supported HTTP methods. These constants, therefore -// are provided strictly as a convenience to users and are exposed via the -// require('http2').constants object. -#define HTTP_KNOWN_METHODS(V) \ - V(ACL, "ACL") \ - V(BASELINE_CONTROL, "BASELINE-CONTROL") \ - V(BIND, "BIND") \ - V(CHECKIN, "CHECKIN") \ - V(CHECKOUT, "CHECKOUT") \ - V(CONNECT, "CONNECT") \ - V(COPY, "COPY") \ - V(DELETE, "DELETE") \ - V(GET, "GET") \ - V(HEAD, "HEAD") \ - V(LABEL, "LABEL") \ - V(LINK, "LINK") \ - V(LOCK, "LOCK") \ - V(MERGE, "MERGE") \ - V(MKACTIVITY, "MKACTIVITY") \ - V(MKCALENDAR, "MKCALENDAR") \ - V(MKCOL, "MKCOL") \ - V(MKREDIRECTREF, "MKREDIRECTREF") \ - V(MKWORKSPACE, "MKWORKSPACE") \ - V(MOVE, "MOVE") \ - V(OPTIONS, "OPTIONS") \ - V(ORDERPATCH, "ORDERPATCH") \ - V(PATCH, "PATCH") \ - V(POST, "POST") \ - V(PRI, "PRI") \ - V(PROPFIND, "PROPFIND") \ - V(PROPPATCH, "PROPPATCH") \ - V(PUT, "PUT") \ - V(REBIND, "REBIND") \ - V(REPORT, "REPORT") \ - V(SEARCH, "SEARCH") \ - V(TRACE, "TRACE") \ - V(UNBIND, "UNBIND") \ - V(UNCHECKOUT, "UNCHECKOUT") \ - V(UNLINK, "UNLINK") \ - V(UNLOCK, "UNLOCK") \ - V(UPDATE, "UPDATE") \ - V(UPDATEREDIRECTREF, "UPDATEREDIRECTREF") \ - V(VERSION_CONTROL, "VERSION-CONTROL") - -// These are provided strictly as a convenience to users and are exposed via the -// require('http2').constants objects -#define HTTP_KNOWN_HEADERS(V) \ - V(STATUS, ":status") \ - V(METHOD, ":method") \ - V(AUTHORITY, ":authority") \ - V(SCHEME, ":scheme") \ - V(PATH, ":path") \ - V(PROTOCOL, ":protocol") \ - V(ACCEPT_CHARSET, "accept-charset") \ - V(ACCEPT_ENCODING, "accept-encoding") \ - V(ACCEPT_LANGUAGE, "accept-language") \ - V(ACCEPT_RANGES, "accept-ranges") \ - V(ACCEPT, "accept") \ - V(ACCESS_CONTROL_ALLOW_CREDENTIALS, "access-control-allow-credentials") \ - V(ACCESS_CONTROL_ALLOW_HEADERS, "access-control-allow-headers") \ - V(ACCESS_CONTROL_ALLOW_METHODS, "access-control-allow-methods") \ - V(ACCESS_CONTROL_ALLOW_ORIGIN, "access-control-allow-origin") \ - V(ACCESS_CONTROL_EXPOSE_HEADERS, "access-control-expose-headers") \ - V(ACCESS_CONTROL_MAX_AGE, "access-control-max-age") \ - V(ACCESS_CONTROL_REQUEST_HEADERS, "access-control-request-headers") \ - V(ACCESS_CONTROL_REQUEST_METHOD, "access-control-request-method") \ - V(AGE, "age") \ - V(ALLOW, "allow") \ - V(AUTHORIZATION, "authorization") \ - V(CACHE_CONTROL, "cache-control") \ - V(CONNECTION, "connection") \ - V(CONTENT_DISPOSITION, "content-disposition") \ - V(CONTENT_ENCODING, "content-encoding") \ - V(CONTENT_LANGUAGE, "content-language") \ - V(CONTENT_LENGTH, "content-length") \ - V(CONTENT_LOCATION, "content-location") \ - V(CONTENT_MD5, "content-md5") \ - V(CONTENT_RANGE, "content-range") \ - V(CONTENT_TYPE, "content-type") \ - V(COOKIE, "cookie") \ - V(DATE, "date") \ - V(DNT, "dnt") \ - V(ETAG, "etag") \ - V(EXPECT, "expect") \ - V(EXPIRES, "expires") \ - V(FORWARDED, "forwarded") \ - V(FROM, "from") \ - V(HOST, "host") \ - V(IF_MATCH, "if-match") \ - V(IF_MODIFIED_SINCE, "if-modified-since") \ - V(IF_NONE_MATCH, "if-none-match") \ - V(IF_RANGE, "if-range") \ - V(IF_UNMODIFIED_SINCE, "if-unmodified-since") \ - V(LAST_MODIFIED, "last-modified") \ - V(LINK, "link") \ - V(LOCATION, "location") \ - V(MAX_FORWARDS, "max-forwards") \ - V(PREFER, "prefer") \ - V(PROXY_AUTHENTICATE, "proxy-authenticate") \ - V(PROXY_AUTHORIZATION, "proxy-authorization") \ - V(RANGE, "range") \ - V(REFERER, "referer") \ - V(REFRESH, "refresh") \ - V(RETRY_AFTER, "retry-after") \ - V(SERVER, "server") \ - V(SET_COOKIE, "set-cookie") \ - V(STRICT_TRANSPORT_SECURITY, "strict-transport-security") \ - V(TRAILER, "trailer") \ - V(TRANSFER_ENCODING, "transfer-encoding") \ - V(TE, "te") \ - V(TK, "tk") \ - V(UPGRADE_INSECURE_REQUESTS, "upgrade-insecure-requests") \ - V(UPGRADE, "upgrade") \ - V(USER_AGENT, "user-agent") \ - V(VARY, "vary") \ - V(VIA, "via") \ - V(WARNING, "warning") \ - V(WWW_AUTHENTICATE, "www-authenticate") \ - V(X_CONTENT_TYPE_OPTIONS, "x-content-type-options") \ - V(X_FRAME_OPTIONS, "x-frame-options") \ - V(HTTP2_SETTINGS, "http2-settings") \ - V(KEEP_ALIVE, "keep-alive") \ - V(PROXY_CONNECTION, "proxy-connection") - -enum http_known_headers { - HTTP_KNOWN_HEADER_MIN, -#define V(name, value) HTTP_HEADER_##name, - HTTP_KNOWN_HEADERS(V) -#undef V - HTTP_KNOWN_HEADER_MAX -}; - -// While some of these codes are used within the HTTP/2 implementation in -// core, they are provided strictly as a convenience to users and are exposed -// via the require('http2').constants object. -#define HTTP_STATUS_CODES(V) \ - V(CONTINUE, 100) \ - V(SWITCHING_PROTOCOLS, 101) \ - V(PROCESSING, 102) \ - V(EARLY_HINTS, 103) \ - V(OK, 200) \ - V(CREATED, 201) \ - V(ACCEPTED, 202) \ - V(NON_AUTHORITATIVE_INFORMATION, 203) \ - V(NO_CONTENT, 204) \ - V(RESET_CONTENT, 205) \ - V(PARTIAL_CONTENT, 206) \ - V(MULTI_STATUS, 207) \ - V(ALREADY_REPORTED, 208) \ - V(IM_USED, 226) \ - V(MULTIPLE_CHOICES, 300) \ - V(MOVED_PERMANENTLY, 301) \ - V(FOUND, 302) \ - V(SEE_OTHER, 303) \ - V(NOT_MODIFIED, 304) \ - V(USE_PROXY, 305) \ - V(TEMPORARY_REDIRECT, 307) \ - V(PERMANENT_REDIRECT, 308) \ - V(BAD_REQUEST, 400) \ - V(UNAUTHORIZED, 401) \ - V(PAYMENT_REQUIRED, 402) \ - V(FORBIDDEN, 403) \ - V(NOT_FOUND, 404) \ - V(METHOD_NOT_ALLOWED, 405) \ - V(NOT_ACCEPTABLE, 406) \ - V(PROXY_AUTHENTICATION_REQUIRED, 407) \ - V(REQUEST_TIMEOUT, 408) \ - V(CONFLICT, 409) \ - V(GONE, 410) \ - V(LENGTH_REQUIRED, 411) \ - V(PRECONDITION_FAILED, 412) \ - V(PAYLOAD_TOO_LARGE, 413) \ - V(URI_TOO_LONG, 414) \ - V(UNSUPPORTED_MEDIA_TYPE, 415) \ - V(RANGE_NOT_SATISFIABLE, 416) \ - V(EXPECTATION_FAILED, 417) \ - V(TEAPOT, 418) \ - V(MISDIRECTED_REQUEST, 421) \ - V(UNPROCESSABLE_ENTITY, 422) \ - V(LOCKED, 423) \ - V(FAILED_DEPENDENCY, 424) \ - V(TOO_EARLY, 425) \ - V(UPGRADE_REQUIRED, 426) \ - V(PRECONDITION_REQUIRED, 428) \ - V(TOO_MANY_REQUESTS, 429) \ - V(REQUEST_HEADER_FIELDS_TOO_LARGE, 431) \ - V(UNAVAILABLE_FOR_LEGAL_REASONS, 451) \ - V(INTERNAL_SERVER_ERROR, 500) \ - V(NOT_IMPLEMENTED, 501) \ - V(BAD_GATEWAY, 502) \ - V(SERVICE_UNAVAILABLE, 503) \ - V(GATEWAY_TIMEOUT, 504) \ - V(HTTP_VERSION_NOT_SUPPORTED, 505) \ - V(VARIANT_ALSO_NEGOTIATES, 506) \ - V(INSUFFICIENT_STORAGE, 507) \ - V(LOOP_DETECTED, 508) \ - V(BANDWIDTH_LIMIT_EXCEEDED, 509) \ - V(NOT_EXTENDED, 510) \ - V(NETWORK_AUTHENTICATION_REQUIRED, 511) - -enum http_status_codes { -#define V(name, code) HTTP_STATUS_##name = code, - HTTP_STATUS_CODES(V) -#undef V -}; - // The Padding Strategy determines the method by which extra padding is // selected for HEADERS and DATA frames. These are configurable via the // options passed in to a Http2Session object. @@ -446,6 +257,17 @@ class Http2StreamListener : public StreamListener { void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override; }; +struct Http2HeaderTraits { + typedef Http2RcBufferPointer rcbufferpointer_t; + typedef Http2Session allocator_t; + + // HTTP/2 does not support identifying header names by token id. + // HTTP/3 will, however, so we prepare for that now. + static const char* ToHttpHeaderName(int32_t token) { return nullptr; } +}; + +using Http2Header = NgHeader; + class Http2Stream : public AsyncWrap, public StreamBase { public: @@ -476,13 +298,13 @@ class Http2Stream : public AsyncWrap, bool HasWantsWrite() const override { return true; } // Initiate a response on this stream. - int SubmitResponse(nghttp2_nv* nva, size_t len, int options); + int SubmitResponse(const Http2Headers& headers, int options); // Submit informational headers for this stream - int SubmitInfo(nghttp2_nv* nva, size_t len); + int SubmitInfo(const Http2Headers& headers); // Submit trailing headers for this stream - int SubmitTrailers(nghttp2_nv* nva, size_t len); + int SubmitTrailers(const Http2Headers& headers); void OnTrailers(); // Submit a PRIORITY frame for this stream @@ -495,8 +317,7 @@ class Http2Stream : public AsyncWrap, // Submits a PUSH_PROMISE frame with this stream as the parent. Http2Stream* SubmitPushPromise( - nghttp2_nv* nva, - size_t len, + const Http2Headers& headers, int32_t* ret, int options = 0); @@ -545,8 +366,16 @@ class Http2Stream : public AsyncWrap, bool AddHeader(nghttp2_rcbuf* name, nghttp2_rcbuf* value, uint8_t flags); - inline std::vector move_headers() { - return std::move(current_headers_); + template + void TransferHeaders(Fn&& fn) { + size_t i = 0; + for (const auto& header : current_headers_ ) + fn(header, i++); + current_headers_.clear(); + } + + size_t headers_count() const { + return current_headers_.size(); } inline nghttp2_headers_category headers_category() const { @@ -625,7 +454,7 @@ class Http2Stream : public AsyncWrap, // signalling the end of the HEADERS frame nghttp2_headers_category current_headers_category_ = NGHTTP2_HCAT_HEADERS; uint32_t current_headers_length_ = 0; // total number of octets - std::vector current_headers_; + std::vector current_headers_; // This keeps track of the amount of data read from the socket while the // socket was in paused mode. When `ReadStart()` is called (and not before @@ -743,8 +572,7 @@ class Http2Session : public AsyncWrap, // This only works if the session is a client session. Http2Stream* SubmitRequest( nghttp2_priority_spec* prispec, - nghttp2_nv* nva, - size_t len, + const Http2Headers& headers, int32_t* ret, int options = 0); @@ -1188,96 +1016,6 @@ class Http2Session::Http2Settings : public AsyncWrap { nghttp2_settings_entry entries_[IDX_SETTINGS_COUNT]; }; -class ExternalHeader : - public String::ExternalOneByteStringResource { - public: - explicit ExternalHeader(nghttp2_rcbuf* buf) - : buf_(buf), vec_(nghttp2_rcbuf_get_buf(buf)) { - } - - ~ExternalHeader() override { - nghttp2_rcbuf_decref(buf_); - buf_ = nullptr; - } - - const char* data() const override { - return const_cast(reinterpret_cast(vec_.base)); - } - - size_t length() const override { - return vec_.len; - } - - static inline - MaybeLocal GetInternalizedString(Environment* env, - const nghttp2_vec& vec) { - return String::NewFromOneByte(env->isolate(), - vec.base, - v8::NewStringType::kInternalized, - vec.len); - } - - template - static MaybeLocal New(Http2Session* session, nghttp2_rcbuf* buf) { - Environment* env = session->env(); - if (nghttp2_rcbuf_is_static(buf)) { - auto& static_str_map = env->isolate_data()->http2_static_strs; - v8::Eternal& eternal = static_str_map[buf]; - if (eternal.IsEmpty()) { - Local str = - GetInternalizedString(env, nghttp2_rcbuf_get_buf(buf)) - .ToLocalChecked(); - eternal.Set(env->isolate(), str); - return str; - } - return eternal.Get(env->isolate()); - } - - nghttp2_vec vec = nghttp2_rcbuf_get_buf(buf); - if (vec.len == 0) { - nghttp2_rcbuf_decref(buf); - return String::Empty(env->isolate()); - } - - if (may_internalize && vec.len < 64) { - nghttp2_rcbuf_decref(buf); - // This is a short header name, so there is a good chance V8 already has - // it internalized. - return GetInternalizedString(env, vec); - } - - session->StopTrackingRcbuf(buf); - ExternalHeader* h_str = new ExternalHeader(buf); - MaybeLocal str = String::NewExternalOneByte(env->isolate(), h_str); - if (str.IsEmpty()) - delete h_str; - - return str; - } - - private: - nghttp2_rcbuf* buf_; - nghttp2_vec vec_; -}; - -class Headers { - public: - Headers(Isolate* isolate, Local context, Local headers); - ~Headers() = default; - - nghttp2_nv* operator*() { - return reinterpret_cast(*buf_); - } - - size_t length() const { - return count_; - } - - private: - size_t count_; - MaybeStackBuffer buf_; -}; - class Origins { public: Origins(Isolate* isolate, diff --git a/src/node_http_common-inl.h b/src/node_http_common-inl.h new file mode 100644 index 00000000000000..1bc7b46d63a827 --- /dev/null +++ b/src/node_http_common-inl.h @@ -0,0 +1,181 @@ +#ifndef SRC_NODE_HTTP_COMMON_INL_H_ +#define SRC_NODE_HTTP_COMMON_INL_H_ + +#include "node_http_common.h" +#include "node.h" +#include "node_mem-inl.h" +#include "env-inl.h" +#include "v8.h" + +#include + +namespace node { + +template +NgHeaders::NgHeaders(Environment* env, v8::Local headers) { + v8::Local header_string = + headers->Get(env->context(), 0).ToLocalChecked(); + v8::Local header_count = + headers->Get(env->context(), 1).ToLocalChecked(); + CHECK(header_count->IsUint32()); + CHECK(header_string->IsString()); + count_ = header_count.As()->Value(); + int header_string_len = header_string.As()->Length(); + + if (count_ == 0) { + CHECK_EQ(header_string_len, 0); + return; + } + + buf_.AllocateSufficientStorage((alignof(nv_t) - 1) + + count_ * sizeof(nv_t) + + header_string_len); + + char* start = reinterpret_cast( + RoundUp(reinterpret_cast(*buf_), alignof(nv_t))); + char* header_contents = start + (count_ * sizeof(nv_t)); + nv_t* const nva = reinterpret_cast(start); + + CHECK_LE(header_contents + header_string_len, *buf_ + buf_.length()); + CHECK_EQ(header_string.As()->WriteOneByte( + env->isolate(), + reinterpret_cast(header_contents), + 0, + header_string_len, + v8::String::NO_NULL_TERMINATION), + header_string_len); + + size_t n = 0; + char* p; + for (p = header_contents; p < header_contents + header_string_len; n++) { + if (n >= count_) { + static uint8_t zero = '\0'; + nva[0].name = nva[0].value = &zero; + nva[0].namelen = nva[0].valuelen = 1; + count_ = 1; + return; + } + + nva[n].flags = T::kNoneFlag; + nva[n].name = reinterpret_cast(p); + nva[n].namelen = strlen(p); + p += nva[n].namelen + 1; + nva[n].value = reinterpret_cast(p); + nva[n].valuelen = strlen(p); + p += nva[n].valuelen + 1; + } +} + +size_t GetClientMaxHeaderPairs(size_t max_header_pairs) { + static constexpr size_t min_header_pairs = 1; + return std::max(max_header_pairs, min_header_pairs); +} + +size_t GetServerMaxHeaderPairs(size_t max_header_pairs) { + static constexpr size_t min_header_pairs = 4; + return std::max(max_header_pairs, min_header_pairs); +} + +template +bool NgHeader::IsZeroLength( + NgHeader::rcbuf_t* name, + NgHeader::rcbuf_t* value) { + return IsZeroLength(-1, name, value); +} + +template +bool NgHeader::IsZeroLength( + int32_t token, + NgHeader::rcbuf_t* name, + NgHeader::rcbuf_t* value) { + + if (NgHeader::rcbufferpointer_t::IsZeroLength(value)) + return true; + + const char* header_name = T::ToHttpHeaderName(token); + return header_name != nullptr || + NgHeader::rcbufferpointer_t::IsZeroLength(name); +} + +template +NgHeader::NgHeader( + Environment* env, + NgHeader::rcbuf_t* name, + NgHeader::rcbuf_t* value, + uint8_t flags) + : NgHeader(env, -1, name, value, flags) {} + +template +NgHeader::NgHeader( + Environment* env, + int32_t token, + NgHeader::rcbuf_t* name, + NgHeader::rcbuf_t* value, + uint8_t flags) : env_(env), token_(token), flags_(flags) { + if (token == -1) { + CHECK_NOT_NULL(name); + name_.reset(name, true); // Internalizable + } + CHECK_NOT_NULL(value); + name_.reset(name, true); // Internalizable + value_.reset(value); +} + +template +NgHeader::NgHeader(NgHeader&& other) noexcept + : token_(other.token_), + name_(std::move(other.name_)), + value_(std::move(other.value_)), + flags_(other.flags_) { + other.token_ = -1; + other.flags_ = 0; + other.env_ = nullptr; +} + +template +v8::MaybeLocal NgHeader::GetName( + NgHeader::allocator_t* allocator) const { + + // Not all instances will support using token id's for header names. + // HTTP/2 specifically does not support it. + const char* header_name = T::ToHttpHeaderName(token_); + + // If header_name is not nullptr, then it is a known header with + // a statically defined name. We can safely internalize it here. + if (header_name != nullptr) { + auto& static_str_map = env_->isolate_data()->http_static_strs; + v8::Eternal eternal = static_str_map[header_name]; + if (eternal.IsEmpty()) { + v8::Local str = OneByteString(env_->isolate(), header_name); + eternal.Set(env_->isolate(), str); + return str; + } + return eternal.Get(env_->isolate()); + } + return rcbufferpointer_t::External::New(allocator, name_); +} + +template +v8::MaybeLocal NgHeader::GetValue( + NgHeader::allocator_t* allocator) const { + return rcbufferpointer_t::External::New(allocator, value_); +} + +template +std::string NgHeader::name() const { + return name_.str(); +} + +template +std::string NgHeader::value() const { + return value_.str(); +} + +template +size_t NgHeader::length() const { + return name_.len() + value_.len(); +} + +} // namespace node + +#endif // SRC_NODE_HTTP_COMMON_INL_H_ diff --git a/src/node_http_common.h b/src/node_http_common.h new file mode 100644 index 00000000000000..d43418ba6f217c --- /dev/null +++ b/src/node_http_common.h @@ -0,0 +1,527 @@ +#ifndef SRC_NODE_HTTP_COMMON_H_ +#define SRC_NODE_HTTP_COMMON_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "v8.h" +#include "node_mem.h" + +#include + +namespace node { + +class Environment; + +#define MAX_MAX_HEADER_LIST_SIZE 16777215u +#define DEFAULT_MAX_HEADER_LIST_PAIRS 128u +#define DEFAULT_MAX_HEADER_LENGTH 8192 + +#define HTTP_SPECIAL_HEADERS(V) \ + V(STATUS, ":status") \ + V(METHOD, ":method") \ + V(AUTHORITY, ":authority") \ + V(SCHEME, ":scheme") \ + V(PATH, ":path") \ + V(PROTOCOL, ":protocol") + +#define HTTP_REGULAR_HEADERS(V) \ + V(ACCEPT_ENCODING, "accept-encoding") \ + V(ACCEPT_LANGUAGE, "accept-language") \ + V(ACCEPT_RANGES, "accept-ranges") \ + V(ACCEPT, "accept") \ + V(ACCESS_CONTROL_ALLOW_CREDENTIALS, "access-control-allow-credentials") \ + V(ACCESS_CONTROL_ALLOW_HEADERS, "access-control-allow-headers") \ + V(ACCESS_CONTROL_ALLOW_METHODS, "access-control-allow-methods") \ + V(ACCESS_CONTROL_ALLOW_ORIGIN, "access-control-allow-origin") \ + V(ACCESS_CONTROL_EXPOSE_HEADERS, "access-control-expose-headers") \ + V(ACCESS_CONTROL_REQUEST_HEADERS, "access-control-request-headers") \ + V(ACCESS_CONTROL_REQUEST_METHOD, "access-control-request-method") \ + V(AGE, "age") \ + V(AUTHORIZATION, "authorization") \ + V(CACHE_CONTROL, "cache-control") \ + V(CONNECTION, "connection") \ + V(CONTENT_DISPOSITION, "content-disposition") \ + V(CONTENT_ENCODING, "content-encoding") \ + V(CONTENT_LENGTH, "content-length") \ + V(CONTENT_TYPE, "content-type") \ + V(COOKIE, "cookie") \ + V(DATE, "date") \ + V(ETAG, "etag") \ + V(FORWARDED, "forwarded") \ + V(HOST, "host") \ + V(IF_MODIFIED_SINCE, "if-modified-since") \ + V(IF_NONE_MATCH, "if-none-match") \ + V(IF_RANGE, "if-range") \ + V(LAST_MODIFIED, "last-modified") \ + V(LINK, "link") \ + V(LOCATION, "location") \ + V(RANGE, "range") \ + V(REFERER, "referer") \ + V(SERVER, "server") \ + V(SET_COOKIE, "set-cookie") \ + V(STRICT_TRANSPORT_SECURITY, "strict-transport-security") \ + V(TRANSFER_ENCODING, "transfer-encoding") \ + V(TE, "te") \ + V(UPGRADE_INSECURE_REQUESTS, "upgrade-insecure-requests") \ + V(UPGRADE, "upgrade") \ + V(USER_AGENT, "user-agent") \ + V(VARY, "vary") \ + V(X_CONTENT_TYPE_OPTIONS, "x-content-type-options") \ + V(X_FRAME_OPTIONS, "x-frame-options") \ + V(KEEP_ALIVE, "keep-alive") \ + V(PROXY_CONNECTION, "proxy-connection") \ + V(X_XSS_PROTECTION, "x-xss-protection") \ + V(ALT_SVC, "alt-svc") \ + V(CONTENT_SECURITY_POLICY, "content-security-policy") \ + V(EARLY_DATA, "early-data") \ + V(EXPECT_CT, "expect-ct") \ + V(ORIGIN, "origin") \ + V(PURPOSE, "purpose") \ + V(TIMING_ALLOW_ORIGIN, "timing-allow-origin") \ + V(X_FORWARDED_FOR, "x-forwarded-for") + +#define HTTP_ADDITIONAL_HEADERS(V) \ + V(ACCEPT_CHARSET, "accept-charset") \ + V(ACCESS_CONTROL_MAX_AGE, "access-control-max-age") \ + V(ALLOW, "allow") \ + V(CONTENT_LANGUAGE, "content-language") \ + V(CONTENT_LOCATION, "content-location") \ + V(CONTENT_MD5, "content-md5") \ + V(CONTENT_RANGE, "content-range") \ + V(DNT, "dnt") \ + V(EXPECT, "expect") \ + V(EXPIRES, "expires") \ + V(FROM, "from") \ + V(IF_MATCH, "if-match") \ + V(IF_UNMODIFIED_SINCE, "if-unmodified-since") \ + V(MAX_FORWARDS, "max-forwards") \ + V(PREFER, "prefer") \ + V(PROXY_AUTHENTICATE, "proxy-authenticate") \ + V(PROXY_AUTHORIZATION, "proxy-authorization") \ + V(REFRESH, "refresh") \ + V(RETRY_AFTER, "retry-after") \ + V(TRAILER, "trailer") \ + V(TK, "tk") \ + V(VIA, "via") \ + V(WARNING, "warning") \ + V(WWW_AUTHENTICATE, "www-authenticate") \ + V(HTTP2_SETTINGS, "http2-settings") + +// Special and regular headers are handled specifically by the HTTP/2 (and +// later HTTP/3) implementation. +#define HTTP_KNOWN_HEADERS(V) \ + HTTP_SPECIAL_HEADERS(V) \ + HTTP_REGULAR_HEADERS(V) \ + HTTP_ADDITIONAL_HEADERS(V) + +enum http_known_headers { + HTTP_KNOWN_HEADER_MIN, +#define V(name, value) HTTP_HEADER_##name, + HTTP_KNOWN_HEADERS(V) +#undef V + HTTP_KNOWN_HEADER_MAX +}; + +#define HTTP_STATUS_CODES(V) \ + V(CONTINUE, 100) \ + V(SWITCHING_PROTOCOLS, 101) \ + V(PROCESSING, 102) \ + V(EARLY_HINTS, 103) \ + V(OK, 200) \ + V(CREATED, 201) \ + V(ACCEPTED, 202) \ + V(NON_AUTHORITATIVE_INFORMATION, 203) \ + V(NO_CONTENT, 204) \ + V(RESET_CONTENT, 205) \ + V(PARTIAL_CONTENT, 206) \ + V(MULTI_STATUS, 207) \ + V(ALREADY_REPORTED, 208) \ + V(IM_USED, 226) \ + V(MULTIPLE_CHOICES, 300) \ + V(MOVED_PERMANENTLY, 301) \ + V(FOUND, 302) \ + V(SEE_OTHER, 303) \ + V(NOT_MODIFIED, 304) \ + V(USE_PROXY, 305) \ + V(TEMPORARY_REDIRECT, 307) \ + V(PERMANENT_REDIRECT, 308) \ + V(BAD_REQUEST, 400) \ + V(UNAUTHORIZED, 401) \ + V(PAYMENT_REQUIRED, 402) \ + V(FORBIDDEN, 403) \ + V(NOT_FOUND, 404) \ + V(METHOD_NOT_ALLOWED, 405) \ + V(NOT_ACCEPTABLE, 406) \ + V(PROXY_AUTHENTICATION_REQUIRED, 407) \ + V(REQUEST_TIMEOUT, 408) \ + V(CONFLICT, 409) \ + V(GONE, 410) \ + V(LENGTH_REQUIRED, 411) \ + V(PRECONDITION_FAILED, 412) \ + V(PAYLOAD_TOO_LARGE, 413) \ + V(URI_TOO_LONG, 414) \ + V(UNSUPPORTED_MEDIA_TYPE, 415) \ + V(RANGE_NOT_SATISFIABLE, 416) \ + V(EXPECTATION_FAILED, 417) \ + V(TEAPOT, 418) \ + V(MISDIRECTED_REQUEST, 421) \ + V(UNPROCESSABLE_ENTITY, 422) \ + V(LOCKED, 423) \ + V(FAILED_DEPENDENCY, 424) \ + V(TOO_EARLY, 425) \ + V(UPGRADE_REQUIRED, 426) \ + V(PRECONDITION_REQUIRED, 428) \ + V(TOO_MANY_REQUESTS, 429) \ + V(REQUEST_HEADER_FIELDS_TOO_LARGE, 431) \ + V(UNAVAILABLE_FOR_LEGAL_REASONS, 451) \ + V(INTERNAL_SERVER_ERROR, 500) \ + V(NOT_IMPLEMENTED, 501) \ + V(BAD_GATEWAY, 502) \ + V(SERVICE_UNAVAILABLE, 503) \ + V(GATEWAY_TIMEOUT, 504) \ + V(HTTP_VERSION_NOT_SUPPORTED, 505) \ + V(VARIANT_ALSO_NEGOTIATES, 506) \ + V(INSUFFICIENT_STORAGE, 507) \ + V(LOOP_DETECTED, 508) \ + V(BANDWIDTH_LIMIT_EXCEEDED, 509) \ + V(NOT_EXTENDED, 510) \ + V(NETWORK_AUTHENTICATION_REQUIRED, 511) + +enum http_status_codes { +#define V(name, code) HTTP_STATUS_##name = code, + HTTP_STATUS_CODES(V) +#undef V +}; + +// Unlike the HTTP/1 implementation, the HTTP/2 implementation is not limited +// to a fixed number of known supported HTTP methods. These constants, therefore +// are provided strictly as a convenience to users and are exposed via the +// require('http2').constants object. +#define HTTP_KNOWN_METHODS(V) \ + V(ACL, "ACL") \ + V(BASELINE_CONTROL, "BASELINE-CONTROL") \ + V(BIND, "BIND") \ + V(CHECKIN, "CHECKIN") \ + V(CHECKOUT, "CHECKOUT") \ + V(CONNECT, "CONNECT") \ + V(COPY, "COPY") \ + V(DELETE, "DELETE") \ + V(GET, "GET") \ + V(HEAD, "HEAD") \ + V(LABEL, "LABEL") \ + V(LINK, "LINK") \ + V(LOCK, "LOCK") \ + V(MERGE, "MERGE") \ + V(MKACTIVITY, "MKACTIVITY") \ + V(MKCALENDAR, "MKCALENDAR") \ + V(MKCOL, "MKCOL") \ + V(MKREDIRECTREF, "MKREDIRECTREF") \ + V(MKWORKSPACE, "MKWORKSPACE") \ + V(MOVE, "MOVE") \ + V(OPTIONS, "OPTIONS") \ + V(ORDERPATCH, "ORDERPATCH") \ + V(PATCH, "PATCH") \ + V(POST, "POST") \ + V(PRI, "PRI") \ + V(PROPFIND, "PROPFIND") \ + V(PROPPATCH, "PROPPATCH") \ + V(PUT, "PUT") \ + V(REBIND, "REBIND") \ + V(REPORT, "REPORT") \ + V(SEARCH, "SEARCH") \ + V(TRACE, "TRACE") \ + V(UNBIND, "UNBIND") \ + V(UNCHECKOUT, "UNCHECKOUT") \ + V(UNLINK, "UNLINK") \ + V(UNLOCK, "UNLOCK") \ + V(UPDATE, "UPDATE") \ + V(UPDATEREDIRECTREF, "UPDATEREDIRECTREF") \ + V(VERSION_CONTROL, "VERSION-CONTROL") + +// NgHeaders takes as input a block of headers provided by the +// JavaScript side (see http2's mapToHeaders function) and +// converts it into a array of ng header structs. This is done +// generically to handle both http/2 and (in the future) http/3, +// which use nearly identical structs. The template parameter +// takes a Traits type that defines the ng header struct and +// the kNoneFlag value. See Http2HeaderTraits in node_http2.h +// for an example. +template +class NgHeaders { + public: + typedef typename T::nv_t nv_t; + inline NgHeaders(Environment* env, v8::Local headers); + ~NgHeaders() = default; + + const nv_t* operator*() const { + return reinterpret_cast(*buf_); + } + + const nv_t* data() const { + return reinterpret_cast(*buf_); + } + + size_t length() const { + return count_; + } + + private: + size_t count_; + MaybeStackBuffer buf_; +}; + +// The ng libraries (nghttp2 and nghttp3) each use nearly identical +// reference counted structures for retaining header name and value +// information in memory until the application is done with it. +// The NgRcBufPointer is an intelligent pointer capable of working +// with either type, handling the ref counting increment and +// decrement as appropriate. The Template takes a single Traits +// type that provides the rc buffer and vec type, as well as +// implementations for multiple static functions. +// See Http2RcBufferPointerTraits in node_http2.h for an example. +template +class NgRcBufPointer : public MemoryRetainer { + public: + typedef typename T::rcbuf_t rcbuf_t; + typedef typename T::vector_t vector_t; + + NgRcBufPointer() = default; + + explicit NgRcBufPointer(rcbuf_t* buf) { + reset(buf); + } + + template + NgRcBufPointer(const NgRcBufPointer& other) { + reset(other.get()); + } + + NgRcBufPointer(const NgRcBufPointer& other) { + reset(other.get()); + } + + template + NgRcBufPointer& operator=(const NgRcBufPointer& other) { + if (other.get() == get()) return *this; + this->~NgRcBufPointer(); + return *new (this) NgRcBufPointer(other); + } + + NgRcBufPointer& operator=(const NgRcBufPointer& other) { + if (other.get() == get()) return *this; + this->~NgRcBufPointer(); + return *new (this) NgRcBufPointer(other); + } + + NgRcBufPointer(NgRcBufPointer&& other) { + this->~NgRcBufPointer(); + buf_ = other.buf_; + other.buf_ = nullptr; + } + + NgRcBufPointer& operator=(NgRcBufPointer&& other) { + this->~NgRcBufPointer(); + return *new (this) NgRcBufPointer(std::move(other)); + } + + ~NgRcBufPointer() { + reset(); + } + + // Returns the underlying ngvec for this rcbuf + uint8_t* data() const { + vector_t v = T::get_vec(buf_); + return v.base; + } + + size_t len() const { + vector_t v = T::get_vec(buf_); + return v.len; + } + + std::string str() const { + return std::string(reinterpret_cast(data()), len()); + } + + void reset(rcbuf_t* ptr = nullptr, bool internalizable = false) { + if (buf_ == ptr) + return; + + if (buf_ != nullptr) + T::dec(buf_); + + buf_ = ptr; + + if (ptr != nullptr) { + T::inc(ptr); + internalizable_ = internalizable; + } + } + + rcbuf_t* get() const { return buf_; } + rcbuf_t& operator*() const { return *get(); } + rcbuf_t* operator->() const { return buf_; } + operator bool() const { return buf_ != nullptr; } + bool IsStatic() const { return T::is_static(buf_) != 0; } + void SetInternalizable() { internalizable_ = true; } + bool IsInternalizable() const { return internalizable_; } + + static inline bool IsZeroLength(rcbuf_t* buf) { + if (buf == nullptr) + return true; + vector_t b = T::get_vec(buf); + return b.len == 0; + } + + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackFieldWithSize("buf", len(), "buf"); + } + + SET_MEMORY_INFO_NAME(NgRcBufPointer) + SET_SELF_SIZE(NgRcBufPointer) + + class External : public v8::String::ExternalOneByteStringResource { + public: + explicit External(const NgRcBufPointer& ptr) : ptr_(ptr) {} + + const char* data() const override { + return const_cast(reinterpret_cast(ptr_.data())); + } + + size_t length() const override { + return ptr_.len(); + } + + static inline + v8::MaybeLocal GetInternalizedString( + Environment* env, + const NgRcBufPointer& ptr) { + return v8::String::NewFromOneByte( + env->isolate(), + ptr.data(), + v8::NewStringType::kInternalized, + ptr.len()); + } + + template + static v8::MaybeLocal New( + Allocator* allocator, + NgRcBufPointer ptr) { + Environment* env = allocator->env(); + if (ptr.IsStatic()) { + auto& static_str_map = env->isolate_data()->http_static_strs; + const char* header_name = reinterpret_cast(ptr.data()); + v8::Eternal& eternal = static_str_map[header_name]; + if (eternal.IsEmpty()) { + v8::Local str = + GetInternalizedString(env, ptr).ToLocalChecked(); + eternal.Set(env->isolate(), str); + return str; + } + return eternal.Get(env->isolate()); + } + + size_t len = ptr.len(); + + if (len == 0) { + ptr.reset(); + return v8::String::Empty(env->isolate()); + } + + if (ptr.IsInternalizable() && len < 64) { + v8::MaybeLocal ret = GetInternalizedString(env, ptr); + ptr.reset(); + return ret; + } + + allocator->StopTrackingMemory(ptr.get()); + External* h_str = new External(std::move(ptr)); + v8::MaybeLocal str = + v8::String::NewExternalOneByte(env->isolate(), h_str); + if (str.IsEmpty()) + delete h_str; + + return str; + } + + private: + NgRcBufPointer ptr_; + }; + + private: + rcbuf_t* buf_ = nullptr; + bool internalizable_ = false; +}; + +// The ng libraries use nearly identical structs to represent +// received http headers. The NgHeader class wraps those in a +// consistent way and allows converting the name and value to +// v8 strings. The template is given a Traits type that provides +// the NgRcBufPointer type, the NgLibMemoryManager to use for +// memory tracking, and implementation of static utility functions. +// See Http2HeaderTraits in node_http2.h for an example. +template +class NgHeader : public MemoryRetainer { + public: + typedef typename T::rcbufferpointer_t rcbufferpointer_t; + typedef typename T::rcbufferpointer_t::rcbuf_t rcbuf_t; + typedef typename T::allocator_t allocator_t; + + inline static bool IsZeroLength(rcbuf_t* name, rcbuf_t* value); + inline static bool IsZeroLength(int32_t token, rcbuf_t* name, rcbuf_t* value); + inline NgHeader( + Environment* env, + rcbuf_t* name, + rcbuf_t* value, + uint8_t flags); + inline NgHeader( + Environment* env, + int32_t token, + rcbuf_t* name, + rcbuf_t* value, + uint8_t flags); + inline NgHeader(NgHeader&& other) noexcept; + + // Calling GetName and GetValue will have the effect of releasing + // control over the reference counted buffer from this NgHeader + // object to the v8 string. Once the v8 string is garbage collected, + // the reference counter will be decremented. + + inline v8::MaybeLocal GetName(allocator_t* allocator) const; + inline v8::MaybeLocal GetValue(allocator_t* allocator) const; + + inline std::string name() const; + inline std::string value() const; + inline size_t length() const; + + void MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("name", name_); + tracker->TrackField("value", value_); + } + + SET_MEMORY_INFO_NAME(NgHeader) + SET_SELF_SIZE(NgHeader) + + std::string ToString() const { + std::string ret = name(); + ret += " = "; + ret += value(); + return ret; + } + + private: + Environment* env_; + rcbufferpointer_t name_; + rcbufferpointer_t value_; + int32_t token_ = -1; + uint8_t flags_ = 0; +}; + +inline size_t GetServerMaxHeaderPairs(size_t max_header_pairs); +inline size_t GetClientMaxHeaderPairs(size_t max_header_pairs); + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_NODE_HTTP_COMMON_H_ diff --git a/test/parallel/test-http2-binding.js b/test/parallel/test-http2-binding.js index 81f49691e3ecff..e81a58dfe8a5c1 100644 --- a/test/parallel/test-http2-binding.js +++ b/test/parallel/test-http2-binding.js @@ -170,7 +170,16 @@ const expectedHeaderNames = { HTTP2_HEADER_CONTENT_MD5: 'content-md5', HTTP2_HEADER_TE: 'te', HTTP2_HEADER_UPGRADE: 'upgrade', - HTTP2_HEADER_HTTP2_SETTINGS: 'http2-settings' + HTTP2_HEADER_HTTP2_SETTINGS: 'http2-settings', + HTTP2_HEADER_X_XSS_PROTECTION: 'x-xss-protection', + HTTP2_HEADER_ALT_SVC: 'alt-svc', + HTTP2_HEADER_CONTENT_SECURITY_POLICY: 'content-security-policy', + HTTP2_HEADER_EARLY_DATA: 'early-data', + HTTP2_HEADER_EXPECT_CT: 'expect-ct', + HTTP2_HEADER_ORIGIN: 'origin', + HTTP2_HEADER_PURPOSE: 'purpose', + HTTP2_HEADER_TIMING_ALLOW_ORIGIN: 'timing-allow-origin', + HTTP2_HEADER_X_FORWARDED_FOR: 'x-forwarded-for', }; const expectedNGConstants = { From 3befe80c4fa864fbef445dc8eee7b81f22b184de Mon Sep 17 00:00:00 2001 From: Andrey Pechkurov Date: Wed, 4 Mar 2020 14:57:56 +0300 Subject: [PATCH 011/108] async_hooks: fix ctx loss after nested ALS calls PR-URL: https://github.com/nodejs/node/pull/32085 Reviewed-By: Stephen Belanger Reviewed-By: Vladimir de Turckheim Reviewed-By: Michael Dawson --- lib/async_hooks.js | 21 +++++---- ...test-async-local-storage-enable-disable.js | 8 ++++ .../test-async-local-storage-nested.js | 44 +++++++++++++------ 3 files changed, 50 insertions(+), 23 deletions(-) diff --git a/lib/async_hooks.js b/lib/async_hooks.js index 3797baf183250a..bd3cd57d022f4b 100644 --- a/lib/async_hooks.js +++ b/lib/async_hooks.js @@ -255,23 +255,21 @@ class AsyncLocalStorage { resource[this.kResourceStore] = store; } - _exit() { - const resource = executionAsyncResource(); - if (resource) { - resource[this.kResourceStore] = undefined; - } - } - runSyncAndReturn(store, callback, ...args) { + const resource = executionAsyncResource(); + const outerStore = resource[this.kResourceStore]; this._enter(store); try { return callback(...args); } finally { - this._exit(); + resource[this.kResourceStore] = outerStore; } } exitSyncAndReturn(callback, ...args) { + if (!this.enabled) { + return callback(...args); + } this.enabled = false; try { return callback(...args); @@ -288,12 +286,17 @@ class AsyncLocalStorage { } run(store, callback, ...args) { + const resource = executionAsyncResource(); + const outerStore = resource[this.kResourceStore]; this._enter(store); process.nextTick(callback, ...args); - this._exit(); + resource[this.kResourceStore] = outerStore; } exit(callback, ...args) { + if (!this.enabled) { + return process.nextTick(callback, ...args); + } this.enabled = false; process.nextTick(callback, ...args); this.enabled = true; diff --git a/test/async-hooks/test-async-local-storage-enable-disable.js b/test/async-hooks/test-async-local-storage-enable-disable.js index 22a3f5f6c8c43f..93132079827eeb 100644 --- a/test/async-hooks/test-async-local-storage-enable-disable.js +++ b/test/async-hooks/test-async-local-storage-enable-disable.js @@ -12,8 +12,16 @@ asyncLocalStorage.runSyncAndReturn(new Map(), () => { process.nextTick(() => { assert.strictEqual(asyncLocalStorage.getStore(), undefined); }); + asyncLocalStorage.disable(); assert.strictEqual(asyncLocalStorage.getStore(), undefined); + + // Calls to exit() should not mess with enabled status + asyncLocalStorage.exit(() => { + assert.strictEqual(asyncLocalStorage.getStore(), undefined); + }); + assert.strictEqual(asyncLocalStorage.getStore(), undefined); + process.nextTick(() => { assert.strictEqual(asyncLocalStorage.getStore(), undefined); asyncLocalStorage.runSyncAndReturn(new Map(), () => { diff --git a/test/async-hooks/test-async-local-storage-nested.js b/test/async-hooks/test-async-local-storage-nested.js index 1409a8ebc82a04..143d5d45de9e25 100644 --- a/test/async-hooks/test-async-local-storage-nested.js +++ b/test/async-hooks/test-async-local-storage-nested.js @@ -4,19 +4,35 @@ const assert = require('assert'); const { AsyncLocalStorage } = require('async_hooks'); const asyncLocalStorage = new AsyncLocalStorage(); +const outer = {}; +const inner = {}; -setTimeout(() => { - asyncLocalStorage.run(new Map(), () => { - const asyncLocalStorage2 = new AsyncLocalStorage(); - asyncLocalStorage2.run(new Map(), () => { - const store = asyncLocalStorage.getStore(); - const store2 = asyncLocalStorage2.getStore(); - store.set('hello', 'world'); - store2.set('hello', 'foo'); - setTimeout(() => { - assert.strictEqual(asyncLocalStorage.getStore().get('hello'), 'world'); - assert.strictEqual(asyncLocalStorage2.getStore().get('hello'), 'foo'); - }, 200); - }); +function testInner() { + assert.strictEqual(asyncLocalStorage.getStore(), outer); + + asyncLocalStorage.run(inner, () => { + assert.strictEqual(asyncLocalStorage.getStore(), inner); + }); + assert.strictEqual(asyncLocalStorage.getStore(), outer); + + asyncLocalStorage.exit(() => { + assert.strictEqual(asyncLocalStorage.getStore(), undefined); + }); + assert.strictEqual(asyncLocalStorage.getStore(), outer); + + asyncLocalStorage.runSyncAndReturn(inner, () => { + assert.strictEqual(asyncLocalStorage.getStore(), inner); }); -}, 100); + assert.strictEqual(asyncLocalStorage.getStore(), outer); + + asyncLocalStorage.exitSyncAndReturn(() => { + assert.strictEqual(asyncLocalStorage.getStore(), undefined); + }); + assert.strictEqual(asyncLocalStorage.getStore(), outer); +} + +asyncLocalStorage.run(outer, testInner); +assert.strictEqual(asyncLocalStorage.getStore(), undefined); + +asyncLocalStorage.runSyncAndReturn(outer, testInner); +assert.strictEqual(asyncLocalStorage.getStore(), undefined); From 2585b814b0da4895b156e486351fa740910068b7 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Mon, 2 Mar 2020 12:44:42 +0100 Subject: [PATCH 012/108] stream: add comments to pipeline implementation Fixes: https://github.com/nodejs/node/issues/32039 PR-URL: https://github.com/nodejs/node/pull/32042 Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina Reviewed-By: James M Snell --- lib/internal/streams/pipeline.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/internal/streams/pipeline.js b/lib/internal/streams/pipeline.js index df0d7bc85d366c..b03c4e1513925b 100644 --- a/lib/internal/streams/pipeline.js +++ b/lib/internal/streams/pipeline.js @@ -219,6 +219,11 @@ function pipeline(...streams) { PassThrough = require('_stream_passthrough'); } + // If the last argument to pipeline is not a stream + // we must create a proxy stream so that pipeline(...) + // always returns a stream which can be further + // composed through `.pipe(stream)`. + const pt = new PassThrough(); if (isPromise(ret)) { ret @@ -253,6 +258,9 @@ function pipeline(...streams) { } } + // TODO(ronag): Consider returning a Duplex proxy if the first argument + // is a writable. Would improve composability. + // See, https://github.com/nodejs/node/issues/32020 return ret; } From 55486bceb977fcc46a6d80431fe14b016b9c3c9a Mon Sep 17 00:00:00 2001 From: zfx <502545703@qq.com> Date: Tue, 18 Feb 2020 22:35:08 +0800 Subject: [PATCH 013/108] events: fix removeListener for Symbols Fix removeListener when eventName type is 'symbol'. ```js const EventEmitter = require('events'); const myEmitter = new EventEmitter(); const sym = Symbol('symbol'); const fn = () => { }; myEmitter.on(sym, fn); myEmitter.on('removeListener', (...args) => { console.log('removeListener'); console.log(args, args[0] === sym, args[1] === fn); }); myEmitter.removeAllListeners() ``` When the listener's eventName type is 'symbol' and removeListener is called with no parameters, removeListener should be emitted. PR-URL: https://github.com/nodejs/node/pull/31847 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: Ruben Bridgewater Reviewed-By: Shelley Vohr Reviewed-By: Yongsheng Zhang Reviewed-By: Luigi Pinca Reviewed-By: James M Snell --- lib/events.js | 3 +-- .../test-event-emitter-remove-all-listeners.js | 13 +++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/events.js b/lib/events.js index 4a8311af059295..b138979e138fdf 100644 --- a/lib/events.js +++ b/lib/events.js @@ -31,7 +31,6 @@ const { ObjectDefineProperty, ObjectGetPrototypeOf, ObjectSetPrototypeOf, - ObjectKeys, Promise, PromiseReject, PromiseResolve, @@ -526,7 +525,7 @@ EventEmitter.prototype.removeAllListeners = // Emit removeListener for all listeners on all events if (arguments.length === 0) { - for (const key of ObjectKeys(events)) { + for (const key of ReflectOwnKeys(events)) { if (key === 'removeListener') continue; this.removeAllListeners(key); } diff --git a/test/parallel/test-event-emitter-remove-all-listeners.js b/test/parallel/test-event-emitter-remove-all-listeners.js index 3dfe65a8b4bfd0..c62183fd08c203 100644 --- a/test/parallel/test-event-emitter-remove-all-listeners.js +++ b/test/parallel/test-event-emitter-remove-all-listeners.js @@ -108,3 +108,16 @@ function expect(expected) { ee._events = undefined; assert.strictEqual(ee, ee.removeAllListeners()); } + +{ + const ee = new events.EventEmitter(); + const symbol = Symbol('symbol'); + const noop = common.mustNotCall(); + ee.on(symbol, noop); + + ee.on('removeListener', common.mustCall((...args) => { + assert.deepStrictEqual(args, [symbol, noop]); + })); + + ee.removeAllListeners(); +} From 1ffa9f388fbd4d732c9d91a0696225e65ba68fe9 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Fri, 28 Feb 2020 22:27:39 +0100 Subject: [PATCH 014/108] http: fix socket re-use races Whether and when a socket is destroyed or not after a timeout is up to the user. This leaves an edge case where a socket that has emitted 'timeout' might be re-used from the free pool. Even if destroy is called on the socket, it won't be removed from the freelist until 'close' which can happen several ticks later. Sockets are removed from the free list on the 'close' event. However, there is a delay between calling destroy() and 'close' being emitted. This means that it possible for a socket that has been destroyed to be re-used from the free list, causing unexpected failures. PR-URL: https://github.com/nodejs/node/pull/32000 Reviewed-By: Matteo Collina Reviewed-By: Denys Otrishko Reviewed-By: Luigi Pinca --- doc/api/http.md | 3 + lib/_http_agent.js | 50 ++++++---- .../test-http-agent-timeout-option.js | 4 +- test/parallel/test-http-agent-timeout.js | 94 +++++++++++++++++++ .../test-http-client-set-timeout-after-end.js | 2 +- test/parallel/test-http-client-set-timeout.js | 2 +- ...st-http-client-timeout-option-listeners.js | 4 +- ...t-http-client-timeout-option-with-agent.js | 4 +- 8 files changed, 139 insertions(+), 24 deletions(-) create mode 100644 test/parallel/test-http-agent-timeout.js diff --git a/doc/api/http.md b/doc/api/http.md index f6aa93ecedc2eb..3b45a36546d0c4 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -239,6 +239,9 @@ added: v0.11.4 An object which contains arrays of sockets currently awaiting use by the agent when `keepAlive` is enabled. Do not modify. +Sockets in the `freeSockets` list will be automatically destroyed and +removed from the array on `'timeout'`. + ### `agent.getName(options)` + +* `store` {any} + +Calling `asyncLocalStorage.enterWith(store)` will transition into the context +for the remainder of the current synchronous execution and will persist +through any following asynchronous calls. + +Example: + +```js +const store = { id: 1 }; +asyncLocalStorage.enterWith(store); +asyncLocalStorage.getStore(); // Returns the store object +someAsyncOperation(() => { + asyncLocalStorage.getStore(); // Returns the same object +}); +``` + +This transition will continue for the _entire_ synchronous execution. +This means that if, for example, the context is entered within an event +handler subsequent event handlers will also run within that context unless +specifically bound to another context with an `AsyncResource`. + +```js +const store = { id: 1 }; + +emitter.on('my-event', () => { + asyncLocalStorage.enterWith(store); +}); +emitter.on('my-event', () => { + asyncLocalStorage.getStore(); // Returns the same object +}); + +asyncLocalStorage.getStore(); // Returns undefined +emitter.emit('my-event'); +asyncLocalStorage.getStore(); // Returns the same object +``` + ### `asyncLocalStorage.run(store, callback[, ...args])` ```C @@ -401,6 +402,7 @@ by the previous call, it will not be called. ### napi_get_instance_data ```C @@ -1663,10 +1665,9 @@ the `napi_value` in question is of the JavaScript type expected by the API. #### napi_key_collection_mode -> Stability: 1 - Experimental - ```C typedef enum { napi_key_include_prototypes, @@ -1685,10 +1686,9 @@ of the objects's prototype chain as well. #### napi_key_filter -> Stability: 1 - Experimental - ```C typedef enum { napi_key_all_properties = 0, @@ -1705,10 +1705,9 @@ Property filter bits. They can be or'ed to build a composite filter. #### napi_key_conversion -> Stability: 1 - Experimental - ```C typedef enum { napi_key_keep_numbers, @@ -2262,10 +2261,9 @@ The JavaScript `Number` type is described in #### napi_create_bigint_int64 -> Stability: 1 - Experimental - ```C napi_status napi_create_bigint_int64(napi_env env, int64_t value, @@ -2283,10 +2281,9 @@ This API converts the C `int64_t` type to the JavaScript `BigInt` type. #### napi_create_bigint_uint64 -> Stability: 1 - Experimental - ```C napi_status napi_create_bigint_uint64(napi_env env, uint64_t value, @@ -2304,10 +2301,9 @@ This API converts the C `uint64_t` type to the JavaScript `BigInt` type. #### napi_create_bigint_words -> Stability: 1 - Experimental - ```C napi_status napi_create_bigint_words(napi_env env, int sign_bit, @@ -2653,10 +2649,9 @@ This API returns the C double primitive equivalent of the given JavaScript #### napi_get_value_bigint_int64 -> Stability: 1 - Experimental - ```C napi_status napi_get_value_bigint_int64(napi_env env, napi_value value, @@ -2680,10 +2675,9 @@ This API returns the C `int64_t` primitive equivalent of the given JavaScript #### napi_get_value_bigint_uint64 -> Stability: 1 - Experimental - ```C napi_status napi_get_value_bigint_uint64(napi_env env, napi_value value, @@ -2707,10 +2701,9 @@ This API returns the C `uint64_t` primitive equivalent of the given JavaScript #### napi_get_value_bigint_words -> Stability: 1 - Experimental - ```C napi_status napi_get_value_bigint_words(napi_env env, napi_value value, @@ -3595,10 +3588,9 @@ included. #### napi_get_all_property_names -> Stability: 1 - Experimental - ```C napi_get_all_property_names(napi_env env, napi_value object, diff --git a/src/js_native_api.h b/src/js_native_api.h index cb69fde5d1990e..2675da505c2368 100644 --- a/src/js_native_api.h +++ b/src/js_native_api.h @@ -4,7 +4,6 @@ // This file needs to be compatible with C compilers. #include // NOLINT(modernize-deprecated-headers) #include // NOLINT(modernize-deprecated-headers) -#include "js_native_api_types.h" // Use INT_MAX, this should only be consumed by the pre-processor anyway. #define NAPI_VERSION_EXPERIMENTAL 2147483647 @@ -18,10 +17,12 @@ // functions available in a new version of N-API that is not yet ported in all // LTS versions, they can set NAPI_VERSION knowing that they have specifically // depended on that version. -#define NAPI_VERSION 5 +#define NAPI_VERSION 6 #endif #endif +#include "js_native_api_types.h" + // If you need __declspec(dllimport), either include instead, or // define NAPI_EXTERN as __declspec(dllimport) on the compiler's command line. #ifndef NAPI_EXTERN @@ -478,7 +479,7 @@ NAPI_EXTERN napi_status napi_add_finalizer(napi_env env, #endif // NAPI_VERSION >= 5 -#ifdef NAPI_EXPERIMENTAL +#if NAPI_VERSION >= 6 // BigInt NAPI_EXTERN napi_status napi_create_bigint_int64(napi_env env, @@ -523,7 +524,9 @@ NAPI_EXTERN napi_status napi_set_instance_data(napi_env env, NAPI_EXTERN napi_status napi_get_instance_data(napi_env env, void** data); +#endif // NAPI_VERSION >= 6 +#ifdef NAPI_EXPERIMENTAL // ArrayBuffer detaching NAPI_EXTERN napi_status napi_detach_arraybuffer(napi_env env, napi_value arraybuffer); diff --git a/src/js_native_api_types.h b/src/js_native_api_types.h index ef44dd457dba6d..7a49fc9f719b30 100644 --- a/src/js_native_api_types.h +++ b/src/js_native_api_types.h @@ -115,7 +115,7 @@ typedef struct { napi_status error_code; } napi_extended_error_info; -#ifdef NAPI_EXPERIMENTAL +#if NAPI_VERSION >= 6 typedef enum { napi_key_include_prototypes, napi_key_own_only @@ -134,6 +134,6 @@ typedef enum { napi_key_keep_numbers, napi_key_numbers_to_strings } napi_key_conversion; -#endif +#endif // NAPI_VERSION >= 6 #endif // SRC_JS_NATIVE_API_TYPES_H_ diff --git a/src/node_version.h b/src/node_version.h index 2e1e77fc55b6b9..d5f031a9bac522 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -93,6 +93,6 @@ // The NAPI_VERSION provided by this version of the runtime. This is the version // which the Node binary being built supports. -#define NAPI_VERSION 5 +#define NAPI_VERSION 6 #endif // SRC_NODE_VERSION_H_ diff --git a/test/js-native-api/test_bigint/test_bigint.c b/test/js-native-api/test_bigint/test_bigint.c index 4befc171baa5da..c62a0a6a6c2bbc 100644 --- a/test/js-native-api/test_bigint/test_bigint.c +++ b/test/js-native-api/test_bigint/test_bigint.c @@ -1,5 +1,3 @@ -#define NAPI_EXPERIMENTAL - #include #include #include diff --git a/test/js-native-api/test_general/test.js b/test/js-native-api/test_general/test.js index 9b847f4f339e49..a4b3df5535a365 100644 --- a/test/js-native-api/test_general/test.js +++ b/test/js-native-api/test_general/test.js @@ -33,7 +33,7 @@ assert.notStrictEqual(test_general.testGetPrototype(baseObject), test_general.testGetPrototype(extendedObject)); // Test version management functions. The expected version is currently 4. -assert.strictEqual(test_general.testGetVersion(), 5); +assert.strictEqual(test_general.testGetVersion(), 6); [ 123, diff --git a/test/js-native-api/test_instance_data/test_instance_data.c b/test/js-native-api/test_instance_data/test_instance_data.c index a64ebec0c1aba9..5255c3e4a02b94 100644 --- a/test/js-native-api/test_instance_data/test_instance_data.c +++ b/test/js-native-api/test_instance_data/test_instance_data.c @@ -1,6 +1,5 @@ #include #include -#define NAPI_EXPERIMENTAL #include #include "../common.h" diff --git a/test/js-native-api/test_object/test_null.c b/test/js-native-api/test_object/test_null.c index 523217f3c0aad2..b6bf4df31cc918 100644 --- a/test/js-native-api/test_object/test_null.c +++ b/test/js-native-api/test_object/test_null.c @@ -1,4 +1,3 @@ -#define NAPI_EXPERIMENTAL #include #include "../common.h" diff --git a/test/js-native-api/test_object/test_object.c b/test/js-native-api/test_object/test_object.c index 9d9589238d4079..08f619bf7ffc88 100644 --- a/test/js-native-api/test_object/test_object.c +++ b/test/js-native-api/test_object/test_object.c @@ -1,5 +1,3 @@ -#define NAPI_EXPERIMENTAL - #include #include "../common.h" #include diff --git a/test/node-api/test_general/test_general.c b/test/node-api/test_general/test_general.c index 05bccaf5c2cf4f..be805f782be8d5 100644 --- a/test/node-api/test_general/test_general.c +++ b/test/node-api/test_general/test_general.c @@ -1,4 +1,3 @@ -#define NAPI_EXPERIMENTAL #include #include #include "../../js-native-api/common.h" diff --git a/test/node-api/test_instance_data/addon.c b/test/node-api/test_instance_data/addon.c index 928b4dfaf8e11e..7cf27bf28ab7eb 100644 --- a/test/node-api/test_instance_data/addon.c +++ b/test/node-api/test_instance_data/addon.c @@ -1,6 +1,5 @@ #include #include -#define NAPI_EXPERIMENTAL #include static void addon_free(napi_env env, void* data, void* hint) { diff --git a/test/node-api/test_instance_data/test_instance_data.c b/test/node-api/test_instance_data/test_instance_data.c index 1a814e91c0665b..24fd502e836176 100644 --- a/test/node-api/test_instance_data/test_instance_data.c +++ b/test/node-api/test_instance_data/test_instance_data.c @@ -1,6 +1,5 @@ #include #include -#define NAPI_EXPERIMENTAL #include #include "../../js-native-api/common.h" diff --git a/test/node-api/test_instance_data/test_ref_then_set.c b/test/node-api/test_instance_data/test_ref_then_set.c index a0df1e5b9f8a64..10c779d3241e0e 100644 --- a/test/node-api/test_instance_data/test_ref_then_set.c +++ b/test/node-api/test_instance_data/test_ref_then_set.c @@ -1,6 +1,5 @@ #include #include -#define NAPI_EXPERIMENTAL #include napi_value addon_new(napi_env env, napi_value exports, bool ref_first); diff --git a/test/node-api/test_instance_data/test_set_then_ref.c b/test/node-api/test_instance_data/test_set_then_ref.c index 6ebed2d1e86871..9a1b31aeed390b 100644 --- a/test/node-api/test_instance_data/test_set_then_ref.c +++ b/test/node-api/test_instance_data/test_set_then_ref.c @@ -1,6 +1,5 @@ #include #include -#define NAPI_EXPERIMENTAL #include napi_value addon_new(napi_env env, napi_value exports, bool ref_first); From 6f9f2c5de489884a162a5b92269e0066a49e5ad4 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Fri, 6 Mar 2020 16:13:00 -0800 Subject: [PATCH 041/108] test: warn when inspector process crashes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the subprocess being inspected hard crashes, there will be no information on the log, and the parent process will just wait until timeout. Logging the error signal when it happens can help developers understand failures faster. Signed-off-by: Matheus Marchini PR-URL: https://github.com/nodejs/node/pull/32133 Reviewed-By: Michaël Zasso Reviewed-By: Jiawen Geng Reviewed-By: Ruben Bridgewater Reviewed-By: James M Snell --- test/common/inspector-helper.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/common/inspector-helper.js b/test/common/inspector-helper.js index 42d6baed4410de..d430137746dd81 100644 --- a/test/common/inspector-helper.js +++ b/test/common/inspector-helper.js @@ -344,6 +344,9 @@ class NodeInstance extends EventEmitter { this._shutdownPromise = new Promise((resolve) => { this._process.once('exit', (exitCode, signal) => { + if (signal) { + console.error(`[err] child process crashed, signal ${signal}`); + } resolve({ exitCode, signal }); this._running = false; }); From e1347b411a9020b0bdda36364879212990ce8318 Mon Sep 17 00:00:00 2001 From: Andrew Neitsch Date: Wed, 4 Mar 2020 16:34:28 -0700 Subject: [PATCH 042/108] cli: allow --jitless V8 flag in NODE_OPTIONS This work is modeled on #30094 which allowed `--disallow-code-generation-from-strings` in `NODE_OPTIONS`. The `--jitless` v8 option has been supported since 12.0.0. As a v8 option, node automatically picks it up, but there have been a few issues that were resolved by simply telling users about the option: #26758, This PR: - allows `--jitless` in `NODE_OPTIONS` - documents `--jitless` - moves `--experimental-loader=module` to locally restore alphabetical order in option documentation Refs: https://github.com/nodejs/node/pull/30094 Refs: https://github.com/nodejs/node/issues/26758 Refs: https://github.com/nodejs/node/issues/28800 PR-URL: https://github.com/nodejs/node/pull/32100 Reviewed-By: Richard Lau Reviewed-By: Colin Ihrig Reviewed-By: Sam Roberts Reviewed-By: Gus Caplan Reviewed-By: Shelley Vohr Reviewed-By: David Carlier Reviewed-By: Anna Henningsen Reviewed-By: James M Snell Reviewed-By: Michael Dawson Reviewed-By: Ruben Bridgewater --- doc/api/cli.md | 30 +++++++++++++++++++------- doc/node.1 | 19 +++++++++++----- src/node_options.cc | 4 ++++ test/parallel/test-cli-node-options.js | 1 + 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index 8b45a4b5bb3b99..66cb665fa82e9f 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -170,6 +170,14 @@ added: v12.9.0 Enable experimental JSON support for the ES Module loader. +### `--experimental-loader=module` + + +Specify the `module` of a custom [experimental ECMAScript Module loader][]. +`module` may be either a path to a file, or an ECMAScript Module name. + ### `--experimental-modules` - -Specify the `module` of a custom [experimental ECMAScript Module loader][]. -`module` may be either a path to a file, or an ECMAScript Module name. - ### `--insecure-http-parser` + +Disable [runtime allocation of executable memory][jitless]. This may be +required on some platforms for security reasons. It can also reduce attack +surface on other platforms, but the performance impact may be severe. + +This flag is inherited from V8 and is subject to change upstream. It may +disappear in a non-semver-major release. + ### `--max-http-header-size=size` + +* Returns {string} + +Returns a string identifying the kernel version. + +On POSIX systems, the operating system release is determined by calling +[uname(3)][]. On Windows, `RtlGetVersion()` is used, and if it is not available, +`GetVersionExW()` will be used. See +https://en.wikipedia.org/wiki/Uname#Examples for more information. + ## OS Constants The following constants are exported by `os.constants`. diff --git a/lib/os.js b/lib/os.js index 2cce09211c3fe8..f533c3f18f13fd 100644 --- a/lib/os.js +++ b/lib/os.js @@ -46,8 +46,7 @@ const { getHostname: _getHostname, getInterfaceAddresses: _getInterfaceAddresses, getLoadAvg, - getOSRelease: _getOSRelease, - getOSType: _getOSType, + getOSInformation: _getOSInformation, getPriority: _getPriority, getTotalMem, getUserInfo, @@ -67,17 +66,25 @@ function getCheckedFunction(fn) { }); } +const [ + type, + version, + release +] = _getOSInformation(); + const getHomeDirectory = getCheckedFunction(_getHomeDirectory); const getHostname = getCheckedFunction(_getHostname); const getInterfaceAddresses = getCheckedFunction(_getInterfaceAddresses); -const getOSRelease = getCheckedFunction(_getOSRelease); -const getOSType = getCheckedFunction(_getOSType); +const getOSRelease = () => release; +const getOSType = () => type; +const getOSVersion = () => version; getFreeMem[SymbolToPrimitive] = () => getFreeMem(); getHostname[SymbolToPrimitive] = () => getHostname(); getHomeDirectory[SymbolToPrimitive] = () => getHomeDirectory(); getOSRelease[SymbolToPrimitive] = () => getOSRelease(); getOSType[SymbolToPrimitive] = () => getOSType(); +getOSVersion[SymbolToPrimitive] = () => getOSVersion(); getTotalMem[SymbolToPrimitive] = () => getTotalMem(); getUptime[SymbolToPrimitive] = () => getUptime(); @@ -283,6 +290,7 @@ module.exports = { tmpdir, totalmem: getTotalMem, type: getOSType, + version: getOSVersion, userInfo, uptime: getUptime, diff --git a/src/node_os.cc b/src/node_os.cc index 12a4ec3551a4db..f84c0f216e3dd9 100644 --- a/src/node_os.cc +++ b/src/node_os.cc @@ -76,7 +76,7 @@ static void GetHostname(const FunctionCallbackInfo& args) { } -static void GetOSType(const FunctionCallbackInfo& args) { +static void GetOSInformation(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); uv_utsname_t info; int err = uv_os_uname(&info); @@ -87,26 +87,16 @@ static void GetOSType(const FunctionCallbackInfo& args) { return args.GetReturnValue().SetUndefined(); } - args.GetReturnValue().Set( - String::NewFromUtf8(env->isolate(), info.sysname, NewStringType::kNormal) - .ToLocalChecked()); -} - - -static void GetOSRelease(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - uv_utsname_t info; - int err = uv_os_uname(&info); + // [sysname, version, release] + Local osInformation[] = { + String::NewFromUtf8(env->isolate(), info.sysname).ToLocalChecked(), + String::NewFromUtf8(env->isolate(), info.version).ToLocalChecked(), + String::NewFromUtf8(env->isolate(), info.release).ToLocalChecked() + }; - if (err != 0) { - CHECK_GE(args.Length(), 1); - env->CollectUVExceptionInfo(args[args.Length() - 1], err, "uv_os_uname"); - return args.GetReturnValue().SetUndefined(); - } - - args.GetReturnValue().Set( - String::NewFromUtf8(env->isolate(), info.release, NewStringType::kNormal) - .ToLocalChecked()); + args.GetReturnValue().Set(Array::New(env->isolate(), + osInformation, + arraysize(osInformation))); } @@ -398,8 +388,7 @@ void Initialize(Local target, env->SetMethod(target, "getTotalMem", GetTotalMemory); env->SetMethod(target, "getFreeMem", GetFreeMemory); env->SetMethod(target, "getCPUs", GetCPUInfo); - env->SetMethod(target, "getOSType", GetOSType); - env->SetMethod(target, "getOSRelease", GetOSRelease); + env->SetMethod(target, "getOSInformation", GetOSInformation); env->SetMethod(target, "getInterfaceAddresses", GetInterfaceAddresses); env->SetMethod(target, "getHomeDirectory", GetHomeDirectory); env->SetMethod(target, "getUserInfo", GetUserInfo); diff --git a/test/parallel/test-os.js b/test/parallel/test-os.js index 57b68ff8dea9ac..5ee0fb9ca8dadb 100644 --- a/test/parallel/test-os.js +++ b/test/parallel/test-os.js @@ -194,6 +194,10 @@ const home = os.homedir(); is.string(home); assert.ok(home.includes(path.sep)); +const version = os.version(); +assert.strictEqual(typeof version, 'string'); +assert(version); + if (common.isWindows && process.env.USERPROFILE) { assert.strictEqual(home, process.env.USERPROFILE); delete process.env.USERPROFILE; From 2388a40f5630b1895693408199252f1d3332246b Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 20 Feb 2020 17:58:32 +0800 Subject: [PATCH 056/108] src: make aliased_buffer.h self-contained aliased_buffer.h uses MultiplyWithOverflowCheck() implemented in util-inl.h PR-URL: https://github.com/nodejs/node/pull/31884 Reviewed-By: Anna Henningsen --- src/aliased_buffer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aliased_buffer.h b/src/aliased_buffer.h index b083fb68e69bd2..281c8fed581645 100644 --- a/src/aliased_buffer.h +++ b/src/aliased_buffer.h @@ -4,7 +4,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include -#include "util.h" +#include "util-inl.h" #include "v8.h" namespace node { From 5127c700d09a1b2d15a71f09c0916d64c5471dcd Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 20 Feb 2020 17:20:56 +0800 Subject: [PATCH 057/108] src: refactor debug category parsing Move the debug category parsing into a new EnabledDebugList class, so that it can be reused outside of Environment. PR-URL: https://github.com/nodejs/node/pull/31884 Reviewed-By: Anna Henningsen --- src/debug_utils-inl.h | 74 +++++++++++++++++++++++++++++++++++ src/debug_utils.cc | 29 ++++++++++++++ src/debug_utils.h | 90 ++++++++++++++++++++++++++++--------------- src/env-inl.h | 14 ------- src/env.cc | 29 +------------- src/env.h | 24 ++---------- src/node_env_var.cc | 1 + 7 files changed, 168 insertions(+), 93 deletions(-) diff --git a/src/debug_utils-inl.h b/src/debug_utils-inl.h index 2f6137700d8a37..5593957c5b2c6b 100644 --- a/src/debug_utils-inl.h +++ b/src/debug_utils-inl.h @@ -4,6 +4,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "debug_utils.h" +#include "env.h" #include @@ -90,6 +91,79 @@ void COLD_NOINLINE FPrintF(FILE* file, const char* format, Args&&... args) { FWrite(file, SPrintF(format, std::forward(args)...)); } +template +inline void FORCE_INLINE Debug(EnabledDebugList* list, + DebugCategory cat, + const char* format, + Args&&... args) { + if (!UNLIKELY(list->enabled(cat))) return; + FPrintF(stderr, format, std::forward(args)...); +} + +inline void FORCE_INLINE Debug(EnabledDebugList* list, + DebugCategory cat, + const char* message) { + if (!UNLIKELY(list->enabled(cat))) return; + FPrintF(stderr, "%s", message); +} + +template +inline void FORCE_INLINE +Debug(Environment* env, DebugCategory cat, const char* format, Args&&... args) { + Debug(env->enabled_debug_list(), cat, format, std::forward(args)...); +} + +inline void FORCE_INLINE Debug(Environment* env, + DebugCategory cat, + const char* message) { + Debug(env->enabled_debug_list(), cat, message); +} + +template +inline void Debug(Environment* env, + DebugCategory cat, + const std::string& format, + Args&&... args) { + Debug(env->enabled_debug_list(), + cat, + format.c_str(), + std::forward(args)...); +} + +// Used internally by the 'real' Debug(AsyncWrap*, ...) functions below, so that +// the FORCE_INLINE flag on them doesn't apply to the contents of this function +// as well. +// We apply COLD_NOINLINE to tell the compiler that it's not worth optimizing +// this function for speed and it should rather focus on keeping it out of +// hot code paths. In particular, we want to keep the string concatenating code +// out of the function containing the original `Debug()` call. +template +void COLD_NOINLINE UnconditionalAsyncWrapDebug(AsyncWrap* async_wrap, + const char* format, + Args&&... args) { + Debug(async_wrap->env(), + static_cast(async_wrap->provider_type()), + async_wrap->diagnostic_name() + " " + format + "\n", + std::forward(args)...); +} + +template +inline void FORCE_INLINE Debug(AsyncWrap* async_wrap, + const char* format, + Args&&... args) { + DCHECK_NOT_NULL(async_wrap); + DebugCategory cat = static_cast(async_wrap->provider_type()); + if (!UNLIKELY(async_wrap->env()->enabled_debug_list()->enabled(cat))) return; + UnconditionalAsyncWrapDebug(async_wrap, format, std::forward(args)...); +} + +template +inline void FORCE_INLINE Debug(AsyncWrap* async_wrap, + const std::string& format, + Args&&... args) { + Debug(async_wrap, format.c_str(), std::forward(args)...); +} + } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/debug_utils.cc b/src/debug_utils.cc index 4553b642b65837..0f25a70e787bce 100644 --- a/src/debug_utils.cc +++ b/src/debug_utils.cc @@ -1,5 +1,6 @@ #include "debug_utils-inl.h" // NOLINT(build/include) #include "env-inl.h" +#include "node_internals.h" #ifdef __POSIX__ #if defined(__linux__) @@ -54,6 +55,34 @@ namespace node { +void EnabledDebugList::Parse(Environment* env) { + std::string cats; + credentials::SafeGetenv("NODE_DEBUG_NATIVE", &cats, env); + Parse(cats, true); +} + +void EnabledDebugList::Parse(const std::string& cats, bool enabled) { + std::string debug_categories = cats; + while (!debug_categories.empty()) { + std::string::size_type comma_pos = debug_categories.find(','); + std::string wanted = ToLower(debug_categories.substr(0, comma_pos)); + +#define V(name) \ + { \ + static const std::string available_category = ToLower(#name); \ + if (available_category.find(wanted) != std::string::npos) \ + set_enabled(DebugCategory::name, enabled); \ + } + + DEBUG_CATEGORY_NAMES(V) +#undef V + + if (comma_pos == std::string::npos) break; + // Use everything after the `,` as the list for the next iteration. + debug_categories = debug_categories.substr(comma_pos + 1); + } +} + #ifdef __POSIX__ #if HAVE_EXECINFO_H class PosixSymbolDebuggingContext final : public NativeSymbolDebuggingContext { diff --git a/src/debug_utils.h b/src/debug_utils.h index c745cbe0a1a74b..527e3549fa0e43 100644 --- a/src/debug_utils.h +++ b/src/debug_utils.h @@ -4,7 +4,6 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "async_wrap.h" -#include "env.h" #include #include @@ -21,6 +20,7 @@ #endif namespace node { +class Environment; template inline std::string ToString(const T& value); @@ -36,31 +36,71 @@ template inline void FPrintF(FILE* file, const char* format, Args&&... args); void FWrite(FILE* file, const std::string& str); +// Listing the AsyncWrap provider types first enables us to cast directly +// from a provider type to a debug category. +#define DEBUG_CATEGORY_NAMES(V) \ + NODE_ASYNC_PROVIDER_TYPES(V) \ + V(INSPECTOR_SERVER) \ + V(INSPECTOR_PROFILER) \ + V(WASI) + +enum class DebugCategory { +#define V(name) name, + DEBUG_CATEGORY_NAMES(V) +#undef V + CATEGORY_COUNT +}; + +class EnabledDebugList { + public: + bool enabled(DebugCategory category) const { + DCHECK_GE(static_cast(category), 0); + DCHECK_LT(static_cast(category), + static_cast(DebugCategory::CATEGORY_COUNT)); + return enabled_[static_cast(category)]; + } + + // Uses NODE_DEBUG_NATIVE to initialize the categories. When env is not a + // nullptr, the environment variables set in the Environment are used. + // Otherwise the system environment variables are used. + void Parse(Environment* env); + + private: + // Set all categories matching cats to the value of enabled. + void Parse(const std::string& cats, bool enabled); + void set_enabled(DebugCategory category, bool enabled) { + DCHECK_GE(static_cast(category), 0); + DCHECK_LT(static_cast(category), + static_cast(DebugCategory::CATEGORY_COUNT)); + enabled_[static_cast(category)] = true; + } + + bool enabled_[static_cast(DebugCategory::CATEGORY_COUNT)] = {false}; +}; + template -inline void FORCE_INLINE Debug(Environment* env, +inline void FORCE_INLINE Debug(EnabledDebugList* list, DebugCategory cat, const char* format, - Args&&... args) { - if (!UNLIKELY(env->debug_enabled(cat))) - return; - FPrintF(stderr, format, std::forward(args)...); -} + Args&&... args); + +inline void FORCE_INLINE Debug(EnabledDebugList* list, + DebugCategory cat, + const char* message); + +template +inline void FORCE_INLINE +Debug(Environment* env, DebugCategory cat, const char* format, Args&&... args); inline void FORCE_INLINE Debug(Environment* env, DebugCategory cat, - const char* message) { - if (!UNLIKELY(env->debug_enabled(cat))) - return; - FPrintF(stderr, "%s", message); -} + const char* message); template inline void Debug(Environment* env, DebugCategory cat, const std::string& format, - Args&&... args) { - Debug(env, cat, format.c_str(), std::forward(args)...); -} + Args&&... args); // Used internally by the 'real' Debug(AsyncWrap*, ...) functions below, so that // the FORCE_INLINE flag on them doesn't apply to the contents of this function @@ -72,31 +112,17 @@ inline void Debug(Environment* env, template void COLD_NOINLINE UnconditionalAsyncWrapDebug(AsyncWrap* async_wrap, const char* format, - Args&&... args) { - Debug(async_wrap->env(), - static_cast(async_wrap->provider_type()), - async_wrap->diagnostic_name() + " " + format + "\n", - std::forward(args)...); -} + Args&&... args); template inline void FORCE_INLINE Debug(AsyncWrap* async_wrap, const char* format, - Args&&... args) { - DCHECK_NOT_NULL(async_wrap); - DebugCategory cat = - static_cast(async_wrap->provider_type()); - if (!UNLIKELY(async_wrap->env()->debug_enabled(cat))) - return; - UnconditionalAsyncWrapDebug(async_wrap, format, std::forward(args)...); -} + Args&&... args); template inline void FORCE_INLINE Debug(AsyncWrap* async_wrap, const std::string& format, - Args&&... args) { - Debug(async_wrap, format.c_str(), std::forward(args)...); -} + Args&&... args); // Debug helper for inspecting the currently running `node` executable. class NativeSymbolDebuggingContext { diff --git a/src/env-inl.h b/src/env-inl.h index 88c027107020b6..558e257c72ec5a 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -606,20 +606,6 @@ inline void Environment::set_http2_state( http2_state_ = std::move(buffer); } -bool Environment::debug_enabled(DebugCategory category) const { - DCHECK_GE(static_cast(category), 0); - DCHECK_LT(static_cast(category), - static_cast(DebugCategory::CATEGORY_COUNT)); - return debug_enabled_[static_cast(category)]; -} - -void Environment::set_debug_enabled(DebugCategory category, bool enabled) { - DCHECK_GE(static_cast(category), 0); - DCHECK_LT(static_cast(category), - static_cast(DebugCategory::CATEGORY_COUNT)); - debug_enabled_[static_cast(category)] = enabled; -} - inline AliasedFloat64Array* Environment::fs_stats_field_array() { return &fs_stats_field_array_; } diff --git a/src/env.cc b/src/env.cc index 64c60f3ab16cc7..850b96d6857fa2 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1,6 +1,7 @@ #include "env.h" #include "async_wrap.h" +#include "debug_utils-inl.h" #include "memory_tracker-inl.h" #include "node_buffer.h" #include "node_context_data.h" @@ -315,6 +316,7 @@ Environment::Environment(IsolateData* isolate_data, Context::Scope context_scope(context); set_env_vars(per_process::system_environment); + enabled_debug_list_.Parse(this); // We create new copies of the per-Environment option sets, so that it is // easier to modify them after Environment creation. The defaults are @@ -375,10 +377,6 @@ Environment::Environment(IsolateData* isolate_data, // By default, always abort when --abort-on-uncaught-exception was passed. should_abort_on_uncaught_toggle_[0] = 1; - std::string debug_cats; - credentials::SafeGetenv("NODE_DEBUG_NATIVE", &debug_cats, this); - set_debug_categories(debug_cats, true); - if (options_->no_force_async_hooks_checks) { async_hooks_.no_force_checks(); } @@ -867,29 +865,6 @@ Local Environment::GetNow() { return Number::New(isolate(), static_cast(now)); } -void Environment::set_debug_categories(const std::string& cats, bool enabled) { - std::string debug_categories = cats; - while (!debug_categories.empty()) { - std::string::size_type comma_pos = debug_categories.find(','); - std::string wanted = ToLower(debug_categories.substr(0, comma_pos)); - -#define V(name) \ - { \ - static const std::string available_category = ToLower(#name); \ - if (available_category.find(wanted) != std::string::npos) \ - set_debug_enabled(DebugCategory::name, enabled); \ - } - - DEBUG_CATEGORY_NAMES(V) -#undef V - - if (comma_pos == std::string::npos) - break; - // Use everything after the `,` as the list for the next iteration. - debug_categories = debug_categories.substr(comma_pos + 1); - } -} - void CollectExceptionInfo(Environment* env, Local obj, int errorno, diff --git a/src/env.h b/src/env.h index 3bed3c64196ee0..7fbd42e71a1c64 100644 --- a/src/env.h +++ b/src/env.h @@ -29,6 +29,7 @@ #include "inspector_agent.h" #include "inspector_profiler.h" #endif +#include "debug_utils.h" #include "handle_wrap.h" #include "node.h" #include "node_binding.h" @@ -550,20 +551,7 @@ struct ContextInfo { bool is_default = false; }; -// Listing the AsyncWrap provider types first enables us to cast directly -// from a provider type to a debug category. -#define DEBUG_CATEGORY_NAMES(V) \ - NODE_ASYNC_PROVIDER_TYPES(V) \ - V(INSPECTOR_SERVER) \ - V(INSPECTOR_PROFILER) \ - V(WASI) - -enum class DebugCategory { -#define V(name) name, - DEBUG_CATEGORY_NAMES(V) -#undef V - CATEGORY_COUNT -}; +class EnabledDebugList; // A unique-pointer-ish object that is compatible with the JS engine's // ArrayBuffer::Allocator. @@ -1024,9 +1012,7 @@ class Environment : public MemoryRetainer { inline http2::Http2State* http2_state() const; inline void set_http2_state(std::unique_ptr state); - inline bool debug_enabled(DebugCategory category) const; - inline void set_debug_enabled(DebugCategory category, bool enabled); - void set_debug_categories(const std::string& cats, bool enabled); + EnabledDebugList* enabled_debug_list() { return &enabled_debug_list_; } inline AliasedFloat64Array* fs_stats_field_array(); inline AliasedBigUint64Array* fs_stats_field_bigint_array(); @@ -1382,9 +1368,7 @@ class Environment : public MemoryRetainer { bool http_parser_buffer_in_use_ = false; std::unique_ptr http2_state_; - bool debug_enabled_[static_cast(DebugCategory::CATEGORY_COUNT)] = { - false}; - + EnabledDebugList enabled_debug_list_; AliasedFloat64Array fs_stats_field_array_; AliasedBigUint64Array fs_stats_field_bigint_array_; diff --git a/src/node_env_var.cc b/src/node_env_var.cc index 6928b595ae5652..208bb6981c2dc8 100644 --- a/src/node_env_var.cc +++ b/src/node_env_var.cc @@ -1,3 +1,4 @@ +#include "debug_utils-inl.h" #include "env-inl.h" #include "node_errors.h" #include "node_process.h" From 6aa797b5466bd3a6a326ff3f98862de16355a7da Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 20 Feb 2020 17:22:48 +0800 Subject: [PATCH 058/108] src: implement per-process native Debug() printer This patch adds a per-process native Debug() printer that can be called when an Environment is not available. PR-URL: https://github.com/nodejs/node/pull/31884 Reviewed-By: Anna Henningsen --- src/debug_utils-inl.h | 14 ++++++++++++++ src/debug_utils.cc | 3 +++ src/debug_utils.h | 10 ++++++++++ src/node.cc | 4 ++++ 4 files changed, 31 insertions(+) diff --git a/src/debug_utils-inl.h b/src/debug_utils-inl.h index 5593957c5b2c6b..ae2d2046486466 100644 --- a/src/debug_utils-inl.h +++ b/src/debug_utils-inl.h @@ -164,6 +164,20 @@ inline void FORCE_INLINE Debug(AsyncWrap* async_wrap, Debug(async_wrap, format.c_str(), std::forward(args)...); } +namespace per_process { + +template +inline void FORCE_INLINE Debug(DebugCategory cat, + const char* format, + Args&&... args) { + Debug(&enabled_debug_list, cat, format, std::forward(args)...); +} + +inline void FORCE_INLINE Debug(DebugCategory cat, const char* message) { + Debug(&enabled_debug_list, cat, message); +} + +} // namespace per_process } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/debug_utils.cc b/src/debug_utils.cc index 0f25a70e787bce..a601c5ecf40ea9 100644 --- a/src/debug_utils.cc +++ b/src/debug_utils.cc @@ -54,6 +54,9 @@ #endif // _WIN32 namespace node { +namespace per_process { +EnabledDebugList enabled_debug_list; +} void EnabledDebugList::Parse(Environment* env) { std::string cats; diff --git a/src/debug_utils.h b/src/debug_utils.h index 527e3549fa0e43..200f1cf07cd27b 100644 --- a/src/debug_utils.h +++ b/src/debug_utils.h @@ -161,6 +161,16 @@ class NativeSymbolDebuggingContext { void CheckedUvLoopClose(uv_loop_t* loop); void PrintLibuvHandleInformation(uv_loop_t* loop, FILE* stream); +namespace per_process { +extern EnabledDebugList enabled_debug_list; + +template +inline void FORCE_INLINE Debug(DebugCategory cat, + const char* format, + Args&&... args); + +inline void FORCE_INLINE Debug(DebugCategory cat, const char* message); +} // namespace per_process } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/node.cc b/src/node.cc index 6b95796e1269b3..1fec85aa793dc4 100644 --- a/src/node.cc +++ b/src/node.cc @@ -916,6 +916,10 @@ void Init(int* argc, } InitializationResult InitializeOncePerProcess(int argc, char** argv) { + // Initialized the enabled list for Debug() calls with system + // environment variables. + per_process::enabled_debug_list.Parse(nullptr); + atexit(ResetStdio); PlatformInit(); From bb41383bdca096e0fb499cd30398cbd07fa66746 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 20 Feb 2020 17:57:55 +0800 Subject: [PATCH 059/108] tools: use per-process native Debug() printer in mkcodecache PR-URL: https://github.com/nodejs/node/pull/31884 Reviewed-By: Anna Henningsen --- node.gyp | 20 ++++++++++++++++++++ src/debug_utils.h | 1 + tools/code_cache/cache_builder.cc | 25 ++++++++++--------------- tools/code_cache/mkcodecache.cc | 3 +++ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/node.gyp b/node.gyp index 86c953ca9a1736..607e6cf897f4e5 100644 --- a/node.gyp +++ b/node.gyp @@ -1258,6 +1258,16 @@ ], 'conditions': [ + [ 'node_use_openssl=="true"', { + 'defines': [ + 'HAVE_OPENSSL=1', + ], + }], + ['v8_enable_inspector==1', { + 'defines': [ + 'HAVE_INSPECTOR=1', + ], + }], ['OS=="win"', { 'libraries': [ 'dbghelp.lib', @@ -1302,6 +1312,16 @@ ], 'conditions': [ + [ 'node_use_openssl=="true"', { + 'defines': [ + 'HAVE_OPENSSL=1', + ], + }], + ['v8_enable_inspector==1', { + 'defines': [ + 'HAVE_INSPECTOR=1', + ], + }], ['OS=="win"', { 'libraries': [ 'Dbghelp.lib', diff --git a/src/debug_utils.h b/src/debug_utils.h index 200f1cf07cd27b..b654159ac2a24e 100644 --- a/src/debug_utils.h +++ b/src/debug_utils.h @@ -42,6 +42,7 @@ void FWrite(FILE* file, const std::string& str); NODE_ASYNC_PROVIDER_TYPES(V) \ V(INSPECTOR_SERVER) \ V(INSPECTOR_PROFILER) \ + V(CODE_CACHE) \ V(WASI) enum class DebugCategory { diff --git a/tools/code_cache/cache_builder.cc b/tools/code_cache/cache_builder.cc index 8210355c4c37e2..28d61a6c70c467 100644 --- a/tools/code_cache/cache_builder.cc +++ b/tools/code_cache/cache_builder.cc @@ -1,4 +1,5 @@ #include "cache_builder.h" +#include "debug_utils-inl.h" #include "node_native_module.h" #include "util.h" @@ -67,8 +68,7 @@ static void GetInitializer(const std::string& id, std::stringstream& ss) { } static std::string GenerateCodeCache( - const std::map& data, - bool log_progress) { + const std::map& data) { std::stringstream ss; ss << R"(#include #include "node_native_module_env.h" @@ -89,11 +89,13 @@ const bool has_code_cache = true; total += cached_data->length; std::string def = GetDefinition(id, cached_data->length, cached_data->data); ss << def << "\n\n"; - if (log_progress) { - std::cout << "Generated cache for " << id - << ", size = " << FormatSize(cached_data->length) - << ", total = " << FormatSize(total) << "\n"; - } + std::string size_str = FormatSize(cached_data->length); + std::string total_str = FormatSize(total); + per_process::Debug(DebugCategory::CODE_CACHE, + "Generated cache for %s, size = %s, total = %s\n", + id.c_str(), + size_str.c_str(), + total_str.c_str()); } ss << R"(void NativeModuleEnv::InitializeCodeCache() { @@ -142,14 +144,7 @@ std::string CodeCacheBuilder::Generate(Local context) { } } - char env_buf[32]; - size_t env_size = sizeof(env_buf); - int ret = uv_os_getenv("NODE_DEBUG", env_buf, &env_size); - bool log_progress = false; - if (ret == 0 && strcmp(env_buf, "mkcodecache") == 0) { - log_progress = true; - } - return GenerateCodeCache(data, log_progress); + return GenerateCodeCache(data); } } // namespace native_module diff --git a/tools/code_cache/mkcodecache.cc b/tools/code_cache/mkcodecache.cc index e5b43a44b8d0d6..34af7bc61ba374 100644 --- a/tools/code_cache/mkcodecache.cc +++ b/tools/code_cache/mkcodecache.cc @@ -6,6 +6,7 @@ #include #include "cache_builder.h" +#include "debug_utils-inl.h" #include "libplatform/libplatform.h" #include "v8.h" @@ -40,6 +41,8 @@ int main(int argc, char* argv[]) { return 1; } + node::per_process::enabled_debug_list.Parse(nullptr); + std::unique_ptr platform = v8::platform::NewDefaultPlatform(); v8::V8::InitializePlatform(platform.get()); v8::V8::Initialize(); From 188f1d275f96ea957e63883f488be8daedecec03 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 7 Mar 2020 17:04:15 -0800 Subject: [PATCH 060/108] test: improve test-debug-usage test-debug-usage fails if run with `--trace-warnings` because the regular expression checking does not allow for the resulting stack trace from the deprecation warning. The test also only tests two parts of the three-part usage message. Improve the test so that it passes with `--trace-warnings` and verifies all three parts of the usage message. Signed-off-by: Rich Trott PR-URL: https://github.com/nodejs/node/pull/32141 Reviewed-By: Anna Henningsen Reviewed-By: Ruben Bridgewater --- test/parallel/test-debug-usage.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/parallel/test-debug-usage.js b/test/parallel/test-debug-usage.js index 4582ac7bb5b6e2..eb9594f236b35c 100644 --- a/test/parallel/test-debug-usage.js +++ b/test/parallel/test-debug-usage.js @@ -11,8 +11,7 @@ child.stderr.setEncoding('utf8'); const expectedLines = [ /^\(node:\d+\) \[DEP0068\] DeprecationWarning:/, - /^Usage: .*node.* debug script\.js$/, - /^ .*node.* debug :$/ + /Usage: .*node.* debug script\.js\r?\n .*node.* debug :\r?\n .*node.* debug -p \r?\n$/, ]; let actualUsageMessage = ''; @@ -21,11 +20,10 @@ child.stderr.on('data', function(data) { }); child.on('exit', common.mustCall(function(code) { - const outputLines = actualUsageMessage.split('\n'); assert.strictEqual(code, 1); for (let i = 0; i < expectedLines.length; i++) assert.ok( - expectedLines[i].test(outputLines[i]), - `${outputLines[i]} did not match ${expectedLines[i]}` + expectedLines[i].test(actualUsageMessage), + `${actualUsageMessage} did not match ${expectedLines[i]}` ); })); From 2d39369ee506b4e3fa52a8ae6a0b583f105faf1a Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 7 Mar 2020 17:35:00 -0800 Subject: [PATCH 061/108] doc: remove personal pronoun usage in addons.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per our style guide, avoid personal pronouns (I, you, we, etc.) in reference documentation. PR-URL: https://github.com/nodejs/node/pull/32142 Reviewed-By: Tobias Nießen Reviewed-By: Ruben Bridgewater Reviewed-By: Trivikram Kamat --- doc/api/addons.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/addons.md b/doc/api/addons.md index 93f99afbeee7e7..e6b5704e8fbdd5 100644 --- a/doc/api/addons.md +++ b/doc/api/addons.md @@ -8,8 +8,8 @@ Addons are dynamically-linked shared objects written in C++. The Addons provide an interface between JavaScript and C/C++ libraries. There are three options for implementing Addons: N-API, nan, or direct -use of internal V8, libuv and Node.js libraries. Unless you need direct -access to functionality which is not exposed by N-API, use N-API. +use of internal V8, libuv and Node.js libraries. Unless there is a need for +direct access to functionality which is not exposed by N-API, use N-API. Refer to [C/C++ Addons with N-API](n-api.html) for more information on N-API. When not using N-API, implementing Addons is complicated, From fa99fb2eacfb3c9a9e7444fb944417705c725bd4 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 7 Mar 2020 17:35:00 -0800 Subject: [PATCH 062/108] doc: remove personal pronoun usage in errors.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per our style guide, avoid personal pronouns (I, you, we, etc.) in reference documentation. PR-URL: https://github.com/nodejs/node/pull/32142 Reviewed-By: Tobias Nießen Reviewed-By: Ruben Bridgewater Reviewed-By: Trivikram Kamat --- doc/api/errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/errors.md b/doc/api/errors.md index 046f8c3133c5dd..0001697e768b7b 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1880,7 +1880,7 @@ added: v13.10.0 --> The TLS socket must be connected and securily established. Ensure the 'secure' -event is emitted, before you continue. +event is emitted before continuing. ### `ERR_TLS_INVALID_PROTOCOL_METHOD` From 618b389b6a5b1673750cf6b386aebfb68d48193c Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 7 Mar 2020 17:35:00 -0800 Subject: [PATCH 063/108] doc: remove personal pronoun usage in fs.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per our style guide, avoid personal pronouns (I, you, we, etc.) in reference documentation. PR-URL: https://github.com/nodejs/node/pull/32142 Reviewed-By: Tobias Nießen Reviewed-By: Ruben Bridgewater Reviewed-By: Trivikram Kamat --- doc/api/fs.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index a68fb1e1c8f862..8a2d942eac41dc 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -1720,9 +1720,9 @@ By default, the stream will not emit a `'close'` event after it has been destroyed. This is the opposite of the default for other `Readable` streams. Set the `emitClose` option to `true` to change this behavior. -By providing the `fs` option it is possible to override the corresponding `fs` -implementations for `open`, `read` and `close`. When providing the `fs` option, -you must override `open`, `close` and `read`. +By providing the `fs` option, it is possible to override the corresponding `fs` +implementations for `open`, `read`, and `close`. When providing the `fs` option, +overrides for `open`, `read`, and `close` are required. ```js const fs = require('fs'); @@ -1816,8 +1816,8 @@ Set the `emitClose` option to `true` to change this behavior. By providing the `fs` option it is possible to override the corresponding `fs` implementations for `open`, `write`, `writev` and `close`. Overriding `write()` without `writev()` can reduce performance as some optimizations (`_writev()`) -will be disabled. When providing the `fs` option, you must override `open`, -`close` and at least one of `write` and `writev`. +will be disabled. When providing the `fs` option, overrides for `open`, +`close`, and at least one of `write` and `writev` are required. Like [`ReadStream`][], if `fd` is specified, [`WriteStream`][] will ignore the `path` argument and will use the specified file descriptor. This means that no @@ -4237,8 +4237,8 @@ in that they provide an object oriented API for working with files. If a `FileHandle` is not closed using the `filehandle.close()` method, it might automatically close the file descriptor and will emit a process warning, thereby helping to prevent memory leaks. -Please do not rely on this behavior in your code because it is unreliable and -your file may not be closed. Instead, always explicitly close `FileHandle`s. +Please do not rely on this behavior because it is unreliable and +the file may not be closed. Instead, always explicitly close `FileHandle`s. Node.js may change this behavior in the future. Instances of the `FileHandle` object are created internally by the From 7de4dfba79cc58906dfc5874ba60388f23e54d19 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 7 Mar 2020 17:35:00 -0800 Subject: [PATCH 064/108] doc: remove personal pronoun usage in policy.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per our style guide, avoid personal pronouns (I, you, we, etc.) in reference documentation. PR-URL: https://github.com/nodejs/node/pull/32142 Reviewed-By: Tobias Nießen Reviewed-By: Ruben Bridgewater Reviewed-By: Trivikram Kamat --- doc/api/policy.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/policy.md b/doc/api/policy.md index bf4cc214552eff..35ea48b40b410e 100644 --- a/doc/api/policy.md +++ b/doc/api/policy.md @@ -166,9 +166,9 @@ only with care after auditing a module to ensure its behavior is valid. #### Example: Patched Dependency -Since a dependency can be redirected, you can provide attenuated or modified -forms of dependencies as fits your application. For example, you could log -data about timing of function durations by wrapping the original: +Redirected dependencies can provide attenuated or modified functionality as fits +the application. For example, log data about timing of function durations by +wrapping the original: ```js const original = require('fn'); From 1e9a2516df1725845d0d444bb54b0e667e0e2d17 Mon Sep 17 00:00:00 2001 From: Sam Roberts Date: Fri, 6 Mar 2020 16:27:19 -0800 Subject: [PATCH 065/108] src: use C++ style for struct with initializers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes warning on clang 11: In file included from ../../src/node_http2.cc:6: ../../src/node_http2.h:508:15: warning: anonymous non-C-compatible type given name for linkage purposes by typedef declaration; add a tag name here [-Wnon-c-typedef-for-linkage] typedef struct { ^ SessionJSFields ../../src/node_http2.h:512:33: note: type is not C-compatible due to this default member initializer uint32_t max_invalid_frames = 1000; ^~~~ ../../src/node_http2.h:514:3: note: type is given name 'SessionJSFields' for linkage purposes by this typedef declaration } SessionJSFields; ^ PR-URL: https://github.com/nodejs/node/pull/32134 Reviewed-By: Anna Henningsen Reviewed-By: Tobias Nießen Reviewed-By: Colin Ihrig Reviewed-By: David Carlier --- src/node_http2.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node_http2.h b/src/node_http2.h index 690a97027848c8..319d50e4ce39be 100644 --- a/src/node_http2.h +++ b/src/node_http2.h @@ -505,13 +505,13 @@ class Http2Stream::Provider::Stream : public Http2Stream::Provider { void* user_data); }; -typedef struct { +struct SessionJSFields { uint8_t bitfield; uint8_t priority_listener_count; uint8_t frame_error_listener_count; uint32_t max_invalid_frames = 1000; uint32_t max_rejected_streams = 100; -} SessionJSFields; +}; // Indices for js_fields_, which serves as a way to communicate data with JS // land fast. In particular, we store information about the number/presence From 3c06316679172f4e2239b45cfc313106860491e5 Mon Sep 17 00:00:00 2001 From: Matheus Marchini Date: Sat, 7 Mar 2020 12:14:18 -0800 Subject: [PATCH 066/108] build: workaround for gclient python3 issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gclient doesn't support Python 3 yet. To workaround that problem, add an enviroment variable to override the Python version used by ./configure. Signed-off-by: Matheus Marchini PR-URL: https://github.com/nodejs/node/pull/32140 Reviewed-By: Anna Henningsen Reviewed-By: Michaël Zasso Reviewed-By: Christian Clauss --- configure | 1 + 1 file changed, 1 insertion(+) diff --git a/configure b/configure index 39decd9a55ccc7..bc0a01d985520b 100755 --- a/configure +++ b/configure @@ -7,6 +7,7 @@ # pyenv will alert which shims are available and then will fail the build. _=[ 'exec' '/bin/sh' '-c' ''' test ${TRAVIS} && exec python "$0" "$@" # workaround for pyenv on Travis CI +test ${FORCE_PYTHON2} && exec python2 "$0" "$@" # workaround for gclient which python3.8 >/dev/null && exec python3.8 "$0" "$@" which python3.7 >/dev/null && exec python3.7 "$0" "$@" which python3.6 >/dev/null && exec python3.6 "$0" "$@" From bc1e3575d4f02af889abc76aaf869e822e5334df Mon Sep 17 00:00:00 2001 From: Gerhard Stoebich <18708370+Flarna@users.noreply.github.com> Date: Tue, 3 Mar 2020 08:52:27 +0100 Subject: [PATCH 067/108] doc: change worker.takeHeapSnapshot to getHeapSnapshot Adapt doc to match implementation which exports getHeapSnapshot(). PR-URL: https://github.com/nodejs/node/pull/32061 Refs: https://github.com/nodejs/node/pull/31569 Refs: https://github.com/nodejs/node/blob/987a67339518d0380177a2e589f2bbd274230d0e/lib/internal/worker.js#L323 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: Shelley Vohr Reviewed-By: Matheus Marchini Reviewed-By: James M Snell Reviewed-By: Luigi Pinca Reviewed-By: Rich Trott Reviewed-By: Ruben Bridgewater --- doc/api/worker_threads.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index e773949b49df52..406728995c9a94 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -612,6 +612,21 @@ added: v10.5.0 The `'online'` event is emitted when the worker thread has started executing JavaScript code. +### `worker.getHeapSnapshot()` + + +* Returns: {Promise} A promise for a Readable Stream containing + a V8 heap snapshot + +Returns a readable stream for a V8 snapshot of the current state of the Worker. +See [`v8.getHeapSnapshot()`][] for more details. + +If the Worker thread is no longer running, which may occur before the +[`'exit'` event][] is emitted, the returned `Promise` will be rejected +immediately with an [`ERR_WORKER_NOT_RUNNING`][] error. + ### `worker.postMessage(value[, transferList])` - -* Returns: {Promise} A promise for a Readable Stream containing - a V8 heap snapshot - -Returns a readable stream for a V8 snapshot of the current state of the Worker. -See [`v8.getHeapSnapshot()`][] for more details. - -If the Worker thread is no longer running, which may occur before the -[`'exit'` event][] is emitted, the returned `Promise` will be rejected -immediately with an [`ERR_WORKER_NOT_RUNNING`][] error. - ### `worker.terminate()` ## Technology Sponsors diff --git a/tools/node_modules/eslint/conf/default-cli-options.js b/tools/node_modules/eslint/conf/default-cli-options.js index 0f7b377fb362ec..e09a829d17cee2 100644 --- a/tools/node_modules/eslint/conf/default-cli-options.js +++ b/tools/node_modules/eslint/conf/default-cli-options.js @@ -12,7 +12,7 @@ module.exports = { useEslintrc: true, envs: [], globals: [], - extensions: [".js"], + extensions: null, ignore: true, ignorePath: void 0, cache: false, diff --git a/tools/node_modules/eslint/lib/cli-engine/cascading-config-array-factory.js b/tools/node_modules/eslint/lib/cli-engine/cascading-config-array-factory.js index 52703a873bb67e..f91dea4c448276 100644 --- a/tools/node_modules/eslint/lib/cli-engine/cascading-config-array-factory.js +++ b/tools/node_modules/eslint/lib/cli-engine/cascading-config-array-factory.js @@ -26,6 +26,7 @@ const os = require("os"); const path = require("path"); const { validateConfigArray } = require("../shared/config-validator"); +const { emitDeprecationWarning } = require("../shared/deprecation-warnings"); const { ConfigArrayFactory } = require("./config-array-factory"); const { ConfigArray, ConfigDependency, IgnorePattern } = require("./config-array"); const loadRules = require("./load-rules"); @@ -105,6 +106,7 @@ function createBaseConfigArray({ */ if (rulePaths && rulePaths.length > 0) { baseConfigArray.push({ + type: "config", name: "--rulesdir", filePath: "", plugins: { @@ -290,10 +292,11 @@ class CascadingConfigArrayFactory { /** * Load and normalize config files from the ancestor directories. * @param {string} directoryPath The path to a leaf directory. + * @param {boolean} configsExistInSubdirs `true` if configurations exist in subdirectories. * @returns {ConfigArray} The loaded config. * @private */ - _loadConfigInAncestors(directoryPath) { + _loadConfigInAncestors(directoryPath, configsExistInSubdirs = false) { const { baseConfigArray, configArrayFactory, @@ -320,6 +323,16 @@ class CascadingConfigArrayFactory { // Consider this is root. if (directoryPath === homePath && cwd !== homePath) { debug("Stop traversing because of considered root."); + if (configsExistInSubdirs) { + const filePath = ConfigArrayFactory.getPathToConfigFileInDirectory(directoryPath); + + if (filePath) { + emitDeprecationWarning( + filePath, + "ESLINT_PERSONAL_CONFIG_SUPPRESS" + ); + } + } return this._cacheConfig(directoryPath, baseConfigArray); } @@ -344,7 +357,10 @@ class CascadingConfigArrayFactory { // Load from the ancestors and merge it. const parentPath = path.dirname(directoryPath); const parentConfigArray = parentPath && parentPath !== directoryPath - ? this._loadConfigInAncestors(parentPath) + ? this._loadConfigInAncestors( + parentPath, + configsExistInSubdirs || configArray.length > 0 + ) : baseConfigArray; if (configArray.length > 0) { @@ -400,12 +416,29 @@ class CascadingConfigArrayFactory { configArray.every(c => !c.filePath) && cliConfigArray.every(c => !c.filePath) // `--config` option can be a file. ) { - debug("Loading the config file of the home directory."); + const homePath = os.homedir(); + + debug("Loading the config file of the home directory:", homePath); - finalConfigArray = configArrayFactory.loadInDirectory( - os.homedir(), - { name: "PersonalConfig", parent: finalConfigArray } + const personalConfigArray = configArrayFactory.loadInDirectory( + homePath, + { name: "PersonalConfig" } ); + + if ( + personalConfigArray.length > 0 && + !directoryPath.startsWith(homePath) + ) { + const lastElement = + personalConfigArray[personalConfigArray.length - 1]; + + emitDeprecationWarning( + lastElement.filePath, + "ESLINT_PERSONAL_CONFIG_LOAD" + ); + } + + finalConfigArray = finalConfigArray.concat(personalConfigArray); } // Apply CLI options. diff --git a/tools/node_modules/eslint/lib/cli-engine/cli-engine.js b/tools/node_modules/eslint/lib/cli-engine/cli-engine.js index 22336a91de2909..6a0352399b7738 100644 --- a/tools/node_modules/eslint/lib/cli-engine/cli-engine.js +++ b/tools/node_modules/eslint/lib/cli-engine/cli-engine.js @@ -57,7 +57,7 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]); * @property {string} configFile The configuration file to use. * @property {string} cwd The value to use for the current working directory. * @property {string[]} envs An array of environments to load. - * @property {string[]} extensions An array of file extensions to check. + * @property {string[]|null} extensions An array of file extensions to check. * @property {boolean|Function} fix Execute in autofix mode. If a function, should return a boolean. * @property {string[]} fixTypes Array of rule types to apply fixes for. * @property {string[]} globals An array of global variables to declare. @@ -201,7 +201,7 @@ function calculateStatsPerRun(results) { * @param {boolean} config.fix If `true` then it does fix. * @param {boolean} config.allowInlineConfig If `true` then it uses directive comments. * @param {boolean} config.reportUnusedDisableDirectives If `true` then it reports unused `eslint-disable` comments. - * @param {RegExp} config.extensionRegExp The `RegExp` object that tests if a file path has the allowed file extensions. + * @param {FileEnumerator} config.fileEnumerator The file enumerator to check if a path is a target or not. * @param {Linter} config.linter The linter instance to verify. * @returns {LintResult} The result of linting. * @private @@ -214,7 +214,7 @@ function verifyText({ fix, allowInlineConfig, reportUnusedDisableDirectives, - extensionRegExp, + fileEnumerator, linter }) { const filePath = providedFilePath || ""; @@ -238,13 +238,11 @@ function verifyText({ /** * Check if the linter should adopt a given code block or not. - * Currently, the linter adopts code blocks if the name matches `--ext` option. - * In the future, `overrides` in the configuration would affect the adoption (https://github.com/eslint/rfcs/pull/20). * @param {string} blockFilename The virtual filename of a code block. * @returns {boolean} `true` if the linter should adopt the code block. */ filterCodeBlock(blockFilename) { - return extensionRegExp.test(blockFilename); + return fileEnumerator.isTargetPath(blockFilename); } } ); @@ -704,7 +702,7 @@ class CLIEngine { return patterns.filter(Boolean); } - const extensions = options.extensions.map(ext => ext.replace(/^\./u, "")); + const extensions = (options.extensions || [".js"]).map(ext => ext.replace(/^\./u, "")); const dirSuffix = `/**/*.{${extensions.join(",")}}`; return patterns.filter(Boolean).map(pathname => { @@ -803,7 +801,7 @@ class CLIEngine { fix, allowInlineConfig, reportUnusedDisableDirectives, - extensionRegExp: fileEnumerator.extensionRegExp, + fileEnumerator, linter }); @@ -891,7 +889,7 @@ class CLIEngine { fix, allowInlineConfig, reportUnusedDisableDirectives, - extensionRegExp: fileEnumerator.extensionRegExp, + fileEnumerator, linter })); } diff --git a/tools/node_modules/eslint/lib/cli-engine/config-array-factory.js b/tools/node_modules/eslint/lib/cli-engine/config-array-factory.js index 76c4ccd70212cd..997a7e15318dfd 100644 --- a/tools/node_modules/eslint/lib/cli-engine/config-array-factory.js +++ b/tools/node_modules/eslint/lib/cli-engine/config-array-factory.js @@ -436,6 +436,29 @@ class ConfigArrayFactory { ); } + /** + * Check if a config file on a given directory exists or not. + * @param {string} directoryPath The path to a directory. + * @returns {string | null} The path to the found config file. If not found then null. + */ + static getPathToConfigFileInDirectory(directoryPath) { + for (const filename of configFilenames) { + const filePath = path.join(directoryPath, filename); + + if (fs.existsSync(filePath)) { + if (filename === "package.json") { + try { + loadPackageJSONConfigFile(filePath); + return filePath; + } catch (error) { /* ignore */ } + } else { + return filePath; + } + } + } + return null; + } + /** * Load `.eslintignore` file. * @param {string} filePath The path to a `.eslintignore` file to load. @@ -542,7 +565,7 @@ class ConfigArrayFactory { */ *_normalizeESLintIgnoreData(ignorePatterns, filePath, name) { const elements = this._normalizeObjectConfigData( - { ignorePatterns }, + { type: "ignore", ignorePatterns }, filePath, name ); @@ -644,6 +667,7 @@ class ConfigArrayFactory { root, rules, settings, + type = "config", overrides: overrideList = [] }, filePath, @@ -675,6 +699,7 @@ class ConfigArrayFactory { yield { // Debug information. + type, name, filePath, @@ -1024,6 +1049,7 @@ class ConfigArrayFactory { if (processorId.startsWith(".")) { yield* this._normalizeObjectConfigData( { + type: "implicit-processor", files: [`*${processorId}`], processor: `${pluginId}/${processorId}` }, diff --git a/tools/node_modules/eslint/lib/cli-engine/config-array/config-array.js b/tools/node_modules/eslint/lib/cli-engine/config-array/config-array.js index 4fae8deaca1b69..02f73b43e65438 100644 --- a/tools/node_modules/eslint/lib/cli-engine/config-array/config-array.js +++ b/tools/node_modules/eslint/lib/cli-engine/config-array/config-array.js @@ -454,6 +454,25 @@ class ConfigArray extends Array { return cache.get(cacheKey); } + + /** + * Check if a given path is an additional lint target. + * @param {string} filePath The absolute path to the target file. + * @returns {boolean} `true` if the file is an additional lint target. + */ + isAdditionalTargetPath(filePath) { + for (const { criteria, type } of this) { + if ( + type === "config" && + criteria && + !criteria.endsWithWildcard && + criteria.test(filePath) + ) { + return true; + } + } + return false; + } } const exportObject = { diff --git a/tools/node_modules/eslint/lib/cli-engine/config-array/override-tester.js b/tools/node_modules/eslint/lib/cli-engine/config-array/override-tester.js index 67c8a6ed8a0cbb..e7ba1202f1cb08 100644 --- a/tools/node_modules/eslint/lib/cli-engine/config-array/override-tester.js +++ b/tools/node_modules/eslint/lib/cli-engine/config-array/override-tester.js @@ -96,14 +96,22 @@ class OverrideTester { static create(files, excludedFiles, basePath) { const includePatterns = normalizePatterns(files); const excludePatterns = normalizePatterns(excludedFiles); - const allPatterns = includePatterns.concat(excludePatterns); + let endsWithWildcard = false; - if (allPatterns.length === 0) { + if (includePatterns.length === 0) { return null; } // Rejects absolute paths or relative paths to parents. - for (const pattern of allPatterns) { + for (const pattern of includePatterns) { + if (path.isAbsolute(pattern) || pattern.includes("..")) { + throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`); + } + if (pattern.endsWith("*")) { + endsWithWildcard = true; + } + } + for (const pattern of excludePatterns) { if (path.isAbsolute(pattern) || pattern.includes("..")) { throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`); } @@ -112,7 +120,11 @@ class OverrideTester { const includes = toMatcher(includePatterns); const excludes = toMatcher(excludePatterns); - return new OverrideTester([{ includes, excludes }], basePath); + return new OverrideTester( + [{ includes, excludes }], + basePath, + endsWithWildcard + ); } /** @@ -125,28 +137,44 @@ class OverrideTester { */ static and(a, b) { if (!b) { - return a && new OverrideTester(a.patterns, a.basePath); + return a && new OverrideTester( + a.patterns, + a.basePath, + a.endsWithWildcard + ); } if (!a) { - return new OverrideTester(b.patterns, b.basePath); + return new OverrideTester( + b.patterns, + b.basePath, + b.endsWithWildcard + ); } assert.strictEqual(a.basePath, b.basePath); - return new OverrideTester(a.patterns.concat(b.patterns), a.basePath); + return new OverrideTester( + a.patterns.concat(b.patterns), + a.basePath, + a.endsWithWildcard || b.endsWithWildcard + ); } /** * Initialize this instance. * @param {Pattern[]} patterns The matchers. * @param {string} basePath The base path. + * @param {boolean} endsWithWildcard If `true` then a pattern ends with `*`. */ - constructor(patterns, basePath) { + constructor(patterns, basePath, endsWithWildcard = false) { /** @type {Pattern[]} */ this.patterns = patterns; /** @type {string} */ this.basePath = basePath; + + /** @type {boolean} */ + this.endsWithWildcard = endsWithWildcard; } /** diff --git a/tools/node_modules/eslint/lib/cli-engine/file-enumerator.js b/tools/node_modules/eslint/lib/cli-engine/file-enumerator.js index c67e01aef9b1c8..16e5182ad8a450 100644 --- a/tools/node_modules/eslint/lib/cli-engine/file-enumerator.js +++ b/tools/node_modules/eslint/lib/cli-engine/file-enumerator.js @@ -88,7 +88,7 @@ const IGNORED = 2; * @typedef {Object} FileEnumeratorInternalSlots * @property {CascadingConfigArrayFactory} configArrayFactory The factory for config arrays. * @property {string} cwd The base directory to start lookup. - * @property {RegExp} extensionRegExp The RegExp to test if a string ends with specific file extensions. + * @property {RegExp|null} extensionRegExp The RegExp to test if a string ends with specific file extensions. * @property {boolean} globInputPaths Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file. * @property {boolean} ignoreFlag The flag to check ignored files. * @property {(filePath:string, dot:boolean) => boolean} defaultIgnores The default predicate function to ignore files. @@ -127,12 +127,12 @@ function statSafeSync(filePath) { /** * Get filenames in a given path to a directory. * @param {string} directoryPath The path to target directory. - * @returns {string[]} The filenames. + * @returns {import("fs").Dirent[]} The filenames. * @private */ function readdirSafeSync(directoryPath) { try { - return fs.readdirSync(directoryPath); + return fs.readdirSync(directoryPath, { withFileTypes: true }); } catch (error) { /* istanbul ignore next */ if (error.code !== "ENOENT") { @@ -142,6 +142,27 @@ function readdirSafeSync(directoryPath) { } } +/** + * Create a `RegExp` object to detect extensions. + * @param {string[] | null} extensions The extensions to create. + * @returns {RegExp | null} The created `RegExp` object or null. + */ +function createExtensionRegExp(extensions) { + if (extensions) { + const normalizedExts = extensions.map(ext => escapeRegExp( + ext.startsWith(".") + ? ext.slice(1) + : ext + )); + + return new RegExp( + `.\\.(?:${normalizedExts.join("|")})$`, + "u" + ); + } + return null; +} + /** * The error type when no files match a glob. */ @@ -188,7 +209,7 @@ class FileEnumerator { constructor({ cwd = process.cwd(), configArrayFactory = new CascadingConfigArrayFactory({ cwd }), - extensions = [".js"], + extensions = null, globInputPaths = true, errorOnUnmatchedPattern = true, ignore = true @@ -197,17 +218,7 @@ class FileEnumerator { configArrayFactory, cwd, defaultIgnores: IgnorePattern.createDefaultIgnore(cwd), - extensionRegExp: new RegExp( - `.\\.(?:${extensions - .map(ext => escapeRegExp( - ext.startsWith(".") - ? ext.slice(1) - : ext - )) - .join("|") - })$`, - "u" - ), + extensionRegExp: createExtensionRegExp(extensions), globInputPaths, errorOnUnmatchedPattern, ignoreFlag: ignore @@ -215,11 +226,36 @@ class FileEnumerator { } /** - * The `RegExp` object that tests if a file path has the allowed file extensions. - * @type {RegExp} + * Check if a given file is target or not. + * @param {string} filePath The path to a candidate file. + * @param {ConfigArray} [providedConfig] Optional. The configuration for the file. + * @returns {boolean} `true` if the file is a target. */ - get extensionRegExp() { - return internalSlotsMap.get(this).extensionRegExp; + isTargetPath(filePath, providedConfig) { + const { + configArrayFactory, + extensionRegExp + } = internalSlotsMap.get(this); + + // If `--ext` option is present, use it. + if (extensionRegExp) { + return extensionRegExp.test(filePath); + } + + // `.js` file is target by default. + if (filePath.endsWith(".js")) { + return true; + } + + // use `overrides[].files` to check additional targets. + const config = + providedConfig || + configArrayFactory.getConfigArrayForFile( + filePath, + { ignoreNotFoundError: true } + ); + + return config.isAdditionalTargetPath(filePath); } /** @@ -380,18 +416,17 @@ class FileEnumerator { */ *_iterateFilesRecursive(directoryPath, options) { debug(`Enter the directory: ${directoryPath}`); - const { configArrayFactory, extensionRegExp } = internalSlotsMap.get(this); + const { configArrayFactory } = internalSlotsMap.get(this); /** @type {ConfigArray|null} */ let config = null; // Enumerate the files of this directory. - for (const filename of readdirSafeSync(directoryPath)) { - const filePath = path.join(directoryPath, filename); - const stat = statSafeSync(filePath); // TODO: Use `withFileTypes` in the future. + for (const entry of readdirSafeSync(directoryPath)) { + const filePath = path.join(directoryPath, entry.name); // Check if the file is matched. - if (stat && stat.isFile()) { + if (entry.isFile()) { if (!config) { config = configArrayFactory.getConfigArrayForFile( filePath, @@ -404,29 +439,30 @@ class FileEnumerator { { ignoreNotFoundError: true } ); } - const ignored = this._isIgnoredFile(filePath, { ...options, config }); - const flag = ignored ? IGNORED_SILENTLY : NONE; const matched = options.selector // Started with a glob pattern; choose by the pattern. ? options.selector.match(filePath) // Started with a directory path; choose by file extensions. - : extensionRegExp.test(filePath); + : this.isTargetPath(filePath, config); if (matched) { - debug(`Yield: ${filename}${ignored ? " but ignored" : ""}`); + const ignored = this._isIgnoredFile(filePath, { ...options, config }); + const flag = ignored ? IGNORED_SILENTLY : NONE; + + debug(`Yield: ${entry.name}${ignored ? " but ignored" : ""}`); yield { config: configArrayFactory.getConfigArrayForFile(filePath), filePath, flag }; } else { - debug(`Didn't match: ${filename}`); + debug(`Didn't match: ${entry.name}`); } // Dive into the sub directory. - } else if (options.recursive && stat && stat.isDirectory()) { + } else if (options.recursive && entry.isDirectory()) { if (!config) { config = configArrayFactory.getConfigArrayForFile( filePath, diff --git a/tools/node_modules/eslint/lib/cli-engine/formatters/junit.js b/tools/node_modules/eslint/lib/cli-engine/formatters/junit.js index c32425883f7cac..a994b4b1980eea 100644 --- a/tools/node_modules/eslint/lib/cli-engine/formatters/junit.js +++ b/tools/node_modules/eslint/lib/cli-engine/formatters/junit.js @@ -32,7 +32,7 @@ function getMessageType(message) { * @private */ function pathWithoutExt(filePath) { - return path.posix.join(path.posix.dirname(filePath), path.basename(filePath, path.extname(filePath))); + return path.join(path.dirname(filePath), path.basename(filePath, path.extname(filePath))); } //------------------------------------------------------------------------------ diff --git a/tools/node_modules/eslint/lib/cli.js b/tools/node_modules/eslint/lib/cli.js index 944b4b79353b3d..815ce68c22fe2e 100644 --- a/tools/node_modules/eslint/lib/cli.js +++ b/tools/node_modules/eslint/lib/cli.js @@ -17,7 +17,6 @@ const fs = require("fs"), path = require("path"), - mkdirp = require("mkdirp"), { CLIEngine } = require("./cli-engine"), options = require("./options"), log = require("./shared/logging"), @@ -115,7 +114,7 @@ function printResults(engine, results, format, outputFile) { } try { - mkdirp.sync(path.dirname(filePath)); + fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, output); } catch (ex) { log.error("There was a problem writing the output file:\n%s", ex); diff --git a/tools/node_modules/eslint/lib/linter/linter.js b/tools/node_modules/eslint/lib/linter/linter.js index 6d88cb5aa1245f..76d35b49eefb55 100644 --- a/tools/node_modules/eslint/lib/linter/linter.js +++ b/tools/node_modules/eslint/lib/linter/linter.js @@ -267,6 +267,15 @@ function createDisableDirectives(options) { return result; } +/** + * Remove the ignored part from a given directive comment and trim it. + * @param {string} value The comment text to strip. + * @returns {string} The stripped text. + */ +function stripDirectiveComment(value) { + return value.split(/\s-{2,}\s/u)[0].trim(); +} + /** * Parses comments in file to extract file-specific config of rules, globals * and environments and merges them with global config; also code blocks @@ -286,7 +295,7 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) { const disableDirectives = []; ast.comments.filter(token => token.type !== "Shebang").forEach(comment => { - const trimmedCommentText = comment.value.trim(); + const trimmedCommentText = stripDirectiveComment(comment.value); const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(trimmedCommentText); if (!match) { @@ -444,8 +453,11 @@ function findEslintEnv(text) { eslintEnvPattern.lastIndex = 0; - while ((match = eslintEnvPattern.exec(text))) { - retv = Object.assign(retv || {}, commentParser.parseListConfig(match[1])); + while ((match = eslintEnvPattern.exec(text)) !== null) { + retv = Object.assign( + retv || {}, + commentParser.parseListConfig(stripDirectiveComment(match[1])) + ); } return retv; diff --git a/tools/node_modules/eslint/lib/rule-tester/rule-tester.js b/tools/node_modules/eslint/lib/rule-tester/rule-tester.js index 44e01dadf09b5d..2ac26534fcc0ea 100644 --- a/tools/node_modules/eslint/lib/rule-tester/rule-tester.js +++ b/tools/node_modules/eslint/lib/rule-tester/rule-tester.js @@ -50,6 +50,52 @@ const const ajv = require("../shared/ajv")({ strictDefaults: true }); + +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** + * A test case that is expected to pass lint. + * @typedef {Object} ValidTestCase + * @property {string} code Code for the test case. + * @property {any[]} [options] Options for the test case. + * @property {{ [name: string]: any }} [settings] Settings for the test case. + * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames. + * @property {string} [parser] The absolute path for the parser. + * @property {{ [name: string]: any }} [parserOptions] Options for the parser. + * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables. + * @property {{ [name: string]: boolean }} [env] Environments for the test case. + */ + +/** + * A test case that is expected to fail lint. + * @typedef {Object} InvalidTestCase + * @property {string} code Code for the test case. + * @property {number | Array} errors Expected errors. + * @property {string | null} [output] The expected code after autofixes are applied. If set to `null`, the test runner will assert that no autofix is suggested. + * @property {any[]} [options] Options for the test case. + * @property {{ [name: string]: any }} [settings] Settings for the test case. + * @property {string} [filename] The fake filename for the test case. Useful for rules that make assertion about filenames. + * @property {string} [parser] The absolute path for the parser. + * @property {{ [name: string]: any }} [parserOptions] Options for the parser. + * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables. + * @property {{ [name: string]: boolean }} [env] Environments for the test case. + */ + +/** + * A description of a reported error used in a rule tester test. + * @typedef {Object} TestCaseError + * @property {string | RegExp} [message] Message. + * @property {string} [messageId] Message ID. + * @property {string} [type] The type of the reported AST node. + * @property {{ [name: string]: string }} [data] The data used to fill the message template. + * @property {number} [line] The 1-based line number of the reported start location. + * @property {number} [column] The 1-based column number of the reported start location. + * @property {number} [endLine] The 1-based line number of the reported end location. + * @property {number} [endColumn] The 1-based column number of the reported end location. + */ + //------------------------------------------------------------------------------ // Private Members //------------------------------------------------------------------------------ @@ -128,7 +174,7 @@ function freezeDeeply(x) { */ function sanitize(text) { return text.replace( - /[\u0000-\u0009|\u000b-\u001a]/gu, // eslint-disable-line no-control-regex + /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}` ); } @@ -273,7 +319,10 @@ class RuleTester { * Adds a new rule test to execute. * @param {string} ruleName The name of the rule to run. * @param {Function} rule The rule to test. - * @param {Object} test The collection of tests to run. + * @param {{ + * valid: (ValidTestCase | string)[], + * invalid: InvalidTestCase[] + * }} test The collection of tests to run. * @returns {void} */ run(ruleName, rule, test) { diff --git a/tools/node_modules/eslint/lib/rules/array-callback-return.js b/tools/node_modules/eslint/lib/rules/array-callback-return.js index d632a3f30c28a3..6e425dcdcb89da 100644 --- a/tools/node_modules/eslint/lib/rules/array-callback-return.js +++ b/tools/node_modules/eslint/lib/rules/array-callback-return.js @@ -18,7 +18,7 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u; -const TARGET_METHODS = /^(?:every|filter|find(?:Index)?|map|reduce(?:Right)?|some|sort)$/u; +const TARGET_METHODS = /^(?:every|filter|find(?:Index)?|flatMap|map|reduce(?:Right)?|some|sort)$/u; /** * Checks a given code path segment is reachable. diff --git a/tools/node_modules/eslint/lib/rules/array-element-newline.js b/tools/node_modules/eslint/lib/rules/array-element-newline.js index 1da67667bee849..b7a967865b92a1 100644 --- a/tools/node_modules/eslint/lib/rules/array-element-newline.js +++ b/tools/node_modules/eslint/lib/rules/array-element-newline.js @@ -24,28 +24,52 @@ module.exports = { fixable: "whitespace", - schema: [ - { - oneOf: [ - { - enum: ["always", "never", "consistent"] - }, - { - type: "object", - properties: { - multiline: { - type: "boolean" + schema: { + definitions: { + basicConfig: { + oneOf: [ + { + enum: ["always", "never", "consistent"] + }, + { + type: "object", + properties: { + multiline: { + type: "boolean" + }, + minItems: { + type: ["integer", "null"], + minimum: 0 + } }, - minItems: { - type: ["integer", "null"], - minimum: 0 - } + additionalProperties: false + } + ] + } + }, + items: [ + { + oneOf: [ + { + $ref: "#/definitions/basicConfig" }, - additionalProperties: false - } - ] - } - ], + { + type: "object", + properties: { + ArrayExpression: { + $ref: "#/definitions/basicConfig" + }, + ArrayPattern: { + $ref: "#/definitions/basicConfig" + } + }, + additionalProperties: false, + minProperties: 1 + } + ] + } + ] + }, messages: { unexpectedLineBreak: "There should be no linebreak here.", @@ -93,6 +117,20 @@ module.exports = { * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object. */ function normalizeOptions(options) { + if (options && (options.ArrayExpression || options.ArrayPattern)) { + let expressionOptions, patternOptions; + + if (options.ArrayExpression) { + expressionOptions = normalizeOptionValue(options.ArrayExpression); + } + + if (options.ArrayPattern) { + patternOptions = normalizeOptionValue(options.ArrayPattern); + } + + return { ArrayExpression: expressionOptions, ArrayPattern: patternOptions }; + } + const value = normalizeOptionValue(options); return { ArrayExpression: value, ArrayPattern: value }; @@ -177,6 +215,10 @@ module.exports = { const normalizedOptions = normalizeOptions(context.options[0]); const options = normalizedOptions[node.type]; + if (!options) { + return; + } + let elementBreak = false; /* diff --git a/tools/node_modules/eslint/lib/rules/computed-property-spacing.js b/tools/node_modules/eslint/lib/rules/computed-property-spacing.js index a0bd7f48ced66b..48bea6a6fdc8f4 100644 --- a/tools/node_modules/eslint/lib/rules/computed-property-spacing.js +++ b/tools/node_modules/eslint/lib/rules/computed-property-spacing.js @@ -67,7 +67,7 @@ module.exports = { function reportNoBeginningSpace(node, token, tokenAfter) { context.report({ node, - loc: token.loc.start, + loc: { start: token.loc.end, end: tokenAfter.loc.start }, messageId: "unexpectedSpaceAfter", data: { tokenValue: token.value @@ -88,7 +88,7 @@ module.exports = { function reportNoEndingSpace(node, token, tokenBefore) { context.report({ node, - loc: token.loc.start, + loc: { start: tokenBefore.loc.end, end: token.loc.start }, messageId: "unexpectedSpaceBefore", data: { tokenValue: token.value @@ -108,7 +108,7 @@ module.exports = { function reportRequiredBeginningSpace(node, token) { context.report({ node, - loc: token.loc.start, + loc: token.loc, messageId: "missingSpaceAfter", data: { tokenValue: token.value @@ -128,7 +128,7 @@ module.exports = { function reportRequiredEndingSpace(node, token) { context.report({ node, - loc: token.loc.start, + loc: token.loc, messageId: "missingSpaceBefore", data: { tokenValue: token.value diff --git a/tools/node_modules/eslint/lib/rules/default-case-last.js b/tools/node_modules/eslint/lib/rules/default-case-last.js new file mode 100644 index 00000000000000..80c5d6bda73958 --- /dev/null +++ b/tools/node_modules/eslint/lib/rules/default-case-last.js @@ -0,0 +1,44 @@ +/** + * @fileoverview Rule to enforce default clauses in switch statements to be last + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "enforce default clauses in switch statements to be last", + category: "Best Practices", + recommended: false, + url: "https://eslint.org/docs/rules/default-case-last" + }, + + schema: [], + + messages: { + notLast: "Default clause should be the last clause." + } + }, + + create(context) { + return { + SwitchStatement(node) { + const cases = node.cases, + indexOfDefault = cases.findIndex(c => c.test === null); + + if (indexOfDefault !== -1 && indexOfDefault !== cases.length - 1) { + const defaultClause = cases[indexOfDefault]; + + context.report({ node: defaultClause, messageId: "notLast" }); + } + } + }; + } +}; diff --git a/tools/node_modules/eslint/lib/rules/id-blacklist.js b/tools/node_modules/eslint/lib/rules/id-blacklist.js index 53be62e68a3626..e5dcdc63091568 100644 --- a/tools/node_modules/eslint/lib/rules/id-blacklist.js +++ b/tools/node_modules/eslint/lib/rules/id-blacklist.js @@ -61,9 +61,12 @@ module.exports = { * @returns {boolean} whether an error should be reported or not */ function shouldReport(effectiveParent, name) { - return effectiveParent.type !== "CallExpression" && + return ( + effectiveParent.type !== "CallExpression" && effectiveParent.type !== "NewExpression" && - isInvalid(name); + effectiveParent.parent.type !== "ObjectPattern" && + isInvalid(name) + ); } /** @@ -98,11 +101,11 @@ module.exports = { report(node); } - // Report AssignmentExpressions only if they are the left side of the assignment + // Report AssignmentExpressions only if they are the left side of the assignment } else if (effectiveParent.type === "AssignmentExpression" && (effectiveParent.right.type !== "MemberExpression" || - effectiveParent.left.type === "MemberExpression" && - effectiveParent.left.property.name === node.name)) { + effectiveParent.left.type === "MemberExpression" && + effectiveParent.left.property.name === node.name)) { if (isInvalid(name)) { report(node); } @@ -115,7 +118,7 @@ module.exports = { report(node); } - // Report anything that is a match and not a CallExpression + // Report anything that is a match and not a CallExpression } else if (shouldReport(effectiveParent, name)) { report(node); } diff --git a/tools/node_modules/eslint/lib/rules/indent.js b/tools/node_modules/eslint/lib/rules/indent.js index 694cf7d9e6ed4f..d576fde0382461 100644 --- a/tools/node_modules/eslint/lib/rules/indent.js +++ b/tools/node_modules/eslint/lib/rules/indent.js @@ -540,8 +540,15 @@ module.exports = { ] }, outerIIFEBody: { - type: "integer", - minimum: 0 + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + enum: ["off"] + } + ] }, MemberExpression: { oneOf: [ @@ -590,6 +597,10 @@ module.exports = { type: "boolean", default: false }, + offsetTernaryExpressions: { + type: "boolean", + default: false + }, ignoredNodes: { type: "array", items: { @@ -922,7 +933,11 @@ module.exports = { parameterParens.add(openingParen); parameterParens.add(closingParen); - offsets.setDesiredOffset(openingParen, sourceCode.getTokenBefore(openingParen), 0); + + const offsetAfterToken = node.callee.type === "TaggedTemplateExpression" ? sourceCode.getFirstToken(node.callee.quasi) : openingParen; + const offsetToken = sourceCode.getTokenBefore(offsetAfterToken); + + offsets.setDesiredOffset(openingParen, offsetToken, 0); addElementListIndent(node.arguments, openingParen, closingParen, options.CallExpression.arguments); } @@ -1088,7 +1103,6 @@ module.exports = { }, "BlockStatement, ClassBody"(node) { - let blockIndentLevel; if (node.parent && isOuterIIFE(node.parent)) { @@ -1108,6 +1122,7 @@ module.exports = { if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) { offsets.setDesiredOffset(sourceCode.getFirstToken(node), sourceCode.getFirstToken(node.parent), 0); } + addElementListIndent(node.body, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), blockIndentLevel); }, @@ -1142,7 +1157,8 @@ module.exports = { offsets.setDesiredOffset(questionMarkToken, firstToken, 1); offsets.setDesiredOffset(colonToken, firstToken, 1); - offsets.setDesiredOffset(firstConsequentToken, firstToken, 1); + offsets.setDesiredOffset(firstConsequentToken, firstToken, + options.offsetTernaryExpressions ? 2 : 1); /* * The alternate and the consequent should usually have the same indentation. @@ -1167,7 +1183,9 @@ module.exports = { * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up * having no expected indentation. */ - offsets.setDesiredOffset(firstAlternateToken, firstToken, 1); + offsets.setDesiredOffset(firstAlternateToken, firstToken, + firstAlternateToken.type === "Punctuator" && + options.offsetTernaryExpressions ? 2 : 1); } } }, diff --git a/tools/node_modules/eslint/lib/rules/index.js b/tools/node_modules/eslint/lib/rules/index.js index d3fbe412080360..7f563eb2ebfb96 100644 --- a/tools/node_modules/eslint/lib/rules/index.js +++ b/tools/node_modules/eslint/lib/rules/index.js @@ -37,6 +37,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "constructor-super": () => require("./constructor-super"), curly: () => require("./curly"), "default-case": () => require("./default-case"), + "default-case-last": () => require("./default-case-last"), "default-param-last": () => require("./default-param-last"), "dot-location": () => require("./dot-location"), "dot-notation": () => require("./dot-notation"), @@ -178,6 +179,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "no-prototype-builtins": () => require("./no-prototype-builtins"), "no-redeclare": () => require("./no-redeclare"), "no-regex-spaces": () => require("./no-regex-spaces"), + "no-restricted-exports": () => require("./no-restricted-exports"), "no-restricted-globals": () => require("./no-restricted-globals"), "no-restricted-imports": () => require("./no-restricted-imports"), "no-restricted-modules": () => require("./no-restricted-modules"), @@ -215,6 +217,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "no-unused-labels": () => require("./no-unused-labels"), "no-unused-vars": () => require("./no-unused-vars"), "no-use-before-define": () => require("./no-use-before-define"), + "no-useless-backreference": () => require("./no-useless-backreference"), "no-useless-call": () => require("./no-useless-call"), "no-useless-catch": () => require("./no-useless-catch"), "no-useless-computed-key": () => require("./no-useless-computed-key"), diff --git a/tools/node_modules/eslint/lib/rules/key-spacing.js b/tools/node_modules/eslint/lib/rules/key-spacing.js index 6d1b9121c78c5f..c405043794c7d1 100644 --- a/tools/node_modules/eslint/lib/rules/key-spacing.js +++ b/tools/node_modules/eslint/lib/rules/key-spacing.js @@ -414,8 +414,7 @@ module.exports = { if (property.computed) { return sourceCode.getText().slice(key.range[0], key.range[1]); } - - return property.key.name || property.key.value; + return astUtils.getStaticPropertyName(property); } /** diff --git a/tools/node_modules/eslint/lib/rules/max-len.js b/tools/node_modules/eslint/lib/rules/max-len.js index b29099ed4c4f09..82229c3be4894a 100644 --- a/tools/node_modules/eslint/lib/rules/max-len.js +++ b/tools/node_modules/eslint/lib/rules/max-len.js @@ -183,6 +183,21 @@ module.exports = { (end.line > lineNumber || (end.line === lineNumber && end.column === line.length)); } + /** + * Check if a node is a JSXEmptyExpression contained in a single line JSXExpressionContainer. + * @param {ASTNode} node A node to check. + * @returns {boolean} True if the node is a JSXEmptyExpression contained in a single line JSXExpressionContainer. + */ + function isJSXEmptyExpressionInSingleLineContainer(node) { + if (!node || !node.parent || node.type !== "JSXEmptyExpression" || node.parent.type !== "JSXExpressionContainer") { + return false; + } + + const parent = node.parent; + + return parent.loc.start.line === parent.loc.end.line; + } + /** * Gets the line after the comment and any remaining trailing whitespace is * stripped. @@ -252,6 +267,33 @@ module.exports = { return acc; } + /** + * Returns an array of all comments in the source code. + * If the element in the array is a JSXEmptyExpression contained with a single line JSXExpressionContainer, + * the element is changed with JSXExpressionContainer node. + * @returns {ASTNode[]} An array of comment nodes + */ + function getAllComments() { + const comments = []; + + sourceCode.getAllComments() + .forEach(commentNode => { + const containingNode = sourceCode.getNodeByRangeIndex(commentNode.range[0]); + + if (isJSXEmptyExpressionInSingleLineContainer(containingNode)) { + + // push a unique node only + if (comments[comments.length - 1] !== containingNode.parent) { + comments.push(containingNode.parent); + } + } else { + comments.push(commentNode); + } + }); + + return comments; + } + /** * Check the program for max length * @param {ASTNode} node Node to examine @@ -264,7 +306,7 @@ module.exports = { const lines = sourceCode.lines, // list of comments to ignore - comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? sourceCode.getAllComments() : []; + comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? getAllComments() : []; // we iterate over comments in parallel with the lines let commentsIndex = 0; diff --git a/tools/node_modules/eslint/lib/rules/max-lines-per-function.js b/tools/node_modules/eslint/lib/rules/max-lines-per-function.js index 0cfc1f8da9e65e..03539fae470078 100644 --- a/tools/node_modules/eslint/lib/rules/max-lines-per-function.js +++ b/tools/node_modules/eslint/lib/rules/max-lines-per-function.js @@ -54,9 +54,6 @@ const OPTIONS_OR_INTEGER_SCHEMA = { function getCommentLineNumbers(comments) { const map = new Map(); - if (!comments) { - return map; - } comments.forEach(comment => { for (let i = comment.loc.start.line; i <= comment.loc.end.line; i++) { map.set(i, comment); diff --git a/tools/node_modules/eslint/lib/rules/new-cap.js b/tools/node_modules/eslint/lib/rules/new-cap.js index cee979310ea7cc..7cce968c5aed0e 100644 --- a/tools/node_modules/eslint/lib/rules/new-cap.js +++ b/tools/node_modules/eslint/lib/rules/new-cap.js @@ -9,6 +9,8 @@ // Requirements //------------------------------------------------------------------------------ +const astUtils = require("./utils/ast-utils"); + //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ @@ -160,13 +162,7 @@ module.exports = { let name = ""; if (node.callee.type === "MemberExpression") { - const property = node.callee.property; - - if (property.type === "Literal" && (typeof property.value === "string")) { - name = property.value; - } else if (property.type === "Identifier" && !node.callee.computed) { - name = property.name; - } + name = astUtils.getStaticPropertyName(node.callee) || ""; } else { name = node.callee.name; } diff --git a/tools/node_modules/eslint/lib/rules/no-dupe-class-members.js b/tools/node_modules/eslint/lib/rules/no-dupe-class-members.js index 6041e9e371ce5e..55639746b27590 100644 --- a/tools/node_modules/eslint/lib/rules/no-dupe-class-members.js +++ b/tools/node_modules/eslint/lib/rules/no-dupe-class-members.js @@ -5,6 +5,8 @@ "use strict"; +const astUtils = require("./utils/ast-utils"); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -53,21 +55,6 @@ module.exports = { return stateMap[key][isStatic ? "static" : "nonStatic"]; } - /** - * Gets the name text of a given node. - * @param {ASTNode} node A node to get the name. - * @returns {string} The name text of the node. - */ - function getName(node) { - switch (node.type) { - case "Identifier": return node.name; - case "Literal": return String(node.value); - - /* istanbul ignore next: syntax error */ - default: return ""; - } - } - return { // Initializes the stack of state of member declarations. @@ -91,7 +78,7 @@ module.exports = { return; } - const name = getName(node.key); + const name = astUtils.getStaticPropertyName(node) || ""; const state = getState(name, node.static); let isDuplicate = false; diff --git a/tools/node_modules/eslint/lib/rules/no-extra-parens.js b/tools/node_modules/eslint/lib/rules/no-extra-parens.js index f96e572bfeef54..e70353576bb950 100644 --- a/tools/node_modules/eslint/lib/rules/no-extra-parens.js +++ b/tools/node_modules/eslint/lib/rules/no-extra-parens.js @@ -169,6 +169,28 @@ module.exports = { return ruleApplies(node) && isParenthesisedTwice(node); } + /** + * Determines if a node that is expected to be parenthesised is surrounded by + * (potentially) invalid extra parentheses with considering precedence level of the node. + * If the preference level of the node is not higher or equal to precedence lower limit, it also checks + * whether the node is surrounded by parentheses twice or not. + * @param {ASTNode} node The node to be checked. + * @param {number} precedenceLowerLimit The lower limit of precedence. + * @returns {boolean} True if the node is has an unexpected extra pair of parentheses. + * @private + */ + function hasExcessParensWithPrecedence(node, precedenceLowerLimit) { + if (ruleApplies(node) && isParenthesised(node)) { + if ( + precedence(node) >= precedenceLowerLimit || + isParenthesisedTwice(node) + ) { + return true; + } + } + return false; + } + /** * Determines if a node test expression is allowed to have a parenthesised assignment * @param {ASTNode} node The node to be checked. @@ -320,6 +342,17 @@ module.exports = { return node.type === "CallExpression" && node.callee.type === "FunctionExpression"; } + /** + * Determines if the given node can be the assignment target in destructuring or the LHS of an assignment. + * This is to avoid an autofix that could change behavior because parsers mistakenly allow invalid syntax, + * such as `(a = b) = c` and `[(a = b) = c] = []`. Ideally, this function shouldn't be necessary. + * @param {ASTNode} [node] The node to check + * @returns {boolean} `true` if the given node can be a valid assignment target + */ + function canBeAssignmentTarget(node) { + return node && (node.type === "Identifier" || node.type === "MemberExpression"); + } + /** * Report the node * @param {ASTNode} node node to evaluate @@ -370,17 +403,13 @@ module.exports = { } /** - * Evaluate Unary update + * Evaluate a argument of the node. * @param {ASTNode} node node to evaluate * @returns {void} * @private */ - function checkUnaryUpdate(node) { - if (node.type === "UnaryExpression" && node.argument.type === "BinaryExpression" && node.argument.operator === "**") { - return; - } - - if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) { + function checkArgumentWithPrecedence(node) { + if (hasExcessParensWithPrecedence(node.argument, precedence(node))) { report(node.argument); } } @@ -411,25 +440,24 @@ module.exports = { function checkCallNew(node) { const callee = node.callee; - if (hasExcessParens(callee) && precedence(callee) >= precedence(node)) { + if (hasExcessParensWithPrecedence(callee, precedence(node))) { const hasNewParensException = callee.type === "NewExpression" && !isNewExpressionWithParens(callee); if ( hasDoubleExcessParens(callee) || !isIIFE(node) && !hasNewParensException && !( - /* - * Allow extra parens around a new expression if - * there are intervening parentheses. - */ - (callee.type === "MemberExpression" && doesMemberExpressionContainCallExpression(callee)) + // Allow extra parens around a new expression if they are intervening parentheses. + node.type === "NewExpression" && + callee.type === "MemberExpression" && + doesMemberExpressionContainCallExpression(callee) ) ) { report(node.callee); } } node.arguments - .filter(arg => hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) + .filter(arg => hasExcessParensWithPrecedence(arg, PRECEDENCE_OF_ASSIGNMENT_EXPR)) .forEach(report); } @@ -444,15 +472,26 @@ module.exports = { const leftPrecedence = precedence(node.left); const rightPrecedence = precedence(node.right); const isExponentiation = node.operator === "**"; - const shouldSkipLeft = (NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression")) || - node.left.type === "UnaryExpression" && isExponentiation; + const shouldSkipLeft = NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression"); const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression"); - if (!shouldSkipLeft && hasExcessParens(node.left) && (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation))) { - report(node.left); + if (!shouldSkipLeft && hasExcessParens(node.left)) { + if ( + !(node.left.type === "UnaryExpression" && isExponentiation) && + (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation)) || + isParenthesisedTwice(node.left) + ) { + report(node.left); + } } - if (!shouldSkipRight && hasExcessParens(node.right) && (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation))) { - report(node.right); + + if (!shouldSkipRight && hasExcessParens(node.right)) { + if ( + (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation)) || + isParenthesisedTwice(node.right) + ) { + report(node.right); + } } } @@ -485,11 +524,7 @@ module.exports = { * @returns {void} */ function checkSpreadOperator(node) { - const hasExtraParens = precedence(node.argument) >= PRECEDENCE_OF_ASSIGNMENT_EXPR - ? hasExcessParens(node.argument) - : hasDoubleExcessParens(node.argument); - - if (hasExtraParens) { + if (hasExcessParensWithPrecedence(node.argument, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { report(node.argument); } } @@ -651,7 +686,13 @@ module.exports = { return { ArrayExpression(node) { node.elements - .filter(e => e && hasExcessParens(e) && precedence(e) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) + .filter(e => e && hasExcessParensWithPrecedence(e, PRECEDENCE_OF_ASSIGNMENT_EXPR)) + .forEach(report); + }, + + ArrayPattern(node) { + node.elements + .filter(e => canBeAssignmentTarget(e) && hasExcessParens(e)) .forEach(report); }, @@ -674,18 +715,18 @@ module.exports = { if (astUtils.isOpeningParenToken(tokenBeforeFirst) && astUtils.isOpeningBraceToken(firstBodyToken)) { tokensToIgnore.add(firstBodyToken); } - if (hasExcessParens(node.body) && precedence(node.body) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) { + if (hasExcessParensWithPrecedence(node.body, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { report(node.body); } } }, AssignmentExpression(node) { - if (isReturnAssignException(node)) { - return; + if (canBeAssignmentTarget(node.left) && hasExcessParens(node.left)) { + report(node.left); } - if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) { + if (!isReturnAssignException(node) && hasExcessParensWithPrecedence(node.right, precedence(node))) { report(node.right); } }, @@ -702,8 +743,8 @@ module.exports = { ClassBody(node) { node.body - .filter(member => member.type === "MethodDefinition" && member.computed && - member.key && hasExcessParens(member.key) && precedence(member.key) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) + .filter(member => member.type === "MethodDefinition" && member.computed && member.key) + .filter(member => hasExcessParensWithPrecedence(member.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) .forEach(member => report(member.key)); }, @@ -711,16 +752,18 @@ module.exports = { if (isReturnAssignException(node)) { return; } - - if (hasExcessParens(node.test) && precedence(node.test) >= precedence({ type: "LogicalExpression", operator: "||" })) { + if ( + !isCondAssignException(node) && + hasExcessParensWithPrecedence(node.test, precedence({ type: "LogicalExpression", operator: "||" })) + ) { report(node.test); } - if (hasExcessParens(node.consequent) && precedence(node.consequent) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) { + if (hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { report(node.consequent); } - if (hasExcessParens(node.alternate) && precedence(node.alternate) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) { + if (hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { report(node.alternate); } }, @@ -757,9 +800,19 @@ module.exports = { tokensToIgnore.add(firstLeftToken); } } - if (!(node.type === "ForOfStatement" && node.right.type === "SequenceExpression") && hasExcessParens(node.right)) { + + if (node.type === "ForOfStatement") { + const hasExtraParens = node.right.type === "SequenceExpression" + ? hasDoubleExcessParens(node.right) + : hasExcessParens(node.right); + + if (hasExtraParens) { + report(node.right); + } + } else if (hasExcessParens(node.right)) { report(node.right); } + if (hasExcessParens(node.left)) { report(node.left); } @@ -910,11 +963,17 @@ module.exports = { NewExpression: checkCallNew, ObjectExpression(node) { + node.properties + .filter(property => property.value && hasExcessParensWithPrecedence(property.value, PRECEDENCE_OF_ASSIGNMENT_EXPR)) + .forEach(property => report(property.value)); + }, + + ObjectPattern(node) { node.properties .filter(property => { const value = property.value; - return value && hasExcessParens(value) && precedence(value) >= PRECEDENCE_OF_ASSIGNMENT_EXPR; + return canBeAssignmentTarget(value) && hasExcessParens(value); }).forEach(property => report(property.value)); }, @@ -922,12 +981,20 @@ module.exports = { if (node.computed) { const { key } = node; - if (key && hasExcessParens(key) && precedence(key) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) { + if (key && hasExcessParensWithPrecedence(key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { report(key); } } }, + RestElement(node) { + const argument = node.argument; + + if (canBeAssignmentTarget(argument) && hasExcessParens(argument)) { + report(argument); + } + }, + ReturnStatement(node) { const returnToken = sourceCode.getFirstToken(node); @@ -945,8 +1012,10 @@ module.exports = { }, SequenceExpression(node) { + const precedenceOfNode = precedence(node); + node.expressions - .filter(e => hasExcessParens(e) && precedence(e) >= precedence(node)) + .filter(e => hasExcessParensWithPrecedence(e, precedenceOfNode)) .forEach(report); }, @@ -970,16 +1039,17 @@ module.exports = { } }, - UnaryExpression: checkUnaryUpdate, - UpdateExpression: checkUnaryUpdate, - AwaitExpression: checkUnaryUpdate, + UnaryExpression: checkArgumentWithPrecedence, + UpdateExpression: checkArgumentWithPrecedence, + AwaitExpression: checkArgumentWithPrecedence, VariableDeclarator(node) { - if (node.init && hasExcessParens(node.init) && - precedence(node.init) >= PRECEDENCE_OF_ASSIGNMENT_EXPR && + if ( + node.init && hasExcessParensWithPrecedence(node.init, PRECEDENCE_OF_ASSIGNMENT_EXPR) && - // RegExp literal is allowed to have parens (#1589) - !(node.init.type === "Literal" && node.init.regex)) { + // RegExp literal is allowed to have parens (#1589) + !(node.init.type === "Literal" && node.init.regex) + ) { report(node.init); } }, @@ -1022,9 +1092,13 @@ module.exports = { }, AssignmentPattern(node) { - const { right } = node; + const { left, right } = node; + + if (canBeAssignmentTarget(left) && hasExcessParens(left)) { + report(left); + } - if (right && hasExcessParens(right) && precedence(right) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) { + if (right && hasExcessParensWithPrecedence(right, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { report(right); } } diff --git a/tools/node_modules/eslint/lib/rules/no-func-assign.js b/tools/node_modules/eslint/lib/rules/no-func-assign.js index 66756e62bef5b2..33d0ad9ecd1433 100644 --- a/tools/node_modules/eslint/lib/rules/no-func-assign.js +++ b/tools/node_modules/eslint/lib/rules/no-func-assign.js @@ -22,7 +22,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-func-assign" }, - schema: [] + schema: [], + + messages: { + isAFunction: "'{{name}}' is a function." + } }, create(context) { @@ -34,7 +38,13 @@ module.exports = { */ function checkReference(references) { astUtils.getModifyingReferences(references).forEach(reference => { - context.report({ node: reference.identifier, message: "'{{name}}' is a function.", data: { name: reference.identifier.name } }); + context.report({ + node: reference.identifier, + messageId: "isAFunction", + data: { + name: reference.identifier.name + } + }); }); } diff --git a/tools/node_modules/eslint/lib/rules/no-global-assign.js b/tools/node_modules/eslint/lib/rules/no-global-assign.js index 4ab0c706446256..ea854c4aa8cd9b 100644 --- a/tools/node_modules/eslint/lib/rules/no-global-assign.js +++ b/tools/node_modules/eslint/lib/rules/no-global-assign.js @@ -32,7 +32,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + globalShouldNotBeModified: "Read-only global '{{name}}' should not be modified." + } }, create(context) { @@ -60,8 +64,10 @@ module.exports = { ) { context.report({ node: identifier, - message: "Read-only global '{{name}}' should not be modified.", - data: identifier + messageId: "globalShouldNotBeModified", + data: { + name: identifier.name + } }); } } diff --git a/tools/node_modules/eslint/lib/rules/no-implicit-coercion.js b/tools/node_modules/eslint/lib/rules/no-implicit-coercion.js index c80f9813020be5..6d5ee61e96bd5b 100644 --- a/tools/node_modules/eslint/lib/rules/no-implicit-coercion.js +++ b/tools/node_modules/eslint/lib/rules/no-implicit-coercion.js @@ -187,7 +187,11 @@ module.exports = { } }, additionalProperties: false - }] + }], + + messages: { + useRecommendation: "use `{{recommendation}}` instead." + } }, create(context) { @@ -204,7 +208,7 @@ module.exports = { function report(node, recommendation, shouldFix) { context.report({ node, - message: "use `{{recommendation}}` instead.", + messageId: "useRecommendation", data: { recommendation }, diff --git a/tools/node_modules/eslint/lib/rules/no-implied-eval.js b/tools/node_modules/eslint/lib/rules/no-implied-eval.js index e0764d8223db0a..46bb5d4f76e5f7 100644 --- a/tools/node_modules/eslint/lib/rules/no-implied-eval.js +++ b/tools/node_modules/eslint/lib/rules/no-implied-eval.js @@ -20,7 +20,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-implied-eval" }, - schema: [] + schema: [], + + messages: { + impliedEval: "Implied eval. Consider passing a function instead of a string." + } }, create(context) { @@ -107,7 +111,10 @@ module.exports = { // remove the entire substack, to avoid duplicate reports const substack = impliedEvalAncestorsStack.pop(); - context.report({ node: substack[0], message: "Implied eval. Consider passing a function instead of a string." }); + context.report({ + node: substack[0], + messageId: "impliedEval" + }); } } diff --git a/tools/node_modules/eslint/lib/rules/no-inline-comments.js b/tools/node_modules/eslint/lib/rules/no-inline-comments.js index bd226ecc35fde1..41b0f1e664c776 100644 --- a/tools/node_modules/eslint/lib/rules/no-inline-comments.js +++ b/tools/node_modules/eslint/lib/rules/no-inline-comments.js @@ -21,7 +21,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-inline-comments" }, - schema: [] + schema: [], + + messages: { + unexpectedInlineComment: "Unexpected comment inline with code." + } }, create(context) { @@ -64,7 +68,10 @@ module.exports = { return; } - context.report({ node, message: "Unexpected comment inline with code." }); + context.report({ + node, + messageId: "unexpectedInlineComment" + }); } //-------------------------------------------------------------------------- diff --git a/tools/node_modules/eslint/lib/rules/no-inner-declarations.js b/tools/node_modules/eslint/lib/rules/no-inner-declarations.js index 60508d3e864eda..e1c29e0a3b4f3f 100644 --- a/tools/node_modules/eslint/lib/rules/no-inner-declarations.js +++ b/tools/node_modules/eslint/lib/rules/no-inner-declarations.js @@ -24,7 +24,11 @@ module.exports = { { enum: ["functions", "both"] } - ] + ], + + messages: { + moveDeclToRoot: "Move {{type}} declaration to {{body}} root." + } }, create(context) { @@ -68,7 +72,7 @@ module.exports = { if (!valid) { context.report({ node, - message: "Move {{type}} declaration to {{body}} root.", + messageId: "moveDeclToRoot", data: { type: (node.type === "FunctionDeclaration" ? "function" : "variable"), body: (body.type === "Program" ? "program" : "function body") diff --git a/tools/node_modules/eslint/lib/rules/no-invalid-regexp.js b/tools/node_modules/eslint/lib/rules/no-invalid-regexp.js index 852efbbb935b90..4ede995891bbca 100644 --- a/tools/node_modules/eslint/lib/rules/no-invalid-regexp.js +++ b/tools/node_modules/eslint/lib/rules/no-invalid-regexp.js @@ -40,6 +40,8 @@ module.exports = { }, additionalProperties: false }] + + // no messages, because the error text comes directly from the regexpp module }, create(context) { diff --git a/tools/node_modules/eslint/lib/rules/no-invalid-this.js b/tools/node_modules/eslint/lib/rules/no-invalid-this.js index 5398fc3b5f842c..a79c586d719852 100644 --- a/tools/node_modules/eslint/lib/rules/no-invalid-this.js +++ b/tools/node_modules/eslint/lib/rules/no-invalid-this.js @@ -37,7 +37,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + unexpectedThis: "Unexpected 'this'." + } }, create(context) { @@ -130,7 +134,10 @@ module.exports = { const current = stack.getCurrent(); if (current && !current.valid) { - context.report({ node, message: "Unexpected 'this'." }); + context.report({ + node, + messageId: "unexpectedThis" + }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-irregular-whitespace.js b/tools/node_modules/eslint/lib/rules/no-irregular-whitespace.js index f339fa6c8f34c9..fa72c3ee72fcdc 100644 --- a/tools/node_modules/eslint/lib/rules/no-irregular-whitespace.js +++ b/tools/node_modules/eslint/lib/rules/no-irregular-whitespace.js @@ -59,7 +59,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + noIrregularWhitespace: "Irregular whitespace not allowed." + } }, create(context) { @@ -161,7 +165,11 @@ module.exports = { column: match.index }; - errors.push({ node, message: "Irregular whitespace not allowed.", loc: location }); + errors.push({ + node, + messageId: "noIrregularWhitespace", + loc: location + }); } }); } @@ -186,7 +194,11 @@ module.exports = { column: sourceLines[lineIndex].length }; - errors.push({ node, message: "Irregular whitespace not allowed.", loc: location }); + errors.push({ + node, + messageId: "noIrregularWhitespace", + loc: location + }); lastLineIndex = lineIndex; } } diff --git a/tools/node_modules/eslint/lib/rules/no-iterator.js b/tools/node_modules/eslint/lib/rules/no-iterator.js index 82319a3fb1dfb6..0b01d3533d9db1 100644 --- a/tools/node_modules/eslint/lib/rules/no-iterator.js +++ b/tools/node_modules/eslint/lib/rules/no-iterator.js @@ -20,7 +20,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-iterator" }, - schema: [] + schema: [], + + messages: { + noIterator: "Reserved name '__iterator__'." + } }, create(context) { @@ -32,7 +36,10 @@ module.exports = { if (node.property && (node.property.type === "Identifier" && node.property.name === "__iterator__" && !node.computed) || (node.property.type === "Literal" && node.property.value === "__iterator__")) { - context.report({ node, message: "Reserved name '__iterator__'." }); + context.report({ + node, + messageId: "noIterator" + }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-label-var.js b/tools/node_modules/eslint/lib/rules/no-label-var.js index a9fd042a390225..570db03285cf97 100644 --- a/tools/node_modules/eslint/lib/rules/no-label-var.js +++ b/tools/node_modules/eslint/lib/rules/no-label-var.js @@ -26,7 +26,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-label-var" }, - schema: [] + schema: [], + + messages: { + identifierClashWithLabel: "Found identifier with same name as label." + } }, create(context) { @@ -62,7 +66,10 @@ module.exports = { * with the innermost scope. */ if (findIdentifier(scope, node.label.name)) { - context.report({ node, message: "Found identifier with same name as label." }); + context.report({ + node, + messageId: "identifierClashWithLabel" + }); } } diff --git a/tools/node_modules/eslint/lib/rules/no-labels.js b/tools/node_modules/eslint/lib/rules/no-labels.js index 52f4b0f5168b71..85760d80dbedd7 100644 --- a/tools/node_modules/eslint/lib/rules/no-labels.js +++ b/tools/node_modules/eslint/lib/rules/no-labels.js @@ -40,7 +40,13 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + unexpectedLabel: "Unexpected labeled statement.", + unexpectedLabelInBreak: "Unexpected label in break statement.", + unexpectedLabelInContinue: "Unexpected label in continue statement." + } }, create(context) { @@ -113,7 +119,7 @@ module.exports = { if (!isAllowed(scopeInfo.kind)) { context.report({ node, - message: "Unexpected labeled statement." + messageId: "unexpectedLabel" }); } @@ -124,7 +130,7 @@ module.exports = { if (node.label && !isAllowed(getKind(node.label.name))) { context.report({ node, - message: "Unexpected label in break statement." + messageId: "unexpectedLabelInBreak" }); } }, @@ -133,7 +139,7 @@ module.exports = { if (node.label && !isAllowed(getKind(node.label.name))) { context.report({ node, - message: "Unexpected label in continue statement." + messageId: "unexpectedLabelInContinue" }); } } diff --git a/tools/node_modules/eslint/lib/rules/no-lone-blocks.js b/tools/node_modules/eslint/lib/rules/no-lone-blocks.js index 37561b0f9a3055..d7069887b8e460 100644 --- a/tools/node_modules/eslint/lib/rules/no-lone-blocks.js +++ b/tools/node_modules/eslint/lib/rules/no-lone-blocks.js @@ -20,7 +20,12 @@ module.exports = { url: "https://eslint.org/docs/rules/no-lone-blocks" }, - schema: [] + schema: [], + + messages: { + redundantBlock: "Block is redundant.", + redundantNestedBlock: "Nested block is redundant." + } }, create(context) { @@ -35,9 +40,12 @@ module.exports = { * @returns {void} */ function report(node) { - const message = node.parent.type === "BlockStatement" ? "Nested block is redundant." : "Block is redundant."; + const messageId = node.parent.type === "BlockStatement" ? "redundantNestedBlock" : "redundantBlock"; - context.report({ node, message }); + context.report({ + node, + messageId + }); } /** diff --git a/tools/node_modules/eslint/lib/rules/no-lonely-if.js b/tools/node_modules/eslint/lib/rules/no-lonely-if.js index b62d176a264210..53f9445f835ec5 100644 --- a/tools/node_modules/eslint/lib/rules/no-lonely-if.js +++ b/tools/node_modules/eslint/lib/rules/no-lonely-if.js @@ -20,7 +20,11 @@ module.exports = { }, schema: [], - fixable: "code" + fixable: "code", + + messages: { + unexpectedLonelyIf: "Unexpected if as the only statement in an else block." + } }, create(context) { @@ -38,7 +42,7 @@ module.exports = { parent === grandparent.alternate) { context.report({ node, - message: "Unexpected if as the only statement in an else block.", + messageId: "unexpectedLonelyIf", fix(fixer) { const openingElseCurly = sourceCode.getFirstToken(parent); const closingElseCurly = sourceCode.getLastToken(parent); diff --git a/tools/node_modules/eslint/lib/rules/no-magic-numbers.js b/tools/node_modules/eslint/lib/rules/no-magic-numbers.js index 0909e3166d952e..4bf24996cb8f20 100644 --- a/tools/node_modules/eslint/lib/rules/no-magic-numbers.js +++ b/tools/node_modules/eslint/lib/rules/no-magic-numbers.js @@ -5,10 +5,24 @@ "use strict"; +const { isNumericLiteral } = require("./utils/ast-utils"); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ +/** + * Convert the value to bigint if it's a string. Otherwise return the value as-is. + * @param {bigint|number|string} x The value to normalize. + * @returns {bigint|number} The normalized value. + */ +function normalizeIgnoreValue(x) { + if (typeof x === "string") { + return BigInt(x.slice(0, -1)); + } + return x; +} + module.exports = { meta: { type: "suggestion", @@ -34,7 +48,10 @@ module.exports = { ignore: { type: "array", items: { - type: "number" + anyOf: [ + { type: "number" }, + { type: "string", pattern: "^[+-]?(?:0|[1-9][0-9]*)n$" } + ] }, uniqueItems: true }, @@ -56,18 +73,9 @@ module.exports = { const config = context.options[0] || {}, detectObjects = !!config.detectObjects, enforceConst = !!config.enforceConst, - ignore = config.ignore || [], + ignore = (config.ignore || []).map(normalizeIgnoreValue), ignoreArrayIndexes = !!config.ignoreArrayIndexes; - /** - * Returns whether the node is number literal - * @param {Node} node the node literal being evaluated - * @returns {boolean} true if the node is a number literal - */ - function isNumber(node) { - return typeof node.value === "number"; - } - /** * Returns whether the number should be ignored * @param {number} num the number @@ -113,7 +121,7 @@ module.exports = { Literal(node) { const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"]; - if (!isNumber(node)) { + if (!isNumericLiteral(node)) { return; } diff --git a/tools/node_modules/eslint/lib/rules/no-mixed-operators.js b/tools/node_modules/eslint/lib/rules/no-mixed-operators.js index 80fac79affdb0e..37e8906e827a4c 100644 --- a/tools/node_modules/eslint/lib/rules/no-mixed-operators.js +++ b/tools/node_modules/eslint/lib/rules/no-mixed-operators.js @@ -112,7 +112,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + unexpectedMixedOperator: "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'." + } }, create(context) { @@ -188,8 +192,6 @@ module.exports = { const parent = node.parent; const left = (getChildNode(parent) === node) ? node : parent; const right = (getChildNode(parent) !== node) ? node : parent; - const message = - "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'."; const data = { leftOperator: left.operator || "?:", rightOperator: right.operator || "?:" @@ -198,13 +200,13 @@ module.exports = { context.report({ node: left, loc: getOperatorToken(left).loc, - message, + messageId: "unexpectedMixedOperator", data }); context.report({ node: right, loc: getOperatorToken(right).loc, - message, + messageId: "unexpectedMixedOperator", data }); } diff --git a/tools/node_modules/eslint/lib/rules/no-mixed-requires.js b/tools/node_modules/eslint/lib/rules/no-mixed-requires.js index fda8a11d9e2e74..8e988e32c24f84 100644 --- a/tools/node_modules/eslint/lib/rules/no-mixed-requires.js +++ b/tools/node_modules/eslint/lib/rules/no-mixed-requires.js @@ -40,7 +40,12 @@ module.exports = { } ] } - ] + ], + + messages: { + noMixRequire: "Do not mix 'require' and other declarations.", + noMixCoreModuleFileComputed: "Do not mix core, module, file and computed requires." + } }, create(context) { @@ -211,9 +216,15 @@ module.exports = { VariableDeclaration(node) { if (isMixed(node.declarations)) { - context.report({ node, message: "Do not mix 'require' and other declarations." }); + context.report({ + node, + messageId: "noMixRequire" + }); } else if (grouping && !isGrouped(node.declarations)) { - context.report({ node, message: "Do not mix core, module, file and computed requires." }); + context.report({ + node, + messageId: "noMixCoreModuleFileComputed" + }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-mixed-spaces-and-tabs.js b/tools/node_modules/eslint/lib/rules/no-mixed-spaces-and-tabs.js index 7b1e2c4a2a760c..16c2bd4122e851 100644 --- a/tools/node_modules/eslint/lib/rules/no-mixed-spaces-and-tabs.js +++ b/tools/node_modules/eslint/lib/rules/no-mixed-spaces-and-tabs.js @@ -23,14 +23,17 @@ module.exports = { { enum: ["smart-tabs", true, false] } - ] + ], + + messages: { + mixedSpacesAndTabs: "Mixed spaces and tabs." + } }, create(context) { const sourceCode = context.getSourceCode(); let smartTabs; - const ignoredLocs = []; switch (context.options[0]) { case true: // Support old syntax, maybe add deprecation warning here @@ -41,47 +44,23 @@ module.exports = { smartTabs = false; } - /** - * Determines if a given line and column are before a location. - * @param {Location} loc The location object from an AST node. - * @param {int} line The line to check. - * @param {int} column The column to check. - * @returns {boolean} True if the line and column are before the location, false if not. - * @private - */ - function beforeLoc(loc, line, column) { - if (line < loc.start.line) { - return true; - } - return line === loc.start.line && column < loc.start.column; - } - - /** - * Determines if a given line and column are after a location. - * @param {Location} loc The location object from an AST node. - * @param {int} line The line to check. - * @param {int} column The column to check. - * @returns {boolean} True if the line and column are after the location, false if not. - * @private - */ - function afterLoc(loc, line, column) { - if (line > loc.end.line) { - return true; - } - return line === loc.end.line && column > loc.end.column; - } - //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- return { - TemplateElement(node) { - ignoredLocs.push(node.loc); - }, - "Program:exit"(node) { + const lines = sourceCode.lines, + comments = sourceCode.getAllComments(), + ignoredCommentLines = new Set(); + + // Add all lines except the first ones. + comments.forEach(comment => { + for (let i = comment.loc.start.line + 1; i <= comment.loc.end.line; i++) { + ignoredCommentLines.add(i); + } + }); /* * At least one space followed by a tab @@ -89,24 +68,6 @@ module.exports = { * characters begin. */ let regex = /^(?=[\t ]*(\t | \t))/u; - const lines = sourceCode.lines, - comments = sourceCode.getAllComments(); - - comments.forEach(comment => { - ignoredLocs.push(comment.loc); - }); - - ignoredLocs.sort((first, second) => { - if (beforeLoc(first, second.start.line, second.start.column)) { - return 1; - } - - if (beforeLoc(second, first.start.line, second.start.column)) { - return -1; - } - - return 0; - }); if (smartTabs) { @@ -122,25 +83,23 @@ module.exports = { if (match) { const lineNumber = i + 1, - column = match.index + 1; - - for (let j = 0; j < ignoredLocs.length; j++) { - if (beforeLoc(ignoredLocs[j], lineNumber, column)) { - continue; + column = match.index + 1, + loc = { line: lineNumber, column }; + + if (!ignoredCommentLines.has(lineNumber)) { + const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc)); + + if (!(containingNode && ["Literal", "TemplateElement"].includes(containingNode.type))) { + context.report({ + node, + loc, + messageId: "mixedSpacesAndTabs" + }); } - if (afterLoc(ignoredLocs[j], lineNumber, column)) { - continue; - } - - return; } - - context.report({ node, loc: { line: lineNumber, column }, message: "Mixed spaces and tabs." }); } }); } - }; - } }; diff --git a/tools/node_modules/eslint/lib/rules/no-multi-assign.js b/tools/node_modules/eslint/lib/rules/no-multi-assign.js index 8524a1a571ef9e..ab6430c19ef070 100644 --- a/tools/node_modules/eslint/lib/rules/no-multi-assign.js +++ b/tools/node_modules/eslint/lib/rules/no-multi-assign.js @@ -21,7 +21,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-multi-assign" }, - schema: [] + schema: [], + + messages: { + unexpectedChain: "Unexpected chained assignment." + } }, create(context) { @@ -35,7 +39,7 @@ module.exports = { if (["AssignmentExpression", "VariableDeclarator"].indexOf(node.parent.type) !== -1) { context.report({ node, - message: "Unexpected chained assignment." + messageId: "unexpectedChain" }); } } diff --git a/tools/node_modules/eslint/lib/rules/no-multi-spaces.js b/tools/node_modules/eslint/lib/rules/no-multi-spaces.js index 403d04da9dd9ec..d43ed736337945 100644 --- a/tools/node_modules/eslint/lib/rules/no-multi-spaces.js +++ b/tools/node_modules/eslint/lib/rules/no-multi-spaces.js @@ -44,7 +44,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + multipleSpaces: "Multiple spaces found before '{{displayValue}}'." + } }, create(context) { @@ -122,7 +126,7 @@ module.exports = { context.report({ node: rightToken, loc: { start: leftToken.loc.end, end: rightToken.loc.start }, - message: "Multiple spaces found before '{{displayValue}}'.", + messageId: "multipleSpaces", data: { displayValue }, fix: fixer => fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ") }); diff --git a/tools/node_modules/eslint/lib/rules/no-multi-str.js b/tools/node_modules/eslint/lib/rules/no-multi-str.js index f6832f3341777f..7cf1ae367942fe 100644 --- a/tools/node_modules/eslint/lib/rules/no-multi-str.js +++ b/tools/node_modules/eslint/lib/rules/no-multi-str.js @@ -26,7 +26,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-multi-str" }, - schema: [] + schema: [], + + messages: { + multilineString: "Multiline support is limited to browsers supporting ES5 only." + } }, create(context) { @@ -49,7 +53,10 @@ module.exports = { Literal(node) { if (astUtils.LINEBREAK_MATCHER.test(node.raw) && !isJSXElement(node.parent)) { - context.report({ node, message: "Multiline support is limited to browsers supporting ES5 only." }); + context.report({ + node, + messageId: "multilineString" + }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-multiple-empty-lines.js b/tools/node_modules/eslint/lib/rules/no-multiple-empty-lines.js index 41e6be3a289584..9cccef3088a56e 100644 --- a/tools/node_modules/eslint/lib/rules/no-multiple-empty-lines.js +++ b/tools/node_modules/eslint/lib/rules/no-multiple-empty-lines.js @@ -42,7 +42,13 @@ module.exports = { required: ["max"], additionalProperties: false } - ] + ], + + messages: { + blankBeginningOfFile: "Too many blank lines at the beginning of file. Max of {{max}} allowed.", + blankEndOfFile: "Too many blank lines at the end of file. Max of {{max}} allowed.", + consecutiveBlank: "More than {{max}} blank {{pluralizedLines}} not allowed." + } }, create(context) { @@ -94,25 +100,31 @@ module.exports = { // Given two line numbers of non-empty lines, report the lines between if the difference is too large. .reduce((lastLineNumber, lineNumber) => { - let message, maxAllowed; + let messageId, maxAllowed; if (lastLineNumber === 0) { - message = "Too many blank lines at the beginning of file. Max of {{max}} allowed."; + messageId = "blankBeginningOfFile"; maxAllowed = maxBOF; } else if (lineNumber === allLines.length + 1) { - message = "Too many blank lines at the end of file. Max of {{max}} allowed."; + messageId = "blankEndOfFile"; maxAllowed = maxEOF; } else { - message = "More than {{max}} blank {{pluralizedLines}} not allowed."; + messageId = "consecutiveBlank"; maxAllowed = max; } if (lineNumber - lastLineNumber - 1 > maxAllowed) { context.report({ node, - loc: { start: { line: lastLineNumber + maxAllowed + 1, column: 0 }, end: { line: lineNumber, column: 0 } }, - message, - data: { max: maxAllowed, pluralizedLines: maxAllowed === 1 ? "line" : "lines" }, + loc: { + start: { line: lastLineNumber + maxAllowed + 1, column: 0 }, + end: { line: lineNumber, column: 0 } + }, + messageId, + data: { + max: maxAllowed, + pluralizedLines: maxAllowed === 1 ? "line" : "lines" + }, fix(fixer) { const rangeStart = sourceCode.getIndexFromLoc({ line: lastLineNumber + 1, column: 0 }); diff --git a/tools/node_modules/eslint/lib/rules/no-negated-condition.js b/tools/node_modules/eslint/lib/rules/no-negated-condition.js index e55a8287487de3..8a9eba881df04c 100644 --- a/tools/node_modules/eslint/lib/rules/no-negated-condition.js +++ b/tools/node_modules/eslint/lib/rules/no-negated-condition.js @@ -19,7 +19,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-negated-condition" }, - schema: [] + schema: [], + + messages: { + unexpectedNegated: "Unexpected negated condition." + } }, create(context) { @@ -72,12 +76,18 @@ module.exports = { } if (isNegatedIf(node)) { - context.report({ node, message: "Unexpected negated condition." }); + context.report({ + node, + messageId: "unexpectedNegated" + }); } }, ConditionalExpression(node) { if (isNegatedIf(node)) { - context.report({ node, message: "Unexpected negated condition." }); + context.report({ + node, + messageId: "unexpectedNegated" + }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-nested-ternary.js b/tools/node_modules/eslint/lib/rules/no-nested-ternary.js index 87a11e87962a8f..383bb238887d2a 100644 --- a/tools/node_modules/eslint/lib/rules/no-nested-ternary.js +++ b/tools/node_modules/eslint/lib/rules/no-nested-ternary.js @@ -20,7 +20,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-nested-ternary" }, - schema: [] + schema: [], + + messages: { + noNestedTernary: "Do not nest ternary expressions." + } }, create(context) { @@ -29,7 +33,10 @@ module.exports = { ConditionalExpression(node) { if (node.alternate.type === "ConditionalExpression" || node.consequent.type === "ConditionalExpression") { - context.report({ node, message: "Do not nest ternary expressions." }); + context.report({ + node, + messageId: "noNestedTernary" + }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-new-func.js b/tools/node_modules/eslint/lib/rules/no-new-func.js index 23e92f7bf3030c..d1360e9dee036d 100644 --- a/tools/node_modules/eslint/lib/rules/no-new-func.js +++ b/tools/node_modules/eslint/lib/rules/no-new-func.js @@ -20,7 +20,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-new-func" }, - schema: [] + schema: [], + + messages: { + noFunctionConstructor: "The Function constructor is eval." + } }, create(context) { @@ -36,7 +40,10 @@ module.exports = { * @private */ function report(node) { - context.report({ node, message: "The Function constructor is eval." }); + context.report({ + node, + messageId: "noFunctionConstructor" + }); } return { diff --git a/tools/node_modules/eslint/lib/rules/no-new-object.js b/tools/node_modules/eslint/lib/rules/no-new-object.js index f5cc28664f4e1c..f3e99c9bd13502 100644 --- a/tools/node_modules/eslint/lib/rules/no-new-object.js +++ b/tools/node_modules/eslint/lib/rules/no-new-object.js @@ -20,7 +20,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-new-object" }, - schema: [] + schema: [], + + messages: { + preferLiteral: "The object literal notation {} is preferrable." + } }, create(context) { @@ -29,7 +33,10 @@ module.exports = { NewExpression(node) { if (node.callee.name === "Object") { - context.report({ node, message: "The object literal notation {} is preferrable." }); + context.report({ + node, + messageId: "preferLiteral" + }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-new-require.js b/tools/node_modules/eslint/lib/rules/no-new-require.js index 1eae0659430fbd..df12a424e3527e 100644 --- a/tools/node_modules/eslint/lib/rules/no-new-require.js +++ b/tools/node_modules/eslint/lib/rules/no-new-require.js @@ -20,7 +20,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-new-require" }, - schema: [] + schema: [], + + messages: { + noNewRequire: "Unexpected use of new with require." + } }, create(context) { @@ -29,7 +33,10 @@ module.exports = { NewExpression(node) { if (node.callee.type === "Identifier" && node.callee.name === "require") { - context.report({ node, message: "Unexpected use of new with require." }); + context.report({ + node, + messageId: "noNewRequire" + }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-new-symbol.js b/tools/node_modules/eslint/lib/rules/no-new-symbol.js index ccf757ed6a0cee..cb7e4f0fc88d69 100644 --- a/tools/node_modules/eslint/lib/rules/no-new-symbol.js +++ b/tools/node_modules/eslint/lib/rules/no-new-symbol.js @@ -20,7 +20,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-new-symbol" }, - schema: [] + schema: [], + + messages: { + noNewSymbol: "`Symbol` cannot be called as a constructor." + } }, create(context) { @@ -35,7 +39,10 @@ module.exports = { const node = ref.identifier; if (node.parent && node.parent.type === "NewExpression") { - context.report({ node, message: "`Symbol` cannot be called as a constructor." }); + context.report({ + node, + messageId: "noNewSymbol" + }); } }); } diff --git a/tools/node_modules/eslint/lib/rules/no-new-wrappers.js b/tools/node_modules/eslint/lib/rules/no-new-wrappers.js index ae2aeec0341243..0a2861fa5f7799 100644 --- a/tools/node_modules/eslint/lib/rules/no-new-wrappers.js +++ b/tools/node_modules/eslint/lib/rules/no-new-wrappers.js @@ -20,7 +20,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-new-wrappers" }, - schema: [] + schema: [], + + messages: { + noConstructor: "Do not use {{fn}} as a constructor." + } }, create(context) { @@ -31,7 +35,11 @@ module.exports = { const wrapperObjects = ["String", "Number", "Boolean", "Math", "JSON"]; if (wrapperObjects.indexOf(node.callee.name) > -1) { - context.report({ node, message: "Do not use {{fn}} as a constructor.", data: { fn: node.callee.name } }); + context.report({ + node, + messageId: "noConstructor", + data: { fn: node.callee.name } + }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-new.js b/tools/node_modules/eslint/lib/rules/no-new.js index 2e0702597eade3..aa8a4e26876609 100644 --- a/tools/node_modules/eslint/lib/rules/no-new.js +++ b/tools/node_modules/eslint/lib/rules/no-new.js @@ -21,14 +21,21 @@ module.exports = { url: "https://eslint.org/docs/rules/no-new" }, - schema: [] + schema: [], + + messages: { + noNewStatement: "Do not use 'new' for side effects." + } }, create(context) { return { "ExpressionStatement > NewExpression"(node) { - context.report({ node: node.parent, message: "Do not use 'new' for side effects." }); + context.report({ + node: node.parent, + messageId: "noNewStatement" + }); } }; diff --git a/tools/node_modules/eslint/lib/rules/no-octal.js b/tools/node_modules/eslint/lib/rules/no-octal.js index 5ee69f0309ee03..e9940befafa889 100644 --- a/tools/node_modules/eslint/lib/rules/no-octal.js +++ b/tools/node_modules/eslint/lib/rules/no-octal.js @@ -20,7 +20,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-octal" }, - schema: [] + schema: [], + + messages: { + noOcatal: "Octal literals should not be used." + } }, create(context) { @@ -29,7 +33,10 @@ module.exports = { Literal(node) { if (typeof node.value === "number" && /^0[0-9]/u.test(node.raw)) { - context.report({ node, message: "Octal literals should not be used." }); + context.report({ + node, + messageId: "noOcatal" + }); } } }; diff --git a/tools/node_modules/eslint/lib/rules/no-param-reassign.js b/tools/node_modules/eslint/lib/rules/no-param-reassign.js index d65eb34762aaf2..6874af44f389b7 100644 --- a/tools/node_modules/eslint/lib/rules/no-param-reassign.js +++ b/tools/node_modules/eslint/lib/rules/no-param-reassign.js @@ -58,7 +58,12 @@ module.exports = { } ] } - ] + ], + + messages: { + assignmentToFunctionParam: "Assignment to function parameter '{{name}}'.", + assignmentToFunctionParamProp: "Assignment to property of function parameter '{{name}}'." + } }, create(context) { @@ -177,9 +182,17 @@ module.exports = { (index === 0 || references[index - 1].identifier !== identifier) ) { if (reference.isWrite()) { - context.report({ node: identifier, message: "Assignment to function parameter '{{name}}'.", data: { name: identifier.name } }); + context.report({ + node: identifier, + messageId: "assignmentToFunctionParam", + data: { name: identifier.name } + }); } else if (props && isModifyingProp(reference) && !isIgnoredPropertyAssignment(identifier.name)) { - context.report({ node: identifier, message: "Assignment to property of function parameter '{{name}}'.", data: { name: identifier.name } }); + context.report({ + node: identifier, + messageId: "assignmentToFunctionParamProp", + data: { name: identifier.name } + }); } } } diff --git a/tools/node_modules/eslint/lib/rules/no-path-concat.js b/tools/node_modules/eslint/lib/rules/no-path-concat.js index abe0d5247db719..9fa8b852fe8358 100644 --- a/tools/node_modules/eslint/lib/rules/no-path-concat.js +++ b/tools/node_modules/eslint/lib/rules/no-path-concat.js @@ -19,7 +19,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-path-concat" }, - schema: [] + schema: [], + + messages: { + usePathFunctions: "Use path.join() or path.resolve() instead of + to create paths." + } }, create(context) { @@ -42,7 +46,10 @@ module.exports = { (right.type === "Identifier" && MATCHER.test(right.name))) ) { - context.report({ node, message: "Use path.join() or path.resolve() instead of + to create paths." }); + context.report({ + node, + messageId: "usePathFunctions" + }); } } diff --git a/tools/node_modules/eslint/lib/rules/no-plusplus.js b/tools/node_modules/eslint/lib/rules/no-plusplus.js index 1d122dcd31f086..f55303863d25ca 100644 --- a/tools/node_modules/eslint/lib/rules/no-plusplus.js +++ b/tools/node_modules/eslint/lib/rules/no-plusplus.js @@ -32,7 +32,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + unexpectedUnaryOp: "Unary operator '{{operator}}' used." + } }, create(context) { @@ -52,7 +56,7 @@ module.exports = { } context.report({ node, - message: "Unary operator '{{operator}}' used.", + messageId: "unexpectedUnaryOp", data: { operator: node.operator } diff --git a/tools/node_modules/eslint/lib/rules/no-restricted-exports.js b/tools/node_modules/eslint/lib/rules/no-restricted-exports.js new file mode 100644 index 00000000000000..5b5c7d9bffb99c --- /dev/null +++ b/tools/node_modules/eslint/lib/rules/no-restricted-exports.js @@ -0,0 +1,84 @@ +/** + * @fileoverview Rule to disallow specified names in exports + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + + docs: { + description: "disallow specified names in exports", + category: "ECMAScript 6", + recommended: false, + url: "https://eslint.org/docs/rules/no-restricted-exports" + }, + + schema: [{ + type: "object", + properties: { + restrictedNamedExports: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } + }, + additionalProperties: false + }], + + messages: { + restrictedNamed: "'{{name}}' is restricted from being used as an exported name." + } + }, + + create(context) { + + const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports); + + /** + * Checks and reports given exported identifier. + * @param {ASTNode} node exported `Identifer` node to check. + * @returns {void} + */ + function checkExportedName(node) { + const name = node.name; + + if (restrictedNames.has(name)) { + context.report({ + node, + messageId: "restrictedNamed", + data: { name } + }); + } + } + + return { + ExportNamedDeclaration(node) { + const declaration = node.declaration; + + if (declaration) { + if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") { + checkExportedName(declaration.id); + } else if (declaration.type === "VariableDeclaration") { + context.getDeclaredVariables(declaration) + .map(v => v.defs.find(d => d.parent === declaration)) + .map(d => d.name) // Identifier nodes + .forEach(checkExportedName); + } + } else { + node.specifiers + .map(s => s.exported) + .forEach(checkExportedName); + } + } + }; + } +}; diff --git a/tools/node_modules/eslint/lib/rules/no-restricted-globals.js b/tools/node_modules/eslint/lib/rules/no-restricted-globals.js index 1a2629a8ec95e8..2c932a7307c0b2 100644 --- a/tools/node_modules/eslint/lib/rules/no-restricted-globals.js +++ b/tools/node_modules/eslint/lib/rules/no-restricted-globals.js @@ -4,13 +4,6 @@ */ "use strict"; -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -const DEFAULT_MESSAGE_TEMPLATE = "Unexpected use of '{{name}}'.", - CUSTOM_MESSAGE_TEMPLATE = "Unexpected use of '{{name}}'. {{customMessage}}"; - //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -46,6 +39,12 @@ module.exports = { }, uniqueItems: true, minItems: 0 + }, + + messages: { + defaultMessage: "Unexpected use of '{{name}}'.", + // eslint-disable-next-line eslint-plugin/report-message-format + customMessage: "Unexpected use of '{{name}}'. {{customMessage}}" } }, @@ -75,13 +74,13 @@ module.exports = { function reportReference(reference) { const name = reference.identifier.name, customMessage = restrictedGlobalMessages[name], - message = customMessage - ? CUSTOM_MESSAGE_TEMPLATE - : DEFAULT_MESSAGE_TEMPLATE; + messageId = customMessage + ? "customMessage" + : "defaultMessage"; context.report({ node: reference.identifier, - message, + messageId, data: { name, customMessage diff --git a/tools/node_modules/eslint/lib/rules/no-restricted-imports.js b/tools/node_modules/eslint/lib/rules/no-restricted-imports.js index ec0696f99a2de0..c205dad8bdb79d 100644 --- a/tools/node_modules/eslint/lib/rules/no-restricted-imports.js +++ b/tools/node_modules/eslint/lib/rules/no-restricted-imports.js @@ -64,7 +64,11 @@ module.exports = { everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.", // eslint-disable-next-line eslint-plugin/report-message-format - everythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted. {{customMessage}}" + everythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted. {{customMessage}}", + + importName: "'{{importName}}' import from '{{importSource}}' is restricted.", + // eslint-disable-next-line eslint-plugin/report-message-format + importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}" }, schema: { @@ -87,6 +91,7 @@ module.exports = { }, create(context) { + const sourceCode = context.getSourceCode(); const options = Array.isArray(context.options) ? context.options : []; const isPathAndPatternsObject = typeof options[0] === "object" && @@ -95,6 +100,11 @@ module.exports = { const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || []; const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || []; + // if no imports are restricted we don"t need to check + if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) { + return {}; + } + const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => { if (typeof importSource === "string") { memo[importSource] = { message: null }; @@ -107,40 +117,68 @@ module.exports = { return memo; }, {}); - // if no imports are restricted we don"t need to check - if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) { - return {}; - } - const restrictedPatternsMatcher = ignore().add(restrictedPatterns); - /** - * Checks to see if "*" is being used to import everything. - * @param {Set.} importNames Set of import names that are being imported - * @returns {boolean} whether everything is imported or not - */ - function isEverythingImported(importNames) { - return importNames.has("*"); - } - /** * Report a restricted path. + * @param {string} importSource path of the import + * @param {Map} importNames Map of import names that are being imported * @param {node} node representing the restricted path reference * @returns {void} * @private */ - function reportPath(node) { - const importSource = node.source.value.trim(); - const customMessage = restrictedPathMessages[importSource] && restrictedPathMessages[importSource].message; + function checkRestrictedPathAndReport(importSource, importNames, node) { + if (!Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) { + return; + } - context.report({ - node, - messageId: customMessage ? "pathWithCustomMessage" : "path", - data: { - importSource, - customMessage + const customMessage = restrictedPathMessages[importSource].message; + const restrictedImportNames = restrictedPathMessages[importSource].importNames; + + if (restrictedImportNames) { + if (importNames.has("*")) { + const specifierData = importNames.get("*")[0]; + + context.report({ + node, + messageId: customMessage ? "everythingWithCustomMessage" : "everything", + loc: specifierData.loc, + data: { + importSource, + importNames: restrictedImportNames, + customMessage + } + }); } - }); + + restrictedImportNames.forEach(importName => { + if (importNames.has(importName)) { + const specifiers = importNames.get(importName); + + specifiers.forEach(specifier => { + context.report({ + node, + messageId: customMessage ? "importNameWithCustomMessage" : "importName", + loc: specifier.loc, + data: { + importSource, + customMessage, + importName + } + }); + }); + } + }); + } else { + context.report({ + node, + messageId: customMessage ? "pathWithCustomMessage" : "path", + data: { + importSource, + customMessage + } + }); + } } /** @@ -161,75 +199,6 @@ module.exports = { }); } - /** - * Report a restricted path specifically when using the '*' import. - * @param {string} importSource path of the import - * @param {node} node representing the restricted path reference - * @returns {void} - * @private - */ - function reportPathForEverythingImported(importSource, node) { - const importNames = restrictedPathMessages[importSource].importNames; - const customMessage = restrictedPathMessages[importSource] && restrictedPathMessages[importSource].message; - - context.report({ - node, - messageId: customMessage ? "everythingWithCustomMessage" : "everything", - data: { - importSource, - importNames, - customMessage - } - }); - } - - /** - * Check if the given importSource is restricted because '*' is being imported. - * @param {string} importSource path of the import - * @param {Set.} importNames Set of import names that are being imported - * @returns {boolean} whether the path is restricted - * @private - */ - function isRestrictedForEverythingImported(importSource, importNames) { - return Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource) && - restrictedPathMessages[importSource].importNames && - isEverythingImported(importNames); - } - - /** - * Check if the given importNames are restricted given a list of restrictedImportNames. - * @param {Set.} importNames Set of import names that are being imported - * @param {string[]} restrictedImportNames array of import names that are restricted for this import - * @returns {boolean} whether the objectName is restricted - * @private - */ - function isRestrictedObject(importNames, restrictedImportNames) { - return restrictedImportNames.some(restrictedObjectName => ( - importNames.has(restrictedObjectName) - )); - } - - /** - * Check if the given importSource is a restricted path. - * @param {string} importSource path of the import - * @param {Set.} importNames Set of import names that are being imported - * @returns {boolean} whether the variable is a restricted path or not - * @private - */ - function isRestrictedPath(importSource, importNames) { - let isRestricted = false; - - if (Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) { - if (restrictedPathMessages[importSource].importNames) { - isRestricted = isRestrictedObject(importNames, restrictedPathMessages[importSource].importNames); - } else { - isRestricted = true; - } - } - - return isRestricted; - } - /** * Check if the given importSource is restricted by a pattern. * @param {string} importSource path of the import @@ -248,26 +217,39 @@ module.exports = { */ function checkNode(node) { const importSource = node.source.value.trim(); - const importNames = node.specifiers ? node.specifiers.reduce((set, specifier) => { - if (specifier.type === "ImportDefaultSpecifier") { - set.add("default"); - } else if (specifier.type === "ImportNamespaceSpecifier") { - set.add("*"); - } else if (specifier.imported) { - set.add(specifier.imported.name); - } else if (specifier.local) { - set.add(specifier.local.name); - } - return set; - }, new Set()) : new Set(); + const importNames = new Map(); + + if (node.type === "ExportAllDeclaration") { + const starToken = sourceCode.getFirstToken(node, 1); + + importNames.set("*", [{ loc: starToken.loc }]); + } else if (node.specifiers) { + for (const specifier of node.specifiers) { + let name; + const specifierData = { loc: specifier.loc }; + + if (specifier.type === "ImportDefaultSpecifier") { + name = "default"; + } else if (specifier.type === "ImportNamespaceSpecifier") { + name = "*"; + } else if (specifier.imported) { + name = specifier.imported.name; + } else if (specifier.local) { + name = specifier.local.name; + } - if (isRestrictedForEverythingImported(importSource, importNames)) { - reportPathForEverythingImported(importSource, node); + if (name) { + if (importNames.has(name)) { + importNames.get(name).push(specifierData); + } else { + importNames.set(name, [specifierData]); + } + } + } } - if (isRestrictedPath(importSource, importNames)) { - reportPath(node); - } + checkRestrictedPathAndReport(importSource, importNames, node); + if (isRestrictedPattern(importSource)) { reportPathForPatterns(node); } diff --git a/tools/node_modules/eslint/lib/rules/no-shadow.js b/tools/node_modules/eslint/lib/rules/no-shadow.js index bad6cb5f3094cc..1be8590e47abcb 100644 --- a/tools/node_modules/eslint/lib/rules/no-shadow.js +++ b/tools/node_modules/eslint/lib/rules/no-shadow.js @@ -41,7 +41,11 @@ module.exports = { }, additionalProperties: false } - ] + ], + + messages: { + noShadow: "'{{name}}' is already declared in the upper scope." + } }, create(context) { @@ -163,7 +167,7 @@ module.exports = { ) { context.report({ node: variable.identifiers[0], - message: "'{{name}}' is already declared in the upper scope.", + messageId: "noShadow", data: variable }); } diff --git a/tools/node_modules/eslint/lib/rules/no-use-before-define.js b/tools/node_modules/eslint/lib/rules/no-use-before-define.js index ed3540532f9095..c7300567ede0b7 100644 --- a/tools/node_modules/eslint/lib/rules/no-use-before-define.js +++ b/tools/node_modules/eslint/lib/rules/no-use-before-define.js @@ -157,7 +157,11 @@ module.exports = { } ] } - ] + ], + + messages: { + usedBeforeDefined: "'{{name}}' was used before it was defined." + } }, create(context) { @@ -212,7 +216,7 @@ module.exports = { // Reports. context.report({ node: reference.identifier, - message: "'{{name}}' was used before it was defined.", + messageId: "usedBeforeDefined", data: reference.identifier }); }); diff --git a/tools/node_modules/eslint/lib/rules/no-useless-backreference.js b/tools/node_modules/eslint/lib/rules/no-useless-backreference.js new file mode 100644 index 00000000000000..8a6fbe14daafe6 --- /dev/null +++ b/tools/node_modules/eslint/lib/rules/no-useless-backreference.js @@ -0,0 +1,193 @@ +/** + * @fileoverview Rule to disallow useless backreferences in regular expressions + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils"); +const { RegExpParser, visitRegExpAST } = require("regexpp"); +const lodash = require("lodash"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const parser = new RegExpParser(); + +/** + * Finds the path from the given `regexpp` AST node to the root node. + * @param {regexpp.Node} node Node. + * @returns {regexpp.Node[]} Array that starts with the given node and ends with the root node. + */ +function getPathToRoot(node) { + const path = []; + let current = node; + + do { + path.push(current); + current = current.parent; + } while (current); + + return path; +} + +/** + * Determines whether the given `regexpp` AST node is a lookaround node. + * @param {regexpp.Node} node Node. + * @returns {boolean} `true` if it is a lookaround node. + */ +function isLookaround(node) { + return node.type === "Assertion" && + (node.kind === "lookahead" || node.kind === "lookbehind"); +} + +/** + * Determines whether the given `regexpp` AST node is a negative lookaround node. + * @param {regexpp.Node} node Node. + * @returns {boolean} `true` if it is a negative lookaround node. + */ +function isNegativeLookaround(node) { + return isLookaround(node) && node.negate; +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow useless backreferences in regular expressions", + category: "Possible Errors", + recommended: false, + url: "https://eslint.org/docs/rules/no-useless-backreference" + }, + + schema: [], + + messages: { + nested: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' from within that group.", + forward: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which appears later in the pattern.", + backward: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which appears before in the same lookbehind.", + disjunctive: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which is in another alternative.", + intoNegativeLookaround: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}' which is in a negative lookaround." + } + }, + + create(context) { + + /** + * Checks and reports useless backreferences in the given regular expression. + * @param {ASTNode} node Node that represents regular expression. A regex literal or RegExp constructor call. + * @param {string} pattern Regular expression pattern. + * @param {string} flags Regular expression flags. + * @returns {void} + */ + function checkRegex(node, pattern, flags) { + let regExpAST; + + try { + regExpAST = parser.parsePattern(pattern, 0, pattern.length, flags.includes("u")); + } catch (e) { + + // Ignore regular expressions with syntax errors + return; + } + + visitRegExpAST(regExpAST, { + onBackreferenceEnter(bref) { + const group = bref.resolved, + brefPath = getPathToRoot(bref), + groupPath = getPathToRoot(group); + let messageId = null; + + if (brefPath.includes(group)) { + + // group is bref's ancestor => bref is nested ('nested reference') => group hasn't matched yet when bref starts to match. + messageId = "nested"; + } else { + + // Start from the root to find the lowest common ancestor. + let i = brefPath.length - 1, + j = groupPath.length - 1; + + do { + i--; + j--; + } while (brefPath[i] === groupPath[j]); + + const indexOfLowestCommonAncestor = j + 1, + groupCut = groupPath.slice(0, indexOfLowestCommonAncestor), + commonPath = groupPath.slice(indexOfLowestCommonAncestor), + lowestCommonLookaround = commonPath.find(isLookaround), + isMatchingBackward = lowestCommonLookaround && lowestCommonLookaround.kind === "lookbehind"; + + if (!isMatchingBackward && bref.end <= group.start) { + + // bref is left, group is right ('forward reference') => group hasn't matched yet when bref starts to match. + messageId = "forward"; + } else if (isMatchingBackward && group.end <= bref.start) { + + // the opposite of the previous when the regex is matching backward in a lookbehind context. + messageId = "backward"; + } else if (lodash.last(groupCut).type === "Alternative") { + + // group's and bref's ancestor nodes below the lowest common ancestor are sibling alternatives => they're disjunctive. + messageId = "disjunctive"; + } else if (groupCut.some(isNegativeLookaround)) { + + // group is in a negative lookaround which isn't bref's ancestor => group has already failed when bref starts to match. + messageId = "intoNegativeLookaround"; + } + } + + if (messageId) { + context.report({ + node, + messageId, + data: { + bref: bref.raw, + group: group.raw + } + }); + } + } + }); + } + + return { + "Literal[regex]"(node) { + const { pattern, flags } = node.regex; + + checkRegex(node, pattern, flags); + }, + Program() { + const scope = context.getScope(), + tracker = new ReferenceTracker(scope), + traceMap = { + RegExp: { + [CALL]: true, + [CONSTRUCT]: true + } + }; + + for (const { node } of tracker.iterateGlobalReferences(traceMap)) { + const [patternNode, flagsNode] = node.arguments, + pattern = getStringIfConstant(patternNode, scope), + flags = getStringIfConstant(flagsNode, scope); + + if (typeof pattern === "string") { + checkRegex(node, pattern, flags || ""); + } + } + } + }; + } +}; diff --git a/tools/node_modules/eslint/lib/rules/no-useless-computed-key.js b/tools/node_modules/eslint/lib/rules/no-useless-computed-key.js index b5e53174e42be7..0e0acbea7c381c 100644 --- a/tools/node_modules/eslint/lib/rules/no-useless-computed-key.js +++ b/tools/node_modules/eslint/lib/rules/no-useless-computed-key.js @@ -71,14 +71,11 @@ module.exports = { message: MESSAGE_UNNECESSARY_COMPUTED, data: { property: sourceCode.getText(key) }, fix(fixer) { - const leftSquareBracket = sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken); - const rightSquareBracket = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken); - const tokensBetween = sourceCode.getTokensBetween(leftSquareBracket, rightSquareBracket, 1); + const leftSquareBracket = sourceCode.getTokenBefore(key, astUtils.isOpeningBracketToken); + const rightSquareBracket = sourceCode.getTokenAfter(key, astUtils.isClosingBracketToken); - if (tokensBetween.slice(0, -1).some((token, index) => - sourceCode.getText().slice(token.range[1], tokensBetween[index + 1].range[0]).trim())) { - - // If there are comments between the brackets and the property name, don't do a fix. + // If there are comments between the brackets and the property name, don't do a fix. + if (sourceCode.commentsExistBetween(leftSquareBracket, rightSquareBracket)) { return null; } diff --git a/tools/node_modules/eslint/lib/rules/no-useless-constructor.js b/tools/node_modules/eslint/lib/rules/no-useless-constructor.js index 7cf033805f99d9..2920328c6f39c0 100644 --- a/tools/node_modules/eslint/lib/rules/no-useless-constructor.js +++ b/tools/node_modules/eslint/lib/rules/no-useless-constructor.js @@ -143,7 +143,11 @@ module.exports = { url: "https://eslint.org/docs/rules/no-useless-constructor" }, - schema: [] + schema: [], + + messages: { + noUselessConstructor: "Useless constructor." + } }, create(context) { @@ -165,7 +169,7 @@ module.exports = { if (superClass ? isRedundantSuperCall(body, ctorParams) : (body.length === 0)) { context.report({ node, - message: "Useless constructor." + messageId: "noUselessConstructor" }); } } diff --git a/tools/node_modules/eslint/lib/rules/no-void.js b/tools/node_modules/eslint/lib/rules/no-void.js index d2b5d2f9631dff..99c83785552ab8 100644 --- a/tools/node_modules/eslint/lib/rules/no-void.js +++ b/tools/node_modules/eslint/lib/rules/no-void.js @@ -19,22 +19,46 @@ module.exports = { url: "https://eslint.org/docs/rules/no-void" }, - schema: [] + messages: { + noVoid: "Expected 'undefined' and instead saw 'void'." + }, + + schema: [ + { + type: "object", + properties: { + allowAsStatement: { + type: "boolean", + default: false + } + }, + additionalProperties: false + } + ] }, create(context) { + const allowAsStatement = + context.options[0] && context.options[0].allowAsStatement; //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- return { - UnaryExpression(node) { - if (node.operator === "void") { - context.report({ node, message: "Expected 'undefined' and instead saw 'void'." }); + 'UnaryExpression[operator="void"]'(node) { + if ( + allowAsStatement && + node.parent && + node.parent.type === "ExpressionStatement" + ) { + return; } + context.report({ + node, + messageId: "noVoid" + }); } }; - } }; diff --git a/tools/node_modules/eslint/lib/rules/prefer-object-spread.js b/tools/node_modules/eslint/lib/rules/prefer-object-spread.js index bbcd88bee3d071..00f5fb6d4c8991 100644 --- a/tools/node_modules/eslint/lib/rules/prefer-object-spread.js +++ b/tools/node_modules/eslint/lib/rules/prefer-object-spread.js @@ -25,6 +25,36 @@ function hasArraySpread(node) { return node.arguments.some(arg => arg.type === "SpreadElement"); } +/** + * Determines whether the given node is an accessor property (getter/setter). + * @param {ASTNode} node Node to check. + * @returns {boolean} `true` if the node is a getter or a setter. + */ +function isAccessorProperty(node) { + return node.type === "Property" && + (node.kind === "get" || node.kind === "set"); +} + +/** + * Determines whether the given object expression node has accessor properties (getters/setters). + * @param {ASTNode} node `ObjectExpression` node to check. + * @returns {boolean} `true` if the node has at least one getter/setter. + */ +function hasAccessors(node) { + return node.properties.some(isAccessorProperty); +} + +/** + * Determines whether the given call expression node has object expression arguments with accessor properties (getters/setters). + * @param {ASTNode} node `CallExpression` node to check. + * @returns {boolean} `true` if the node has at least one argument that is an object expression with at least one getter/setter. + */ +function hasArgumentsWithAccessors(node) { + return node.arguments + .filter(arg => arg.type === "ObjectExpression") + .some(hasAccessors); +} + /** * Helper that checks if the node needs parentheses to be valid JS. * The default is to wrap the node in parentheses to avoid parsing errors. @@ -249,7 +279,11 @@ module.exports = { if ( node.arguments.length >= 1 && node.arguments[0].type === "ObjectExpression" && - !hasArraySpread(node) + !hasArraySpread(node) && + !( + node.arguments.length > 1 && + hasArgumentsWithAccessors(node) + ) ) { const messageId = node.arguments.length === 1 ? "useLiteralMessage" diff --git a/tools/node_modules/eslint/lib/rules/quote-props.js b/tools/node_modules/eslint/lib/rules/quote-props.js index ab09b8fa938e63..4cc53b988f634d 100644 --- a/tools/node_modules/eslint/lib/rules/quote-props.js +++ b/tools/node_modules/eslint/lib/rules/quote-props.js @@ -8,8 +8,9 @@ // Requirements //------------------------------------------------------------------------------ -const espree = require("espree"), - keywords = require("./utils/keywords"); +const espree = require("espree"); +const astUtils = require("./utils/ast-utils"); +const keywords = require("./utils/keywords"); //------------------------------------------------------------------------------ // Rule Definition @@ -177,7 +178,7 @@ module.exports = { data: { property: key.name }, fix: fixer => fixer.replaceText(key, getQuotedKey(key)) }); - } else if (NUMBERS && key.type === "Literal" && typeof key.value === "number") { + } else if (NUMBERS && key.type === "Literal" && astUtils.isNumericLiteral(key)) { context.report({ node, message: MESSAGE_NUMERIC, diff --git a/tools/node_modules/eslint/lib/rules/quotes.js b/tools/node_modules/eslint/lib/rules/quotes.js index f78d1129425633..d1f4443b9033c9 100644 --- a/tools/node_modules/eslint/lib/rules/quotes.js +++ b/tools/node_modules/eslint/lib/rules/quotes.js @@ -110,7 +110,11 @@ module.exports = { } ] } - ] + ], + + messages: { + wrongQuotes: "Strings must use {{description}}." + } }, create(context) { @@ -273,7 +277,7 @@ module.exports = { if (!isValid) { context.report({ node, - message: "Strings must use {{description}}.", + messageId: "wrongQuotes", data: { description: settings.description }, @@ -304,7 +308,7 @@ module.exports = { context.report({ node, - message: "Strings must use {{description}}.", + messageId: "wrongQuotes", data: { description: settings.description }, diff --git a/tools/node_modules/eslint/lib/rules/require-await.js b/tools/node_modules/eslint/lib/rules/require-await.js index 22c111b6dc852e..274e241cfc7d5b 100644 --- a/tools/node_modules/eslint/lib/rules/require-await.js +++ b/tools/node_modules/eslint/lib/rules/require-await.js @@ -39,7 +39,11 @@ module.exports = { url: "https://eslint.org/docs/rules/require-await" }, - schema: [] + schema: [], + + messages: { + missingAwait: "{{name}} has no 'await' expression." + } }, create(context) { @@ -68,7 +72,7 @@ module.exports = { context.report({ node, loc: astUtils.getFunctionHeadLoc(node, sourceCode), - message: "{{name}} has no 'await' expression.", + messageId: "missingAwait", data: { name: capitalizeFirstLetter( astUtils.getFunctionNameWithKind(node) diff --git a/tools/node_modules/eslint/lib/rules/semi.js b/tools/node_modules/eslint/lib/rules/semi.js index 22e299efe72e49..3491f4765c77a2 100644 --- a/tools/node_modules/eslint/lib/rules/semi.js +++ b/tools/node_modules/eslint/lib/rules/semi.js @@ -67,6 +67,11 @@ module.exports = { maxItems: 2 } ] + }, + + messages: { + missingSemi: "Missing semicolon.", + extraSemi: "Extra semicolon." } }, @@ -91,12 +96,12 @@ module.exports = { */ function report(node, missing) { const lastToken = sourceCode.getLastToken(node); - let message, + let messageId, fix, loc; if (!missing) { - message = "Missing semicolon."; + messageId = "missingSemi"; loc = { start: lastToken.loc.end, end: astUtils.getNextLocation(sourceCode, lastToken.loc.end) @@ -105,7 +110,7 @@ module.exports = { return fixer.insertTextAfter(lastToken, ";"); }; } else { - message = "Extra semicolon."; + messageId = "extraSemi"; loc = lastToken.loc; fix = function(fixer) { @@ -123,7 +128,7 @@ module.exports = { context.report({ node, loc, - message, + messageId, fix }); diff --git a/tools/node_modules/eslint/lib/rules/template-curly-spacing.js b/tools/node_modules/eslint/lib/rules/template-curly-spacing.js index 07da6a39b0e9bb..a16c0732df7b50 100644 --- a/tools/node_modules/eslint/lib/rules/template-curly-spacing.js +++ b/tools/node_modules/eslint/lib/rules/template-curly-spacing.js @@ -57,7 +57,7 @@ module.exports = { * @returns {void} */ function checkSpacingBefore(token) { - const prevToken = sourceCode.getTokenBefore(token); + const prevToken = sourceCode.getTokenBefore(token, { includeComments: true }); if (prevToken && CLOSE_PAREN.test(token.value) && @@ -86,7 +86,7 @@ module.exports = { * @returns {void} */ function checkSpacingAfter(token) { - const nextToken = sourceCode.getTokenAfter(token); + const nextToken = sourceCode.getTokenAfter(token, { includeComments: true }); if (nextToken && OPEN_PAREN.test(token.value) && diff --git a/tools/node_modules/eslint/lib/rules/utils/ast-utils.js b/tools/node_modules/eslint/lib/rules/utils/ast-utils.js index 01c6b47b82eefc..e10544dd61e9ca 100644 --- a/tools/node_modules/eslint/lib/rules/utils/ast-utils.js +++ b/tools/node_modules/eslint/lib/rules/utils/ast-utils.js @@ -865,6 +865,47 @@ module.exports = { return isFunction(node) && module.exports.isEmptyBlock(node.body); }, + /** + * Returns the result of the string conversion applied to the evaluated value of the given expression node, + * if it can be determined statically. + * + * This function returns a `string` value for all `Literal` nodes and simple `TemplateLiteral` nodes only. + * In all other cases, this function returns `null`. + * @param {ASTNode} node Expression node. + * @returns {string|null} String value if it can be determined. Otherwise, `null`. + */ + getStaticStringValue(node) { + switch (node.type) { + case "Literal": + if (node.value === null) { + if (module.exports.isNullLiteral(node)) { + return String(node.value); // "null" + } + if (node.regex) { + return `/${node.regex.pattern}/${node.regex.flags}`; + } + if (node.bigint) { + return node.bigint; + } + + // Otherwise, this is an unknown literal. The function will return null. + + } else { + return String(node.value); + } + break; + case "TemplateLiteral": + if (node.expressions.length === 0 && node.quasis.length === 1) { + return node.quasis[0].value.cooked; + } + break; + + // no default + } + + return null; + }, + /** * Gets the property name of a given node. * The node can be a MemberExpression, a Property, or a MethodDefinition. @@ -911,23 +952,12 @@ module.exports = { // no default } - switch (prop && prop.type) { - case "Literal": - return String(prop.value); - - case "TemplateLiteral": - if (prop.expressions.length === 0 && prop.quasis.length === 1) { - return prop.quasis[0].value.cooked; - } - break; - - case "Identifier": - if (!node.computed) { - return prop.name; - } - break; + if (prop) { + if (prop.type === "Identifier" && !node.computed) { + return prop.name; + } - // no default + return module.exports.getStaticStringValue(prop); } return null; @@ -987,6 +1017,7 @@ module.exports = { * 0o5 // false * 5e0 // false * '5' // false + * 5n // false */ isDecimalInteger(node) { return node.type === "Literal" && typeof node.value === "number" && @@ -1306,6 +1337,18 @@ module.exports = { return node.type === "Literal" && node.value === null && !node.regex && !node.bigint; }, + /** + * Check if a given node is a numeric literal or not. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is a number or bigint literal. + */ + isNumericLiteral(node) { + return ( + node.type === "Literal" && + (typeof node.value === "number" || Boolean(node.bigint)) + ); + }, + /** * Determines whether two tokens can safely be placed next to each other without merging into a single token * @param {Token|string} leftValue The left token. If this is a string, it will be tokenized and the last token will be used. diff --git a/tools/node_modules/eslint/lib/rules/utils/unicode/is-combining-character.js b/tools/node_modules/eslint/lib/rules/utils/unicode/is-combining-character.js index 0fa40ee4ae8630..0498b99a21ed45 100644 --- a/tools/node_modules/eslint/lib/rules/utils/unicode/is-combining-character.js +++ b/tools/node_modules/eslint/lib/rules/utils/unicode/is-combining-character.js @@ -1,13 +1,13 @@ -// THIS FILE WAS GENERATED BY 'tools/update-unicode-utils.js' +/** + * @author Toru Nagashima + */ "use strict"; -const combiningChars = new Set([768,769,770,771,772,773,774,775,776,777,778,779,780,781,782,783,784,785,786,787,788,789,790,791,792,793,794,795,796,797,798,799,800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815,816,817,818,819,820,821,822,823,824,825,826,827,828,829,830,831,832,833,834,835,836,837,838,839,840,841,842,843,844,845,846,847,848,849,850,851,852,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867,868,869,870,871,872,873,874,875,876,877,878,879,1155,1156,1157,1158,1159,1160,1161,1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436,1437,1438,1439,1440,1441,1442,1443,1444,1445,1446,1447,1448,1449,1450,1451,1452,1453,1454,1455,1456,1457,1458,1459,1460,1461,1462,1463,1464,1465,1466,1467,1468,1469,1471,1473,1474,1476,1477,1479,1552,1553,1554,1555,1556,1557,1558,1559,1560,1561,1562,1611,1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628,1629,1630,1631,1648,1750,1751,1752,1753,1754,1755,1756,1759,1760,1761,1762,1763,1764,1767,1768,1770,1771,1772,1773,1809,1840,1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858,1859,1860,1861,1862,1863,1864,1865,1866,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,2027,2028,2029,2030,2031,2032,2033,2034,2035,2070,2071,2072,2073,2075,2076,2077,2078,2079,2080,2081,2082,2083,2085,2086,2087,2089,2090,2091,2092,2093,2137,2138,2139,2260,2261,2262,2263,2264,2265,2266,2267,2268,2269,2270,2271,2272,2273,2275,2276,2277,2278,2279,2280,2281,2282,2283,2284,2285,2286,2287,2288,2289,2290,2291,2292,2293,2294,2295,2296,2297,2298,2299,2300,2301,2302,2303,2304,2305,2306,2307,2362,2363,2364,2366,2367,2368,2369,2370,2371,2372,2373,2374,2375,2376,2377,2378,2379,2380,2381,2382,2383,2385,2386,2387,2388,2389,2390,2391,2402,2403,2433,2434,2435,2492,2494,2495,2496,2497,2498,2499,2500,2503,2504,2507,2508,2509,2519,2530,2531,2561,2562,2563,2620,2622,2623,2624,2625,2626,2631,2632,2635,2636,2637,2641,2672,2673,2677,2689,2690,2691,2748,2750,2751,2752,2753,2754,2755,2756,2757,2759,2760,2761,2763,2764,2765,2786,2787,2810,2811,2812,2813,2814,2815,2817,2818,2819,2876,2878,2879,2880,2881,2882,2883,2884,2887,2888,2891,2892,2893,2902,2903,2914,2915,2946,3006,3007,3008,3009,3010,3014,3015,3016,3018,3019,3020,3021,3031,3072,3073,3074,3075,3134,3135,3136,3137,3138,3139,3140,3142,3143,3144,3146,3147,3148,3149,3157,3158,3170,3171,3201,3202,3203,3260,3262,3263,3264,3265,3266,3267,3268,3270,3271,3272,3274,3275,3276,3277,3285,3286,3298,3299,3328,3329,3330,3331,3387,3388,3390,3391,3392,3393,3394,3395,3396,3398,3399,3400,3402,3403,3404,3405,3415,3426,3427,3458,3459,3530,3535,3536,3537,3538,3539,3540,3542,3544,3545,3546,3547,3548,3549,3550,3551,3570,3571,3633,3636,3637,3638,3639,3640,3641,3642,3655,3656,3657,3658,3659,3660,3661,3662,3761,3764,3765,3766,3767,3768,3769,3771,3772,3784,3785,3786,3787,3788,3789,3864,3865,3893,3895,3897,3902,3903,3953,3954,3955,3956,3957,3958,3959,3960,3961,3962,3963,3964,3965,3966,3967,3968,3969,3970,3971,3972,3974,3975,3981,3982,3983,3984,3985,3986,3987,3988,3989,3990,3991,3993,3994,3995,3996,3997,3998,3999,4000,4001,4002,4003,4004,4005,4006,4007,4008,4009,4010,4011,4012,4013,4014,4015,4016,4017,4018,4019,4020,4021,4022,4023,4024,4025,4026,4027,4028,4038,4139,4140,4141,4142,4143,4144,4145,4146,4147,4148,4149,4150,4151,4152,4153,4154,4155,4156,4157,4158,4182,4183,4184,4185,4190,4191,4192,4194,4195,4196,4199,4200,4201,4202,4203,4204,4205,4209,4210,4211,4212,4226,4227,4228,4229,4230,4231,4232,4233,4234,4235,4236,4237,4239,4250,4251,4252,4253,4957,4958,4959,5906,5907,5908,5938,5939,5940,5970,5971,6002,6003,6068,6069,6070,6071,6072,6073,6074,6075,6076,6077,6078,6079,6080,6081,6082,6083,6084,6085,6086,6087,6088,6089,6090,6091,6092,6093,6094,6095,6096,6097,6098,6099,6109,6155,6156,6157,6277,6278,6313,6432,6433,6434,6435,6436,6437,6438,6439,6440,6441,6442,6443,6448,6449,6450,6451,6452,6453,6454,6455,6456,6457,6458,6459,6679,6680,6681,6682,6683,6741,6742,6743,6744,6745,6746,6747,6748,6749,6750,6752,6753,6754,6755,6756,6757,6758,6759,6760,6761,6762,6763,6764,6765,6766,6767,6768,6769,6770,6771,6772,6773,6774,6775,6776,6777,6778,6779,6780,6783,6832,6833,6834,6835,6836,6837,6838,6839,6840,6841,6842,6843,6844,6845,6846,6912,6913,6914,6915,6916,6964,6965,6966,6967,6968,6969,6970,6971,6972,6973,6974,6975,6976,6977,6978,6979,6980,7019,7020,7021,7022,7023,7024,7025,7026,7027,7040,7041,7042,7073,7074,7075,7076,7077,7078,7079,7080,7081,7082,7083,7084,7085,7142,7143,7144,7145,7146,7147,7148,7149,7150,7151,7152,7153,7154,7155,7204,7205,7206,7207,7208,7209,7210,7211,7212,7213,7214,7215,7216,7217,7218,7219,7220,7221,7222,7223,7376,7377,7378,7380,7381,7382,7383,7384,7385,7386,7387,7388,7389,7390,7391,7392,7393,7394,7395,7396,7397,7398,7399,7400,7405,7410,7411,7412,7415,7416,7417,7616,7617,7618,7619,7620,7621,7622,7623,7624,7625,7626,7627,7628,7629,7630,7631,7632,7633,7634,7635,7636,7637,7638,7639,7640,7641,7642,7643,7644,7645,7646,7647,7648,7649,7650,7651,7652,7653,7654,7655,7656,7657,7658,7659,7660,7661,7662,7663,7664,7665,7666,7667,7668,7669,7670,7671,7672,7673,7675,7676,7677,7678,7679,8400,8401,8402,8403,8404,8405,8406,8407,8408,8409,8410,8411,8412,8413,8414,8415,8416,8417,8418,8419,8420,8421,8422,8423,8424,8425,8426,8427,8428,8429,8430,8431,8432,11503,11504,11505,11647,11744,11745,11746,11747,11748,11749,11750,11751,11752,11753,11754,11755,11756,11757,11758,11759,11760,11761,11762,11763,11764,11765,11766,11767,11768,11769,11770,11771,11772,11773,11774,11775,12330,12331,12332,12333,12334,12335,12441,12442,42607,42608,42609,42610,42612,42613,42614,42615,42616,42617,42618,42619,42620,42621,42654,42655,42736,42737,43010,43014,43019,43043,43044,43045,43046,43047,43136,43137,43188,43189,43190,43191,43192,43193,43194,43195,43196,43197,43198,43199,43200,43201,43202,43203,43204,43205,43232,43233,43234,43235,43236,43237,43238,43239,43240,43241,43242,43243,43244,43245,43246,43247,43248,43249,43302,43303,43304,43305,43306,43307,43308,43309,43335,43336,43337,43338,43339,43340,43341,43342,43343,43344,43345,43346,43347,43392,43393,43394,43395,43443,43444,43445,43446,43447,43448,43449,43450,43451,43452,43453,43454,43455,43456,43493,43561,43562,43563,43564,43565,43566,43567,43568,43569,43570,43571,43572,43573,43574,43587,43596,43597,43643,43644,43645,43696,43698,43699,43700,43703,43704,43710,43711,43713,43755,43756,43757,43758,43759,43765,43766,44003,44004,44005,44006,44007,44008,44009,44010,44012,44013,64286,65024,65025,65026,65027,65028,65029,65030,65031,65032,65033,65034,65035,65036,65037,65038,65039,65056,65057,65058,65059,65060,65061,65062,65063,65064,65065,65066,65067,65068,65069,65070,65071,66045,66272,66422,66423,66424,66425,66426,68097,68098,68099,68101,68102,68108,68109,68110,68111,68152,68153,68154,68159,68325,68326,69632,69633,69634,69688,69689,69690,69691,69692,69693,69694,69695,69696,69697,69698,69699,69700,69701,69702,69759,69760,69761,69762,69808,69809,69810,69811,69812,69813,69814,69815,69816,69817,69818,69888,69889,69890,69927,69928,69929,69930,69931,69932,69933,69934,69935,69936,69937,69938,69939,69940,70003,70016,70017,70018,70067,70068,70069,70070,70071,70072,70073,70074,70075,70076,70077,70078,70079,70080,70090,70091,70092,70188,70189,70190,70191,70192,70193,70194,70195,70196,70197,70198,70199,70206,70367,70368,70369,70370,70371,70372,70373,70374,70375,70376,70377,70378,70400,70401,70402,70403,70460,70462,70463,70464,70465,70466,70467,70468,70471,70472,70475,70476,70477,70487,70498,70499,70502,70503,70504,70505,70506,70507,70508,70512,70513,70514,70515,70516,70709,70710,70711,70712,70713,70714,70715,70716,70717,70718,70719,70720,70721,70722,70723,70724,70725,70726,70832,70833,70834,70835,70836,70837,70838,70839,70840,70841,70842,70843,70844,70845,70846,70847,70848,70849,70850,70851,71087,71088,71089,71090,71091,71092,71093,71096,71097,71098,71099,71100,71101,71102,71103,71104,71132,71133,71216,71217,71218,71219,71220,71221,71222,71223,71224,71225,71226,71227,71228,71229,71230,71231,71232,71339,71340,71341,71342,71343,71344,71345,71346,71347,71348,71349,71350,71351,71453,71454,71455,71456,71457,71458,71459,71460,71461,71462,71463,71464,71465,71466,71467,72193,72194,72195,72196,72197,72198,72199,72200,72201,72202,72243,72244,72245,72246,72247,72248,72249,72251,72252,72253,72254,72263,72273,72274,72275,72276,72277,72278,72279,72280,72281,72282,72283,72330,72331,72332,72333,72334,72335,72336,72337,72338,72339,72340,72341,72342,72343,72344,72345,72751,72752,72753,72754,72755,72756,72757,72758,72760,72761,72762,72763,72764,72765,72766,72767,72850,72851,72852,72853,72854,72855,72856,72857,72858,72859,72860,72861,72862,72863,72864,72865,72866,72867,72868,72869,72870,72871,72873,72874,72875,72876,72877,72878,72879,72880,72881,72882,72883,72884,72885,72886,73009,73010,73011,73012,73013,73014,73018,73020,73021,73023,73024,73025,73026,73027,73028,73029,73031,92912,92913,92914,92915,92916,92976,92977,92978,92979,92980,92981,92982,94033,94034,94035,94036,94037,94038,94039,94040,94041,94042,94043,94044,94045,94046,94047,94048,94049,94050,94051,94052,94053,94054,94055,94056,94057,94058,94059,94060,94061,94062,94063,94064,94065,94066,94067,94068,94069,94070,94071,94072,94073,94074,94075,94076,94077,94078,94095,94096,94097,94098,113821,113822,119141,119142,119143,119144,119145,119149,119150,119151,119152,119153,119154,119163,119164,119165,119166,119167,119168,119169,119170,119173,119174,119175,119176,119177,119178,119179,119210,119211,119212,119213,119362,119363,119364,121344,121345,121346,121347,121348,121349,121350,121351,121352,121353,121354,121355,121356,121357,121358,121359,121360,121361,121362,121363,121364,121365,121366,121367,121368,121369,121370,121371,121372,121373,121374,121375,121376,121377,121378,121379,121380,121381,121382,121383,121384,121385,121386,121387,121388,121389,121390,121391,121392,121393,121394,121395,121396,121397,121398,121403,121404,121405,121406,121407,121408,121409,121410,121411,121412,121413,121414,121415,121416,121417,121418,121419,121420,121421,121422,121423,121424,121425,121426,121427,121428,121429,121430,121431,121432,121433,121434,121435,121436,121437,121438,121439,121440,121441,121442,121443,121444,121445,121446,121447,121448,121449,121450,121451,121452,121461,121476,121499,121500,121501,121502,121503,121505,121506,121507,121508,121509,121510,121511,121512,121513,121514,121515,121516,121517,121518,121519,122880,122881,122882,122883,122884,122885,122886,122888,122889,122890,122891,122892,122893,122894,122895,122896,122897,122898,122899,122900,122901,122902,122903,122904,122907,122908,122909,122910,122911,122912,122913,122915,122916,122918,122919,122920,122921,122922,125136,125137,125138,125139,125140,125141,125142,125252,125253,125254,125255,125256,125257,125258,917760,917761,917762,917763,917764,917765,917766,917767,917768,917769,917770,917771,917772,917773,917774,917775,917776,917777,917778,917779,917780,917781,917782,917783,917784,917785,917786,917787,917788,917789,917790,917791,917792,917793,917794,917795,917796,917797,917798,917799,917800,917801,917802,917803,917804,917805,917806,917807,917808,917809,917810,917811,917812,917813,917814,917815,917816,917817,917818,917819,917820,917821,917822,917823,917824,917825,917826,917827,917828,917829,917830,917831,917832,917833,917834,917835,917836,917837,917838,917839,917840,917841,917842,917843,917844,917845,917846,917847,917848,917849,917850,917851,917852,917853,917854,917855,917856,917857,917858,917859,917860,917861,917862,917863,917864,917865,917866,917867,917868,917869,917870,917871,917872,917873,917874,917875,917876,917877,917878,917879,917880,917881,917882,917883,917884,917885,917886,917887,917888,917889,917890,917891,917892,917893,917894,917895,917896,917897,917898,917899,917900,917901,917902,917903,917904,917905,917906,917907,917908,917909,917910,917911,917912,917913,917914,917915,917916,917917,917918,917919,917920,917921,917922,917923,917924,917925,917926,917927,917928,917929,917930,917931,917932,917933,917934,917935,917936,917937,917938,917939,917940,917941,917942,917943,917944,917945,917946,917947,917948,917949,917950,917951,917952,917953,917954,917955,917956,917957,917958,917959,917960,917961,917962,917963,917964,917965,917966,917967,917968,917969,917970,917971,917972,917973,917974,917975,917976,917977,917978,917979,917980,917981,917982,917983,917984,917985,917986,917987,917988,917989,917990,917991,917992,917993,917994,917995,917996,917997,917998,917999]) - /** * Check whether a given character is a combining mark or not. - * @param {number} c The character code to check. - * @returns {boolean} `true` if the character belongs to the category, one of `Mc`, `Me`, and `Mn`. + * @param {number} codePoint The character code to check. + * @returns {boolean} `true` if the character belongs to the category, any of `Mc`, `Me`, and `Mn`. */ -module.exports = function isCombiningCharacter(c) { - return combiningChars.has(c); +module.exports = function isCombiningCharacter(codePoint) { + return /^[\p{Mc}\p{Me}\p{Mn}]$/u.test(String.fromCodePoint(codePoint)); }; diff --git a/tools/node_modules/eslint/lib/rules/yoda.js b/tools/node_modules/eslint/lib/rules/yoda.js index b00acf82c702b0..be5c59ce072269 100644 --- a/tools/node_modules/eslint/lib/rules/yoda.js +++ b/tools/node_modules/eslint/lib/rules/yoda.js @@ -53,8 +53,7 @@ function looksLikeLiteral(node) { return (node.type === "UnaryExpression" && node.operator === "-" && node.prefix && - node.argument.type === "Literal" && - typeof node.argument.value === "number"); + astUtils.isNumericLiteral(node.argument)); } /** diff --git a/tools/node_modules/eslint/lib/shared/config-validator.js b/tools/node_modules/eslint/lib/shared/config-validator.js index aca6e1fb274fdb..70eaf0a9670514 100644 --- a/tools/node_modules/eslint/lib/shared/config-validator.js +++ b/tools/node_modules/eslint/lib/shared/config-validator.js @@ -10,13 +10,12 @@ //------------------------------------------------------------------------------ const - path = require("path"), util = require("util"), - lodash = require("lodash"), configSchema = require("../../conf/config-schema"), BuiltInEnvironments = require("../../conf/environments"), BuiltInRules = require("../rules"), - ConfigOps = require("./config-ops"); + ConfigOps = require("./config-ops"), + { emitDeprecationWarning } = require("./deprecation-warnings"); const ajv = require("./ajv")(); const ruleValidators = new WeakMap(); @@ -26,11 +25,6 @@ const noop = Function.prototype; // Private //------------------------------------------------------------------------------ let validateSchema; - -// Defitions for deprecation warnings. -const deprecationWarningMessages = { - ESLINT_LEGACY_ECMAFEATURES: "The 'ecmaFeatures' config file property is deprecated, and has no effect." -}; const severityMap = { error: 2, warn: 1, @@ -254,25 +248,6 @@ function formatErrors(errors) { }).map(message => `\t- ${message}.\n`).join(""); } -/** - * Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted - * for each unique file path, but repeated invocations with the same file path have no effect. - * No warnings are emitted if the `--no-deprecation` or `--no-warnings` Node runtime flags are active. - * @param {string} source The name of the configuration source to report the warning for. - * @param {string} errorCode The warning message to show. - * @returns {void} - */ -const emitDeprecationWarning = lodash.memoize((source, errorCode) => { - const rel = path.relative(process.cwd(), source); - const message = deprecationWarningMessages[errorCode]; - - process.emitWarning( - `${message} (found in "${rel}")`, - "DeprecationWarning", - errorCode - ); -}); - /** * Validates the top level properties of the config object. * @param {Object} config The config object to validate. diff --git a/tools/node_modules/eslint/lib/shared/deprecation-warnings.js b/tools/node_modules/eslint/lib/shared/deprecation-warnings.js new file mode 100644 index 00000000000000..e1481a2e9aa0b8 --- /dev/null +++ b/tools/node_modules/eslint/lib/shared/deprecation-warnings.js @@ -0,0 +1,56 @@ +/** + * @fileoverview Provide the function that emits deprecation warnings. + * @author Toru Nagashima + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const path = require("path"); +const lodash = require("lodash"); + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +// Defitions for deprecation warnings. +const deprecationWarningMessages = { + ESLINT_LEGACY_ECMAFEATURES: + "The 'ecmaFeatures' config file property is deprecated and has no effect.", + ESLINT_PERSONAL_CONFIG_LOAD: + "'~/.eslintrc.*' config files have been deprecated. " + + "Please use a config file per project or the '--config' option.", + ESLINT_PERSONAL_CONFIG_SUPPRESS: + "'~/.eslintrc.*' config files have been deprecated. " + + "Please remove it or add 'root:true' to the config files in your " + + "projects in order to avoid loading '~/.eslintrc.*' accidentally." +}; + +/** + * Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted + * for each unique file path, but repeated invocations with the same file path have no effect. + * No warnings are emitted if the `--no-deprecation` or `--no-warnings` Node runtime flags are active. + * @param {string} source The name of the configuration source to report the warning for. + * @param {string} errorCode The warning message to show. + * @returns {void} + */ +const emitDeprecationWarning = lodash.memoize((source, errorCode) => { + const rel = path.relative(process.cwd(), source); + const message = deprecationWarningMessages[errorCode]; + + process.emitWarning( + `${message} (found in "${rel}")`, + "DeprecationWarning", + errorCode + ); +}, (...args) => JSON.stringify(args)); + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + emitDeprecationWarning +}; diff --git a/tools/node_modules/eslint/lib/shared/relative-module-resolver.js b/tools/node_modules/eslint/lib/shared/relative-module-resolver.js index 5b25fa111214ed..fa6cca72361df5 100644 --- a/tools/node_modules/eslint/lib/shared/relative-module-resolver.js +++ b/tools/node_modules/eslint/lib/shared/relative-module-resolver.js @@ -6,35 +6,18 @@ "use strict"; const Module = require("module"); -const path = require("path"); -// Polyfill Node's `Module.createRequire` if not present. We only support the case where the argument is a filepath, not a URL. -const createRequire = ( - - // Added in v12.2.0 - Module.createRequire || - - // Added in v10.12.0, but deprecated in v12.2.0. - Module.createRequireFromPath || - - // Polyfill - This is not executed on the tests on node@>=10. - /* istanbul ignore next */ - (filename => { - const mod = new Module(filename, null); - - mod.filename = filename; - mod.paths = Module._nodeModulePaths(path.dirname(filename)); // eslint-disable-line no-underscore-dangle - mod._compile("module.exports = require;", filename); // eslint-disable-line no-underscore-dangle - return mod.exports; - }) -); +/* + * `Module.createRequire` is added in v12.2.0. It supports URL as well. + * We only support the case where the argument is a filepath, not a URL. + */ +const createRequire = Module.createRequire || Module.createRequireFromPath; module.exports = { /** * Resolves a Node module relative to another module * @param {string} moduleName The name of a Node module, or a path to a Node module. - * * @param {string} relativeToPath An absolute path indicating the module that `moduleName` should be resolved relative to. This must be * a file rather than a directory, but the file need not actually exist. * @returns {string} The absolute path that would result from calling `require.resolve(moduleName)` in a file located at `relativeToPath` @@ -43,6 +26,8 @@ module.exports = { try { return createRequire(relativeToPath).resolve(moduleName); } catch (error) { + + // This `if` block is for older Node.js than 12.0.0. We can remove this block in the future. if ( typeof error === "object" && error !== null && diff --git a/tools/node_modules/eslint/node_modules/@babel/code-frame/lib/index.js b/tools/node_modules/eslint/node_modules/@babel/code-frame/lib/index.js index 35176fbc0682ca..62945f7782a774 100644 --- a/tools/node_modules/eslint/node_modules/@babel/code-frame/lib/index.js +++ b/tools/node_modules/eslint/node_modules/@babel/code-frame/lib/index.js @@ -6,17 +6,11 @@ Object.defineProperty(exports, "__esModule", { exports.codeFrameColumns = codeFrameColumns; exports.default = _default; -function _highlight() { - const data = _interopRequireWildcard(require("@babel/highlight")); +var _highlight = _interopRequireWildcard(require("@babel/highlight")); - _highlight = function () { - return data; - }; - - return data; -} +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } let deprecationWarningShown = false; @@ -35,7 +29,7 @@ function getMarkerLines(loc, source, opts) { column: 0, line: -1 }, loc.start); - const endLoc = Object.assign({}, startLoc, loc.end); + const endLoc = Object.assign({}, startLoc, {}, loc.end); const { linesAbove = 2, linesBelow = 3 @@ -94,8 +88,8 @@ function getMarkerLines(loc, source, opts) { } function codeFrameColumns(rawLines, loc, opts = {}) { - const highlighted = (opts.highlightCode || opts.forceColor) && (0, _highlight().shouldHighlight)(opts); - const chalk = (0, _highlight().getChalk)(opts); + const highlighted = (opts.highlightCode || opts.forceColor) && (0, _highlight.shouldHighlight)(opts); + const chalk = (0, _highlight.getChalk)(opts); const defs = getDefs(chalk); const maybeHighlight = (chalkFn, string) => { @@ -110,7 +104,7 @@ function codeFrameColumns(rawLines, loc, opts = {}) { } = getMarkerLines(loc, lines, opts); const hasColumns = loc.start && typeof loc.start.column === "number"; const numberMaxWidth = String(end).length; - const highlightedLines = highlighted ? (0, _highlight().default)(rawLines, opts) : rawLines; + const highlightedLines = highlighted ? (0, _highlight.default)(rawLines, opts) : rawLines; let frame = highlightedLines.split(NEWLINE).slice(start, end).map((line, index) => { const number = start + 1 + index; const paddedNumber = ` ${number}`.slice(-numberMaxWidth); diff --git a/tools/node_modules/eslint/node_modules/@babel/code-frame/package.json b/tools/node_modules/eslint/node_modules/@babel/code-frame/package.json index d619d9a8f53194..7508b5b1402c94 100644 --- a/tools/node_modules/eslint/node_modules/@babel/code-frame/package.json +++ b/tools/node_modules/eslint/node_modules/@babel/code-frame/package.json @@ -5,7 +5,7 @@ }, "bundleDependencies": false, "dependencies": { - "@babel/highlight": "^7.0.0" + "@babel/highlight": "^7.8.3" }, "deprecated": false, "description": "Generate errors that contain a code frame that point to source locations.", @@ -13,7 +13,7 @@ "chalk": "^2.0.0", "strip-ansi": "^4.0.0" }, - "gitHead": "0407f034f09381b95e9cabefbf6b176c76485a43", + "gitHead": "a7620bd266ae1345975767bbc7abf09034437017", "homepage": "https://babeljs.io/", "license": "MIT", "main": "lib/index.js", @@ -25,5 +25,5 @@ "type": "git", "url": "https://github.com/babel/babel/tree/master/packages/babel-code-frame" }, - "version": "7.5.5" + "version": "7.8.3" } \ No newline at end of file diff --git a/tools/node_modules/eslint/node_modules/@babel/highlight/lib/index.js b/tools/node_modules/eslint/node_modules/@babel/highlight/lib/index.js index 6ac5b4a350b8e6..bf275748784b50 100644 --- a/tools/node_modules/eslint/node_modules/@babel/highlight/lib/index.js +++ b/tools/node_modules/eslint/node_modules/@babel/highlight/lib/index.js @@ -7,39 +7,17 @@ exports.shouldHighlight = shouldHighlight; exports.getChalk = getChalk; exports.default = highlight; -function _jsTokens() { - const data = _interopRequireWildcard(require("js-tokens")); +var _jsTokens = _interopRequireWildcard(require("js-tokens")); - _jsTokens = function () { - return data; - }; - - return data; -} - -function _esutils() { - const data = _interopRequireDefault(require("esutils")); +var _esutils = _interopRequireDefault(require("esutils")); - _esutils = function () { - return data; - }; - - return data; -} - -function _chalk() { - const data = _interopRequireDefault(require("chalk")); - - _chalk = function () { - return data; - }; - - return data; -} +var _chalk = _interopRequireDefault(require("chalk")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function getDefs(chalk) { return { @@ -61,10 +39,10 @@ const BRACKET = /^[()[\]{}]$/; function getTokenType(match) { const [offset, text] = match.slice(-2); - const token = (0, _jsTokens().matchToToken)(match); + const token = (0, _jsTokens.matchToToken)(match); if (token.type === "name") { - if (_esutils().default.keyword.isReservedWordES6(token.value)) { + if (_esutils.default.keyword.isReservedWordES6(token.value)) { return "keyword"; } @@ -89,7 +67,7 @@ function getTokenType(match) { } function highlightTokens(defs, text) { - return text.replace(_jsTokens().default, function (...args) { + return text.replace(_jsTokens.default, function (...args) { const type = getTokenType(args); const colorize = defs[type]; @@ -102,14 +80,14 @@ function highlightTokens(defs, text) { } function shouldHighlight(options) { - return _chalk().default.supportsColor || options.forceColor; + return _chalk.default.supportsColor || options.forceColor; } function getChalk(options) { - let chalk = _chalk().default; + let chalk = _chalk.default; if (options.forceColor) { - chalk = new (_chalk().default.constructor)({ + chalk = new _chalk.default.constructor({ enabled: true, level: 1 }); diff --git a/tools/node_modules/eslint/node_modules/chalk/index.js b/tools/node_modules/eslint/node_modules/@babel/highlight/node_modules/chalk/index.js similarity index 100% rename from tools/node_modules/eslint/node_modules/chalk/index.js rename to tools/node_modules/eslint/node_modules/@babel/highlight/node_modules/chalk/index.js diff --git a/tools/node_modules/eslint/node_modules/chalk/index.js.flow b/tools/node_modules/eslint/node_modules/@babel/highlight/node_modules/chalk/index.js.flow similarity index 100% rename from tools/node_modules/eslint/node_modules/chalk/index.js.flow rename to tools/node_modules/eslint/node_modules/@babel/highlight/node_modules/chalk/index.js.flow diff --git a/tools/node_modules/eslint/node_modules/string-width/node_modules/strip-ansi/license b/tools/node_modules/eslint/node_modules/@babel/highlight/node_modules/chalk/license similarity index 100% rename from tools/node_modules/eslint/node_modules/string-width/node_modules/strip-ansi/license rename to tools/node_modules/eslint/node_modules/@babel/highlight/node_modules/chalk/license diff --git a/tools/node_modules/eslint/node_modules/@babel/highlight/node_modules/chalk/package.json b/tools/node_modules/eslint/node_modules/@babel/highlight/node_modules/chalk/package.json new file mode 100644 index 00000000000000..270fecdc347d42 --- /dev/null +++ b/tools/node_modules/eslint/node_modules/@babel/highlight/node_modules/chalk/package.json @@ -0,0 +1,80 @@ +{ + "bugs": { + "url": "https://github.com/chalk/chalk/issues" + }, + "bundleDependencies": false, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "deprecated": false, + "description": "Terminal string styling done right", + "devDependencies": { + "ava": "*", + "coveralls": "^3.0.0", + "execa": "^0.9.0", + "flow-bin": "^0.68.0", + "import-fresh": "^2.0.0", + "matcha": "^0.7.0", + "nyc": "^11.0.2", + "resolve-from": "^4.0.0", + "typescript": "^2.5.3", + "xo": "*" + }, + "engines": { + "node": ">=4" + }, + "files": [ + "index.js", + "templates.js", + "types/index.d.ts", + "index.js.flow" + ], + "homepage": "https://github.com/chalk/chalk#readme", + "keywords": [ + "color", + "colour", + "colors", + "terminal", + "console", + "cli", + "string", + "str", + "ansi", + "style", + "styles", + "tty", + "formatting", + "rgb", + "256", + "shell", + "xterm", + "log", + "logging", + "command-line", + "text" + ], + "license": "MIT", + "name": "chalk", + "repository": { + "type": "git", + "url": "git+https://github.com/chalk/chalk.git" + }, + "scripts": { + "bench": "matcha benchmark.js", + "coveralls": "nyc report --reporter=text-lcov | coveralls", + "test": "xo && tsc --project types && flow --max-warnings=0 && nyc ava" + }, + "types": "types/index.d.ts", + "version": "2.4.2", + "xo": { + "envs": [ + "node", + "mocha" + ], + "ignores": [ + "test/_flow.js" + ] + } +} \ No newline at end of file diff --git a/tools/node_modules/eslint/node_modules/@babel/highlight/node_modules/chalk/readme.md b/tools/node_modules/eslint/node_modules/@babel/highlight/node_modules/chalk/readme.md new file mode 100644 index 00000000000000..d298e2c48d64a0 --- /dev/null +++ b/tools/node_modules/eslint/node_modules/@babel/highlight/node_modules/chalk/readme.md @@ -0,0 +1,314 @@ +

+
+
+ Chalk +
+
+
+

+ +> Terminal string styling done right + +[![Build Status](https://travis-ci.org/chalk/chalk.svg?branch=master)](https://travis-ci.org/chalk/chalk) [![Coverage Status](https://coveralls.io/repos/github/chalk/chalk/badge.svg?branch=master)](https://coveralls.io/github/chalk/chalk?branch=master) [![](https://img.shields.io/badge/unicorn-approved-ff69b4.svg)](https://www.youtube.com/watch?v=9auOCbH5Ns4) [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/xojs/xo) [![Mentioned in Awesome Node.js](https://awesome.re/mentioned-badge.svg)](https://github.com/sindresorhus/awesome-nodejs) + +### [See what's new in Chalk 2](https://github.com/chalk/chalk/releases/tag/v2.0.0) + + + + +## Highlights + +- Expressive API +- Highly performant +- Ability to nest styles +- [256/Truecolor color support](#256-and-truecolor-color-support) +- Auto-detects color support +- Doesn't extend `String.prototype` +- Clean and focused +- Actively maintained +- [Used by ~23,000 packages](https://www.npmjs.com/browse/depended/chalk) as of December 31, 2017 + + +## Install + +```console +$ npm install chalk +``` + + + + + + +## Usage + +```js +const chalk = require('chalk'); + +console.log(chalk.blue('Hello world!')); +``` + +Chalk comes with an easy to use composable API where you just chain and nest the styles you want. + +```js +const chalk = require('chalk'); +const log = console.log; + +// Combine styled and normal strings +log(chalk.blue('Hello') + ' World' + chalk.red('!')); + +// Compose multiple styles using the chainable API +log(chalk.blue.bgRed.bold('Hello world!')); + +// Pass in multiple arguments +log(chalk.blue('Hello', 'World!', 'Foo', 'bar', 'biz', 'baz')); + +// Nest styles +log(chalk.red('Hello', chalk.underline.bgBlue('world') + '!')); + +// Nest styles of the same type even (color, underline, background) +log(chalk.green( + 'I am a green line ' + + chalk.blue.underline.bold('with a blue substring') + + ' that becomes green again!' +)); + +// ES2015 template literal +log(` +CPU: ${chalk.red('90%')} +RAM: ${chalk.green('40%')} +DISK: ${chalk.yellow('70%')} +`); + +// ES2015 tagged template literal +log(chalk` +CPU: {red ${cpu.totalPercent}%} +RAM: {green ${ram.used / ram.total * 100}%} +DISK: {rgb(255,131,0) ${disk.used / disk.total * 100}%} +`); + +// Use RGB colors in terminal emulators that support it. +log(chalk.keyword('orange')('Yay for orange colored text!')); +log(chalk.rgb(123, 45, 67).underline('Underlined reddish color')); +log(chalk.hex('#DEADED').bold('Bold gray!')); +``` + +Easily define your own themes: + +```js +const chalk = require('chalk'); + +const error = chalk.bold.red; +const warning = chalk.keyword('orange'); + +console.log(error('Error!')); +console.log(warning('Warning!')); +``` + +Take advantage of console.log [string substitution](https://nodejs.org/docs/latest/api/console.html#console_console_log_data_args): + +```js +const name = 'Sindre'; +console.log(chalk.green('Hello %s'), name); +//=> 'Hello Sindre' +``` + + +## API + +### chalk.`