From 717f9c590568e5e9c64841aedc40bfbe2b728699 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Thu, 19 Mar 2020 01:49:49 +0200 Subject: [PATCH 001/190] module: path-only CJS exports extension searching Backport-PR-URL: https://github.com/nodejs/node/pull/32883 PR-URL: https://github.com/nodejs/node/pull/32351 Reviewed-By: Geoffrey Booth Reviewed-By: Bradley Farias Reviewed-By: Jan Krems Reviewed-By: Myles Borins --- doc/api/esm.md | 2 +- doc/api/modules.md | 70 ++++----- lib/internal/modules/cjs/loader.js | 133 ++++++++---------- test/es-module/test-esm-exports.mjs | 38 +++-- .../node_modules/pkgexports/package.json | 4 +- .../pkgexports/subpath/dir1/dir1.js | 1 + .../pkgexports/subpath/dir1/package.json | 3 + .../pkgexports/subpath/dir2/index.js | 1 + .../node_modules/pkgexports/subpath/file.js | 2 + 9 files changed, 125 insertions(+), 129 deletions(-) create mode 100644 test/fixtures/node_modules/pkgexports/subpath/dir1/dir1.js create mode 100644 test/fixtures/node_modules/pkgexports/subpath/dir1/package.json create mode 100644 test/fixtures/node_modules/pkgexports/subpath/dir2/index.js create mode 100644 test/fixtures/node_modules/pkgexports/subpath/file.js diff --git a/doc/api/esm.md b/doc/api/esm.md index 3b2d859b190ea6..da2f5184ed9d4d 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -1645,7 +1645,7 @@ The resolver can throw the following errors: > 1. If _exports_ contains any index property keys, as defined in ECMA-262 > [6.1.7 Array Index][], throw an _Invalid Package Configuration_ error. > 1. For each property _p_ of _target_, in object insertion order as, -> 1. If _env_ contains an entry for _p_, then +> 1. If _p_ equals _"default"_ or _env_ contains an entry for _p_, then > 1. Let _targetValue_ be the value of the _p_ property in _target_. > 1. Return the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**( > _packageURL_, _targetValue_, _subpath_, _env_), continuing the diff --git a/doc/api/modules.md b/doc/api/modules.md index 639516467297c3..10a532e335ccab 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -165,7 +165,7 @@ require(X) from module at path Y 6. THROW "not found" LOAD_AS_FILE(X) -1. If X is a file, load X as JavaScript text. STOP +1. If X is a file, load X as its file extension format. STOP 2. If X.js is a file, load X.js as JavaScript text. STOP 3. If X.json is a file, parse X.json to a JavaScript Object. STOP 4. If X.node is a file, load X.node as binary addon. STOP @@ -189,8 +189,9 @@ LOAD_AS_DIRECTORY(X) LOAD_NODE_MODULES(X, START) 1. let DIRS = NODE_MODULES_PATHS(START) 2. for each DIR in DIRS: - a. LOAD_AS_FILE(DIR/X) - b. LOAD_AS_DIRECTORY(DIR/X) + a. LOAD_PACKAGE_EXPORTS(DIR, X) + b. LOAD_AS_FILE(DIR/X) + c. LOAD_AS_DIRECTORY(DIR/X) NODE_MODULES_PATHS(START) 1. let PARTS = path split(START) @@ -208,50 +209,35 @@ LOAD_SELF_REFERENCE(X, START) 2. If no scope was found, return. 3. If the `package.json` has no "exports", return. 4. If the name in `package.json` isn't a prefix of X, throw "not found". -5. Otherwise, resolve the remainder of X relative to this package as if it - was loaded via `LOAD_NODE_MODULES` with a name in `package.json`. -``` - -Node.js allows packages loaded via -`LOAD_NODE_MODULES` to explicitly declare which file paths to expose and how -they should be interpreted. This expands on the control packages already had -using the `main` field. - -With this feature enabled, the `LOAD_NODE_MODULES` changes are: +5. Otherwise, load the remainder of X relative to this package as if it + was loaded via `LOAD_NODE_MODULES` with a name in `package.json`. -```txt -LOAD_NODE_MODULES(X, START) -1. let DIRS = NODE_MODULES_PATHS(START) -2. for each DIR in DIRS: - a. let FILE_PATH = RESOLVE_BARE_SPECIFIER(DIR, X) - b. LOAD_AS_FILE(FILE_PATH) - c. LOAD_AS_DIRECTORY(FILE_PATH) - -RESOLVE_BARE_SPECIFIER(DIR, X) +LOAD_PACKAGE_EXPORTS(DIR, X) 1. Try to interpret X as a combination of name and subpath where the name may have a @scope/ prefix and the subpath begins with a slash (`/`). -2. If X matches this pattern and DIR/name/package.json is a file: - a. Parse DIR/name/package.json, and look for "exports" field. - b. If "exports" is null or undefined, GOTO 3. - c. If "exports" is an object with some keys starting with "." and some keys - not starting with ".", throw "invalid config". - d. If "exports" is a string, or object with no keys starting with ".", treat - it as having that value as its "." object property. - e. If subpath is "." and "exports" does not have a "." entry, GOTO 3. - f. Find the longest key in "exports" that the subpath starts with. - g. If no such key can be found, throw "not found". - h. let RESOLVED_URL = - PACKAGE_EXPORTS_TARGET_RESOLVE(pathToFileURL(DIR/name), exports[key], - subpath.slice(key.length), ["node", "require"]), as defined in the ESM - resolver. - i. return fileURLToPath(RESOLVED_URL) -3. return DIR/X +2. If X does not match this pattern or DIR/name/package.json is not a file, + return. +3. Parse DIR/name/package.json, and look for "exports" field. +4. If "exports" is null or undefined, return. +5. If "exports" is an object with some keys starting with "." and some keys + not starting with ".", throw "invalid config". +6. If "exports" is a string, or object with no keys starting with ".", treat + it as having that value as its "." object property. +7. If subpath is "." and "exports" does not have a "." entry, return. +8. Find the longest key in "exports" that the subpath starts with. +9. If no such key can be found, throw "not found". +10. let RESOLVED = + fileURLToPath(PACKAGE_EXPORTS_TARGET_RESOLVE(pathToFileURL(DIR/name), + exports[key], subpath.slice(key.length), ["node", "require"])), as defined + in the ESM resolver. +11. If key ends with "/": + a. LOAD_AS_FILE(RESOLVED) + b. LOAD_AS_DIRECTORY(RESOLVED) +12. Otherwise + a. If RESOLVED is a file, load it as its file extension format. STOP +13. Throw "not found" ``` -`"exports"` is only honored when loading a package "name" as defined above. Any -`"exports"` values within nested directories and packages must be declared by -the `package.json` responsible for the "name". - ## Caching diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 734dffa343a522..f6f550d7335a60 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -34,6 +34,7 @@ const { ObjectKeys, ObjectPrototypeHasOwnProperty, ReflectSet, + RegExpPrototypeTest, SafeMap, String, StringPrototypeIndexOf, @@ -122,7 +123,10 @@ function enrichCJSError(err) { after a comment block and/or after a variable definition. */ if (err.message.startsWith('Unexpected token \'export\'') || - (/^\s*import(?=[ {'"*])\s*(?![ (])/).test(lineWithErr)) { + (RegExpPrototypeTest(/^\s*import(?=[ {'"*])\s*(?![ (])/, lineWithErr))) { + // Emit the warning synchronously because we are in the middle of handling + // a SyntaxError that will throw and likely terminate the process before an + // asynchronous warning would be emitted. process.emitWarning( 'To load an ES module, set "type": "module" in the package.json or use ' + 'the .mjs extension.', @@ -349,10 +353,11 @@ const realpathCache = new Map(); // absolute realpath. function tryFile(requestPath, isMain) { const rc = stat(requestPath); + if (rc !== 0) return; if (preserveSymlinks && !isMain) { - return rc === 0 && path.resolve(requestPath); + return path.resolve(requestPath); } - return rc === 0 && toRealPath(requestPath); + return toRealPath(requestPath); } function toRealPath(requestPath) { @@ -389,52 +394,7 @@ function findLongestRegisteredExtension(filename) { return '.js'; } -function resolveBasePath(basePath, exts, isMain, trailingSlash, request) { - let filename; - - const rc = stat(basePath); - if (!trailingSlash) { - if (rc === 0) { // File. - if (!isMain) { - if (preserveSymlinks) { - filename = path.resolve(basePath); - } else { - filename = toRealPath(basePath); - } - } else if (preserveSymlinksMain) { - // For the main module, we use the preserveSymlinksMain flag instead - // mainly for backward compatibility, as the preserveSymlinks flag - // historically has not applied to the main module. Most likely this - // was intended to keep .bin/ binaries working, as following those - // symlinks is usually required for the imports in the corresponding - // files to resolve; that said, in some use cases following symlinks - // causes bigger problems which is why the preserveSymlinksMain option - // is needed. - filename = path.resolve(basePath); - } else { - filename = toRealPath(basePath); - } - } - - if (!filename) { - // Try it with each of the extensions - if (exts === undefined) - exts = ObjectKeys(Module._extensions); - filename = tryExtensions(basePath, exts, isMain); - } - } - - if (!filename && rc === 1) { // Directory. - // try it with each of the extensions at "index" - if (exts === undefined) - exts = ObjectKeys(Module._extensions); - filename = tryPackage(basePath, exts, isMain, request); - } - - return filename; -} - -function trySelf(parentPath, isMain, request) { +function trySelf(parentPath, request) { if (!experimentalModules) return false; const { data: pkg, path: basePath } = readPackageScope(parentPath) || {}; if (!pkg || pkg.exports === undefined) return false; @@ -449,20 +409,11 @@ function trySelf(parentPath, isMain, request) { return false; } - const exts = ObjectKeys(Module._extensions); const fromExports = applyExports(basePath, expansion); - // Use exports if (fromExports) { - let trailingSlash = request.length > 0 && - request.charCodeAt(request.length - 1) === CHAR_FORWARD_SLASH; - if (!trailingSlash) { - trailingSlash = /(?:^|\/)\.?\.$/.test(request); - } - return resolveBasePath(fromExports, exts, isMain, trailingSlash, request); - } else { - // Use main field - return tryPackage(basePath, exts, isMain, request); + return tryFile(fromExports, false); } + assert(fromExports !== false); } function isConditionalDotExportSugar(exports, basePath) { @@ -494,7 +445,7 @@ function applyExports(basePath, expansion) { let pkgExports = readPackageExports(basePath); if (pkgExports === undefined || pkgExports === null || !experimentalModules) - return path.resolve(basePath, mappingKey); + return false; if (isConditionalDotExportSugar(pkgExports, basePath)) pkgExports = { '.': pkgExports }; @@ -518,8 +469,24 @@ function applyExports(basePath, expansion) { if (dirMatch !== '') { const mapping = pkgExports[dirMatch]; const subpath = StringPrototypeSlice(mappingKey, dirMatch.length); - return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping, - subpath, mappingKey); + const resolved = resolveExportsTarget(pathToFileURL(basePath + '/'), + mapping, subpath, mappingKey); + // Extension searching for folder exports only + const rc = stat(resolved); + if (rc === 0) return resolved; + if (!(RegExpPrototypeTest(trailingSlashRegex, resolved))) { + const exts = ObjectKeys(Module._extensions); + const filename = tryExtensions(resolved, exts, false); + if (filename) return filename; + } + if (rc === 1) { + const exts = ObjectKeys(Module._extensions); + const filename = tryPackage(resolved, exts, false, + basePath + expansion); + if (filename) return filename; + } + // Undefined means not found + return; } } @@ -530,20 +497,20 @@ function applyExports(basePath, expansion) { // 1. name/.* // 2. @scope/name/.* const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/; -function resolveExports(nmPath, request, absoluteRequest) { +function resolveExports(nmPath, request) { // The implementation's behavior is meant to mirror resolution in ESM. - if (!absoluteRequest) { - const [, name, expansion = ''] = - StringPrototypeMatch(request, EXPORTS_PATTERN) || []; - if (!name) { - return path.resolve(nmPath, request); - } - - const basePath = path.resolve(nmPath, name); - return applyExports(basePath, expansion); + const [, name, expansion = ''] = + StringPrototypeMatch(request, EXPORTS_PATTERN) || []; + if (!name) { + return false; } - return path.resolve(nmPath, request); + const basePath = path.resolve(nmPath, name); + const fromExports = applyExports(basePath, expansion); + if (fromExports) { + return tryFile(fromExports, false); + } + return fromExports; } function isArrayIndex(p) { @@ -635,6 +602,7 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) { StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey, subpath, target); } +const trailingSlashRegex = /(?:^|\/)\.?\.$/; Module._findPath = function(request, paths, isMain) { const absoluteRequest = path.isAbsolute(request); if (absoluteRequest) { @@ -653,7 +621,7 @@ Module._findPath = function(request, paths, isMain) { let trailingSlash = request.length > 0 && request.charCodeAt(request.length - 1) === CHAR_FORWARD_SLASH; if (!trailingSlash) { - trailingSlash = /(?:^|\/)\.?\.$/.test(request); + trailingSlash = RegExpPrototypeTest(trailingSlashRegex, request); } // For each path @@ -661,7 +629,18 @@ Module._findPath = function(request, paths, isMain) { // Don't search further if path doesn't exist const curPath = paths[i]; if (curPath && stat(curPath) < 1) continue; - const basePath = resolveExports(curPath, request, absoluteRequest); + + if (!absoluteRequest) { + const exportsResolved = resolveExports(curPath, request); + // Undefined means not found, false means no exports + if (exportsResolved === undefined) + break; + if (exportsResolved) { + return exportsResolved; + } + } + + const basePath = path.resolve(curPath, request); let filename; const rc = stat(basePath); @@ -953,7 +932,7 @@ Module._resolveFilename = function(request, parent, isMain, options) { } if (parent && parent.filename) { - const filename = trySelf(parent.filename, isMain, request); + const filename = trySelf(parent.filename, request); if (filename) { emitExperimentalWarning('Package name self resolution'); const cacheKey = request + '\x00' + diff --git a/test/es-module/test-esm-exports.mjs b/test/es-module/test-esm-exports.mjs index cc576e083a236d..8aaa66aae47d19 100644 --- a/test/es-module/test-esm-exports.mjs +++ b/test/es-module/test-esm-exports.mjs @@ -36,6 +36,14 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; ['pkgexports-sugar', { default: 'main' }], ]); + if (isRequire) { + validSpecifiers.set('pkgexports/subpath/file', { default: 'file' }); + validSpecifiers.set('pkgexports/subpath/dir1', { default: 'main' }); + validSpecifiers.set('pkgexports/subpath/dir1/', { default: 'main' }); + validSpecifiers.set('pkgexports/subpath/dir2', { default: 'index' }); + validSpecifiers.set('pkgexports/subpath/dir2/', { default: 'index' }); + } + for (const [validSpecifier, expected] of validSpecifiers) { if (validSpecifier === null) continue; @@ -119,14 +127,28 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js'; })); } - // Covering out bases - not a file is still not a file after dir mapping. - loadFixture('pkgexports/sub/not-a-file.js').catch(mustCall((err) => { - strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND'); - // ESM returns a full file path - assertStartsWith(err.message, isRequire ? - 'Cannot find module \'pkgexports/sub/not-a-file.js\'' : - 'Cannot find module'); - })); + const notFoundExports = new Map([ + // Non-existing file + ['pkgexports/sub/not-a-file.js', 'pkgexports/sub/not-a-file.js'], + // No extension lookups + ['pkgexports/no-ext', 'pkgexports/no-ext'], + ]); + + if (!isRequire) { + notFoundExports.set('pkgexports/subpath/file', 'pkgexports/subpath/file'); + notFoundExports.set('pkgexports/subpath/dir1', 'pkgexports/subpath/dir1'); + notFoundExports.set('pkgexports/subpath/dir2', 'pkgexports/subpath/dir2'); + } + + for (const [specifier, request] of notFoundExports) { + loadFixture(specifier).catch(mustCall((err) => { + strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND'); + // ESM returns a full file path + assertStartsWith(err.message, isRequire ? + `Cannot find module '${request}'` : + 'Cannot find module'); + })); + } // The use of %2F escapes in paths fails loading loadFixture('pkgexports/sub/..%2F..%2Fbar.js').catch(mustCall((err) => { diff --git a/test/fixtures/node_modules/pkgexports/package.json b/test/fixtures/node_modules/pkgexports/package.json index 7f417ad5457bfc..200c028f88c9d3 100644 --- a/test/fixtures/node_modules/pkgexports/package.json +++ b/test/fixtures/node_modules/pkgexports/package.json @@ -32,6 +32,7 @@ "nomatch": "./nothing.js" } }], + "./no-ext": "./asdf", "./resolve-self": { "require": "./resolve-self.js", "import": "./resolve-self.mjs" @@ -39,6 +40,7 @@ "./resolve-self-invalid": { "require": "./resolve-self-invalid.js", "import": "./resolve-self-invalid.mjs" - } + }, + "./subpath/": "./subpath/" } } diff --git a/test/fixtures/node_modules/pkgexports/subpath/dir1/dir1.js b/test/fixtures/node_modules/pkgexports/subpath/dir1/dir1.js new file mode 100644 index 00000000000000..dfdd47b877319c --- /dev/null +++ b/test/fixtures/node_modules/pkgexports/subpath/dir1/dir1.js @@ -0,0 +1 @@ +module.exports = 'main'; diff --git a/test/fixtures/node_modules/pkgexports/subpath/dir1/package.json b/test/fixtures/node_modules/pkgexports/subpath/dir1/package.json new file mode 100644 index 00000000000000..ca1584ec6fe659 --- /dev/null +++ b/test/fixtures/node_modules/pkgexports/subpath/dir1/package.json @@ -0,0 +1,3 @@ +{ + "main": "dir1" +} diff --git a/test/fixtures/node_modules/pkgexports/subpath/dir2/index.js b/test/fixtures/node_modules/pkgexports/subpath/dir2/index.js new file mode 100644 index 00000000000000..d17bd0f1e9ba0f --- /dev/null +++ b/test/fixtures/node_modules/pkgexports/subpath/dir2/index.js @@ -0,0 +1 @@ +module.exports = 'index'; diff --git a/test/fixtures/node_modules/pkgexports/subpath/file.js b/test/fixtures/node_modules/pkgexports/subpath/file.js new file mode 100644 index 00000000000000..1485f701f49e67 --- /dev/null +++ b/test/fixtures/node_modules/pkgexports/subpath/file.js @@ -0,0 +1,2 @@ +module.exports = 'file'; + From d07dd313ae830488a27195095b86218fae6701d0 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sat, 25 Jan 2020 12:01:30 +0100 Subject: [PATCH 002/190] stream: add regression test for async iteration completion A test was missing for an async iterator created after the stream had emitted 'close'. This was regressed by #31314. See: https://github.com/nodejs/node/pull/31314 PR-URL: https://github.com/nodejs/node/pull/31508 Reviewed-By: James M Snell Reviewed-By: Anatoli Papirovski Reviewed-By: Colin Ihrig Reviewed-By: Rich Trott Reviewed-By: Benjamin Gruenbaum --- .../test-stream-readable-async-iterators.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/parallel/test-stream-readable-async-iterators.js b/test/parallel/test-stream-readable-async-iterators.js index 4a63e9fd3022e6..c8b0e5151af0aa 100644 --- a/test/parallel/test-stream-readable-async-iterators.js +++ b/test/parallel/test-stream-readable-async-iterators.js @@ -527,5 +527,24 @@ async function tests() { p.then(common.mustCall()).catch(common.mustNotCall()); } +{ + // AsyncIterator should finish correctly if destroyed. + + const r = new Readable({ + objectMode: true, + read() { + } + }); + + r.destroy(); + r.on('close', () => { + const it = r[Symbol.asyncIterator](); + const next = it.next(); + next + .then(common.mustCall(({ done }) => assert.strictEqual(done, true))) + .catch(common.mustNotCall()); + }); +} + // To avoid missing some tests if a promise does not resolve tests().then(common.mustCall()); From 3bf21b096e292a27334ae90633838e3e795aae68 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Mon, 27 Jan 2020 15:01:25 +0100 Subject: [PATCH 003/190] src: fix minor typo in base_object.h PR-URL: https://github.com/nodejs/node/pull/31535 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: David Carlier Reviewed-By: James M Snell Reviewed-By: Rich Trott --- src/base_object.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base_object.h b/src/base_object.h index daf40b7c1eb7b4..e7ef029995f1cf 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -165,7 +165,7 @@ inline T* Unwrap(v8::Local obj) { // Implementation of a generic strong or weak pointer to a BaseObject. // If strong, this will keep the target BaseObject alive regardless of other -// circumstances such das GC or Environment cleanup. +// circumstances such as the GC or Environment cleanup. // If weak, destruction behaviour is not affected, but the pointer will be // reset to nullptr once the BaseObject is destroyed. // The API matches std::shared_ptr closely. From 28cb7e78ffeb69db0c47eb19dfdc9ac2e96313b2 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sat, 1 Feb 2020 17:16:23 +0100 Subject: [PATCH 004/190] worker: improve MessagePort performance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use a JS function as the single entry point for emitting `.onmessage()` calls, avoiding the overhead of manually constructing each message event object in C++. confidence improvement accuracy (*) (**) (***) worker/echo.js n=100000 sendsPerBroadcast=1 payload='object' workers=1 *** 16.34 % ±1.16% ±1.54% ±1.99% worker/echo.js n=100000 sendsPerBroadcast=1 payload='string' workers=1 *** 24.41 % ±1.50% ±1.99% ±2.58% worker/echo.js n=100000 sendsPerBroadcast=10 payload='object' workers=1 *** 26.66 % ±1.54% ±2.05% ±2.65% worker/echo.js n=100000 sendsPerBroadcast=10 payload='string' workers=1 *** 32.72 % ±1.60% ±2.11% ±2.73% worker/messageport.js n=1000000 payload='object' *** 40.28 % ±1.48% ±1.95% ±2.52% worker/messageport.js n=1000000 payload='string' *** 76.95 % ±2.19% ±2.90% ±3.75% Also fix handling exceptions returned from `MessagePort::New`. PR-URL: https://github.com/nodejs/node/pull/31605 Reviewed-By: James M Snell Reviewed-By: Denys Otrishko Reviewed-By: Rich Trott --- lib/internal/per_context/messageport.js | 12 +++++ node.gyp | 1 + src/api/environment.cc | 1 + src/env.h | 1 - src/node_messaging.cc | 63 ++++++++++++++++--------- src/node_messaging.h | 7 ++- 6 files changed, 61 insertions(+), 24 deletions(-) create mode 100644 lib/internal/per_context/messageport.js diff --git a/lib/internal/per_context/messageport.js b/lib/internal/per_context/messageport.js new file mode 100644 index 00000000000000..ee08a596122a51 --- /dev/null +++ b/lib/internal/per_context/messageport.js @@ -0,0 +1,12 @@ +'use strict'; +class MessageEvent { + constructor(data, target) { + this.data = data; + this.target = target; + } +} + +exports.emitMessage = function(data) { + if (typeof this.onmessage === 'function') + this.onmessage(new MessageEvent(data, this)); +}; diff --git a/node.gyp b/node.gyp index ee99fbe7c522e9..cea8407886ec0b 100644 --- a/node.gyp +++ b/node.gyp @@ -35,6 +35,7 @@ 'lib/internal/bootstrap/switches/is_not_main_thread.js', 'lib/internal/per_context/primordials.js', 'lib/internal/per_context/domexception.js', + 'lib/internal/per_context/messageport.js', 'lib/async_hooks.js', 'lib/assert.js', 'lib/buffer.js', diff --git a/src/api/environment.cc b/src/api/environment.cc index 48c45b6e5e3eb9..1df70cf6a6d44d 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -433,6 +433,7 @@ bool InitializeContextForSnapshot(Local context) { static const char* context_files[] = {"internal/per_context/primordials", "internal/per_context/domexception", + "internal/per_context/messageport", nullptr}; for (const char** module = context_files; *module != nullptr; module++) { diff --git a/src/env.h b/src/env.h index e5e8c107f6881d..d7334fb2b52766 100644 --- a/src/env.h +++ b/src/env.h @@ -413,7 +413,6 @@ constexpr size_t kFsStatsBufferLength = V(http2stream_constructor_template, v8::ObjectTemplate) \ V(http2ping_constructor_template, v8::ObjectTemplate) \ V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \ - V(message_event_object_template, v8::ObjectTemplate) \ V(message_port_constructor_template, v8::FunctionTemplate) \ V(pipe_constructor_template, v8::FunctionTemplate) \ V(promise_wrap_template, v8::ObjectTemplate) \ diff --git a/src/node_messaging.cc b/src/node_messaging.cc index f55cb7ed9bd08c..c7c46063731a5b 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -28,7 +28,6 @@ using v8::Maybe; using v8::MaybeLocal; using v8::Nothing; using v8::Object; -using v8::ObjectTemplate; using v8::SharedArrayBuffer; using v8::String; using v8::Symbol; @@ -188,6 +187,20 @@ uint32_t Message::AddWASMModule(WasmModuleObject::TransferrableModule&& mod) { namespace { +MaybeLocal GetEmitMessageFunction(Local context) { + Isolate* isolate = context->GetIsolate(); + Local per_context_bindings; + Local emit_message_val; + if (!GetPerContextExports(context).ToLocal(&per_context_bindings) || + !per_context_bindings->Get(context, + FIXED_ONE_BYTE_STRING(isolate, "emitMessage")) + .ToLocal(&emit_message_val)) { + return MaybeLocal(); + } + CHECK(emit_message_val->IsFunction()); + return emit_message_val.As(); +} + MaybeLocal GetDOMException(Local context) { Isolate* isolate = context->GetIsolate(); Local per_context_bindings; @@ -498,10 +511,14 @@ MessagePort::MessagePort(Environment* env, MessagePort* channel = ContainerOf(&MessagePort::async_, handle); channel->OnMessage(); }; + CHECK_EQ(uv_async_init(env->event_loop(), &async_, onmessage), 0); - async_.data = static_cast(this); + async_.data = nullptr; // Reset later to indicate success of the constructor. + auto cleanup = OnScopeLeave([&]() { + if (async_.data == nullptr) Close(); + }); Local fn; if (!wrap->Get(context, env->oninit_symbol()).ToLocal(&fn)) @@ -509,9 +526,16 @@ MessagePort::MessagePort(Environment* env, if (fn->IsFunction()) { Local init = fn.As(); - USE(init->Call(context, wrap, 0, nullptr)); + if (init->Call(context, wrap, 0, nullptr).IsEmpty()) + return; } + Local emit_message_fn; + if (!GetEmitMessageFunction(context).ToLocal(&emit_message_fn)) + return; + emit_message_fn_.Reset(env->isolate(), emit_message_fn); + + async_.data = static_cast(this); Debug(this, "Created message port"); } @@ -559,6 +583,11 @@ MessagePort* MessagePort::New( return nullptr; MessagePort* port = new MessagePort(env, context, instance); CHECK_NOT_NULL(port); + if (port->IsHandleClosing()) { + // Construction failed with an exception. + return nullptr; + } + if (data) { port->Detach(); port->data_ = std::move(data); @@ -651,16 +680,8 @@ void MessagePort::OnMessage() { continue; } - Local event; - Local cb_args[1]; - if (!env()->message_event_object_template()->NewInstance(context) - .ToLocal(&event) || - event->Set(context, env()->data_string(), payload).IsNothing() || - event->Set(context, env()->target_string(), object()).IsNothing() || - (cb_args[0] = event, false) || - MakeCallback(env()->onmessage_string(), - arraysize(cb_args), - cb_args).IsEmpty()) { + Local emit_message = PersistentToLocal::Strong(emit_message_fn_); + if (MakeCallback(emit_message, 1, &payload).IsEmpty()) { // Re-schedule OnMessage() execution in case of failure. if (data_) TriggerAsync(); @@ -929,6 +950,7 @@ void MessagePort::Entangle(MessagePort* a, MessagePortData* b) { void MessagePort::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("data", data_); + tracker->TrackField("emit_message_fn", emit_message_fn_); } Local GetMessagePortConstructorTemplate(Environment* env) { @@ -938,8 +960,6 @@ Local GetMessagePortConstructorTemplate(Environment* env) { if (!templ.IsEmpty()) return templ; - Isolate* isolate = env->isolate(); - { Local m = env->NewFunctionTemplate(MessagePort::New); m->SetClassName(env->message_port_constructor_string()); @@ -950,13 +970,6 @@ Local GetMessagePortConstructorTemplate(Environment* env) { env->SetProtoMethod(m, "start", MessagePort::Start); env->set_message_port_constructor_template(m); - - Local event_ctor = FunctionTemplate::New(isolate); - event_ctor->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "MessageEvent")); - Local e = event_ctor->InstanceTemplate(); - e->Set(env->data_string(), Null(isolate)); - e->Set(env->target_string(), Null(isolate)); - env->set_message_event_object_template(e); } return GetMessagePortConstructorTemplate(env); @@ -975,7 +988,13 @@ static void MessageChannel(const FunctionCallbackInfo& args) { Context::Scope context_scope(context); MessagePort* port1 = MessagePort::New(env, context); + if (port1 == nullptr) return; MessagePort* port2 = MessagePort::New(env, context); + if (port2 == nullptr) { + port1->Close(); + return; + } + MessagePort::Entangle(port1, port2); args.This()->Set(context, env->port1_string(), port1->object()) diff --git a/src/node_messaging.h b/src/node_messaging.h index d64bf23e086d05..43f710a84e8315 100644 --- a/src/node_messaging.h +++ b/src/node_messaging.h @@ -132,12 +132,16 @@ class MessagePortData : public MemoryRetainer { // the uv_async_t handle that is used to notify the current event loop of // new incoming messages. class MessagePort : public HandleWrap { - public: + private: // Create a new MessagePort. The `context` argument specifies the Context // instance that is used for creating the values emitted from this port. + // This is called by MessagePort::New(), which is the public API used for + // creating MessagePort instances. MessagePort(Environment* env, v8::Local context, v8::Local wrap); + + public: ~MessagePort() override; // Create a new message port instance, optionally over an existing @@ -206,6 +210,7 @@ class MessagePort : public HandleWrap { std::unique_ptr data_ = nullptr; bool receiving_messages_ = false; uv_async_t async_; + v8::Global emit_message_fn_; friend class MessagePortData; }; From 6e68d9816d65110c9f33c2118d58d5c93c77f33b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Sat, 22 Feb 2020 15:18:56 -0400 Subject: [PATCH 005/190] doc: fix anchor for ERR_TLS_INVALID_CONTEXT PR-URL: https://github.com/nodejs/node/pull/31915 Reviewed-By: Luigi Pinca Reviewed-By: Rich Trott Reviewed-By: Colin Ihrig --- doc/api/errors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/errors.md b/doc/api/errors.md index 8ecb313841c073..2adc69eadc58dc 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1820,8 +1820,8 @@ recommended to use 2048 bits or larger for stronger security. A TLS/SSL handshake timed out. In this case, the server must also abort the connection. - -### ERR_TLS_INVALID_CONTEXT + +### `ERR_TLS_INVALID_CONTEXT` From 7d66ceadbbdc56a6b922ccee9432fcc145ae42fa Mon Sep 17 00:00:00 2001 From: James M Snell Date: Thu, 27 Feb 2020 13:14:38 -0800 Subject: [PATCH 006/190] perf,src: add HistogramBase and internal/histogram.js Separating this out from the QUIC PR to allow it to be separately reviewed. The QUIC implementation makes use of the hdr_histogram for dynamic performance monitoring. This introduces a BaseObject class that allows the internal histograms to be accessed on the JavaScript side and adds a generic Histogram class that will be used by both QUIC and perf_hooks (for the event loop delay monitoring). Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/31988 Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina --- lib/internal/histogram.js | 94 +++++++++++++++++++++++++ lib/perf_hooks.js | 49 ++----------- node.gyp | 2 + src/env.h | 1 + src/histogram-inl.h | 70 ++++++++++++------- src/histogram.cc | 141 ++++++++++++++++++++++++++++++++++++++ src/histogram.h | 70 +++++++++++++++++-- src/node_perf.h | 2 +- 8 files changed, 355 insertions(+), 74 deletions(-) create mode 100644 lib/internal/histogram.js create mode 100644 src/histogram.cc diff --git a/lib/internal/histogram.js b/lib/internal/histogram.js new file mode 100644 index 00000000000000..6deb8314a41bbb --- /dev/null +++ b/lib/internal/histogram.js @@ -0,0 +1,94 @@ +'use strict'; + +const { + customInspectSymbol: kInspect, +} = require('internal/util'); + +const { format } = require('util'); +const { Map, Symbol } = primordials; + +const { + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, +} = require('internal/errors').codes; + +const kDestroy = Symbol('kDestroy'); +const kHandle = Symbol('kHandle'); + +// Histograms are created internally by Node.js and used to +// record various metrics. This Histogram class provides a +// generally read-only view of the internal histogram. +class Histogram { + #handle = undefined; + #map = new Map(); + + constructor(internal) { + this.#handle = internal; + } + + [kInspect]() { + const obj = { + min: this.min, + max: this.max, + mean: this.mean, + exceeds: this.exceeds, + stddev: this.stddev, + percentiles: this.percentiles, + }; + return `Histogram ${format(obj)}`; + } + + get min() { + return this.#handle ? this.#handle.min() : undefined; + } + + get max() { + return this.#handle ? this.#handle.max() : undefined; + } + + get mean() { + return this.#handle ? this.#handle.mean() : undefined; + } + + get exceeds() { + return this.#handle ? this.#handle.exceeds() : undefined; + } + + get stddev() { + return this.#handle ? this.#handle.stddev() : undefined; + } + + percentile(percentile) { + if (typeof percentile !== 'number') + throw new ERR_INVALID_ARG_TYPE('percentile', 'number', percentile); + + if (percentile <= 0 || percentile > 100) + throw new ERR_INVALID_ARG_VALUE.RangeError('percentile', percentile); + + return this.#handle ? this.#handle.percentile(percentile) : undefined; + } + + get percentiles() { + this.#map.clear(); + if (this.#handle) + this.#handle.percentiles(this.#map); + return this.#map; + } + + reset() { + if (this.#handle) + this.#handle.reset(); + } + + [kDestroy]() { + this.#handle = undefined; + } + + get [kHandle]() { return this.#handle; } +} + +module.exports = { + Histogram, + kDestroy, + kHandle, +}; diff --git a/lib/perf_hooks.js b/lib/perf_hooks.js index 267b4577ffee3d..a141705728227b 100644 --- a/lib/perf_hooks.js +++ b/lib/perf_hooks.js @@ -3,7 +3,6 @@ const { ArrayIsArray, Boolean, - Map, NumberIsSafeInteger, ObjectDefineProperties, ObjectDefineProperty, @@ -52,16 +51,18 @@ const kInspect = require('internal/util').customInspectSymbol; const { ERR_INVALID_CALLBACK, - ERR_INVALID_ARG_VALUE, ERR_INVALID_ARG_TYPE, ERR_INVALID_OPT_VALUE, ERR_VALID_PERFORMANCE_ENTRY_TYPE, ERR_INVALID_PERFORMANCE_MARK } = require('internal/errors').codes; +const { + Histogram, + kHandle, +} = require('internal/histogram'); + const { setImmediate } = require('timers'); -const kHandle = Symbol('handle'); -const kMap = Symbol('map'); const kCallback = Symbol('callback'); const kTypes = Symbol('types'); const kEntries = Symbol('entries'); @@ -557,47 +558,9 @@ function sortedInsert(list, entry) { list.splice(location, 0, entry); } -class ELDHistogram { - constructor(handle) { - this[kHandle] = handle; - this[kMap] = new Map(); - } - - reset() { this[kHandle].reset(); } +class ELDHistogram extends Histogram { enable() { return this[kHandle].enable(); } disable() { return this[kHandle].disable(); } - - get exceeds() { return this[kHandle].exceeds(); } - get min() { return this[kHandle].min(); } - get max() { return this[kHandle].max(); } - get mean() { return this[kHandle].mean(); } - get stddev() { return this[kHandle].stddev(); } - percentile(percentile) { - if (typeof percentile !== 'number') { - throw new ERR_INVALID_ARG_TYPE('percentile', 'number', percentile); - } - if (percentile <= 0 || percentile > 100) { - throw new ERR_INVALID_ARG_VALUE.RangeError('percentile', - percentile); - } - return this[kHandle].percentile(percentile); - } - get percentiles() { - this[kMap].clear(); - this[kHandle].percentiles(this[kMap]); - return this[kMap]; - } - - [kInspect]() { - return { - min: this.min, - max: this.max, - mean: this.mean, - stddev: this.stddev, - percentiles: this.percentiles, - exceeds: this.exceeds - }; - } } function monitorEventLoopDelay(options = {}) { diff --git a/node.gyp b/node.gyp index cea8407886ec0b..8f64c7c6825aaf 100644 --- a/node.gyp +++ b/node.gyp @@ -137,6 +137,7 @@ 'lib/internal/fs/utils.js', 'lib/internal/fs/watchers.js', 'lib/internal/http.js', + 'lib/internal/histogram.js', 'lib/internal/idna.js', 'lib/internal/inspector_async_hook.js', 'lib/internal/js_stream_socket.js', @@ -529,6 +530,7 @@ 'src/fs_event_wrap.cc', 'src/handle_wrap.cc', 'src/heap_utils.cc', + 'src/histogram.cc', 'src/js_native_api.h', 'src/js_native_api_types.h', 'src/js_native_api_v8.cc', diff --git a/src/env.h b/src/env.h index d7334fb2b52766..c4d3b57d18c409 100644 --- a/src/env.h +++ b/src/env.h @@ -409,6 +409,7 @@ constexpr size_t kFsStatsBufferLength = V(filehandlereadwrap_template, v8::ObjectTemplate) \ V(fsreqpromise_constructor_template, v8::ObjectTemplate) \ V(handle_wrap_ctor_template, v8::FunctionTemplate) \ + V(histogram_instance_template, v8::ObjectTemplate) \ V(http2settings_constructor_template, v8::ObjectTemplate) \ V(http2stream_constructor_template, v8::ObjectTemplate) \ V(http2ping_constructor_template, v8::ObjectTemplate) \ diff --git a/src/histogram-inl.h b/src/histogram-inl.h index 3135041f7387a9..58911dae8f2dae 100644 --- a/src/histogram-inl.h +++ b/src/histogram-inl.h @@ -4,58 +4,78 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "histogram.h" +#include "base_object-inl.h" #include "node_internals.h" namespace node { -inline Histogram::Histogram(int64_t lowest, int64_t highest, int figures) { - CHECK_EQ(0, hdr_init(lowest, highest, figures, &histogram_)); +void Histogram::Reset() { + hdr_reset(histogram_.get()); } -inline Histogram::~Histogram() { - hdr_close(histogram_); +bool Histogram::Record(int64_t value) { + return hdr_record_value(histogram_.get(), value); } -inline void Histogram::Reset() { - hdr_reset(histogram_); +int64_t Histogram::Min() { + return hdr_min(histogram_.get()); } -inline bool Histogram::Record(int64_t value) { - return hdr_record_value(histogram_, value); +int64_t Histogram::Max() { + return hdr_max(histogram_.get()); } -inline int64_t Histogram::Min() { - return hdr_min(histogram_); +double Histogram::Mean() { + return hdr_mean(histogram_.get()); } -inline int64_t Histogram::Max() { - return hdr_max(histogram_); +double Histogram::Stddev() { + return hdr_stddev(histogram_.get()); } -inline double Histogram::Mean() { - return hdr_mean(histogram_); -} - -inline double Histogram::Stddev() { - return hdr_stddev(histogram_); -} - -inline double Histogram::Percentile(double percentile) { +double Histogram::Percentile(double percentile) { CHECK_GT(percentile, 0); CHECK_LE(percentile, 100); - return hdr_value_at_percentile(histogram_, percentile); + return static_cast( + hdr_value_at_percentile(histogram_.get(), percentile)); } -inline void Histogram::Percentiles(std::function fn) { +template +void Histogram::Percentiles(Iterator&& fn) { hdr_iter iter; - hdr_iter_percentile_init(&iter, histogram_, 1); + hdr_iter_percentile_init(&iter, histogram_.get(), 1); while (hdr_iter_next(&iter)) { double key = iter.specifics.percentiles.percentile; - double value = iter.value; + double value = static_cast(iter.value); fn(key, value); } } +bool HistogramBase::RecordDelta() { + uint64_t time = uv_hrtime(); + bool ret = true; + if (prev_ > 0) { + int64_t delta = time - prev_; + if (delta > 0) { + ret = Record(delta); + TraceDelta(delta); + if (!ret) { + if (exceeds_ < 0xFFFFFFFF) + exceeds_++; + TraceExceeds(delta); + } + } + } + prev_ = time; + return ret; +} + +void HistogramBase::ResetState() { + Reset(); + exceeds_ = 0; + prev_ = 0; +} + } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/histogram.cc b/src/histogram.cc new file mode 100644 index 00000000000000..8d1eb77b1bc88e --- /dev/null +++ b/src/histogram.cc @@ -0,0 +1,141 @@ +#include "histogram.h" // NOLINT(build/include_inline) +#include "histogram-inl.h" +#include "memory_tracker-inl.h" + +namespace node { + +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Local; +using v8::Map; +using v8::Number; +using v8::ObjectTemplate; +using v8::String; +using v8::Value; + +Histogram::Histogram(int64_t lowest, int64_t highest, int figures) { + hdr_histogram* histogram; + CHECK_EQ(0, hdr_init(lowest, highest, figures, &histogram)); + histogram_.reset(histogram); +} + +HistogramBase::HistogramBase( + Environment* env, + v8::Local wrap, + int64_t lowest, + int64_t highest, + int figures) + : BaseObject(env, wrap), + Histogram(lowest, highest, figures) { + MakeWeak(); +} + +void HistogramBase::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackFieldWithSize("histogram", GetMemorySize()); +} + +void HistogramBase::GetMin(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast(histogram->Min()); + args.GetReturnValue().Set(value); +} + +void HistogramBase::GetMax(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast(histogram->Max()); + args.GetReturnValue().Set(value); +} + +void HistogramBase::GetMean(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set(histogram->Mean()); +} + +void HistogramBase::GetExceeds(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + double value = static_cast(histogram->Exceeds()); + args.GetReturnValue().Set(value); +} + +void HistogramBase::GetStddev(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + args.GetReturnValue().Set(histogram->Stddev()); +} + +void HistogramBase::GetPercentile( + const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsNumber()); + double percentile = args[0].As()->Value(); + args.GetReturnValue().Set(histogram->Percentile(percentile)); +} + +void HistogramBase::GetPercentiles( + const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + CHECK(args[0]->IsMap()); + Local map = args[0].As(); + histogram->Percentiles([map, env](double key, double value) { + map->Set( + env->context(), + Number::New(env->isolate(), key), + Number::New(env->isolate(), value)).IsEmpty(); + }); +} + +void HistogramBase::DoReset(const FunctionCallbackInfo& args) { + HistogramBase* histogram; + ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder()); + histogram->ResetState(); +} + +BaseObjectPtr HistogramBase::New( + Environment* env, + int64_t lowest, + int64_t highest, + int figures) { + CHECK_LE(lowest, highest); + CHECK_GT(figures, 0); + v8::Local obj; + auto tmpl = env->histogram_instance_template(); + if (!tmpl->NewInstance(env->context()).ToLocal(&obj)) + return {}; + + return MakeDetachedBaseObject( + env, obj, lowest, highest, figures); +} + +void HistogramBase::Initialize(Environment* env) { + // Guard against multiple initializations + if (!env->histogram_instance_template().IsEmpty()) + return; + + Local histogram = FunctionTemplate::New(env->isolate()); + Local classname = FIXED_ONE_BYTE_STRING(env->isolate(), "Histogram"); + histogram->SetClassName(classname); + + Local histogramt = + histogram->InstanceTemplate(); + + histogramt->SetInternalFieldCount(1); + env->SetProtoMethod(histogram, "exceeds", HistogramBase::GetExceeds); + env->SetProtoMethod(histogram, "min", HistogramBase::GetMin); + env->SetProtoMethod(histogram, "max", HistogramBase::GetMax); + env->SetProtoMethod(histogram, "mean", HistogramBase::GetMean); + env->SetProtoMethod(histogram, "stddev", HistogramBase::GetStddev); + env->SetProtoMethod(histogram, "percentile", HistogramBase::GetPercentile); + env->SetProtoMethod(histogram, "percentiles", HistogramBase::GetPercentiles); + env->SetProtoMethod(histogram, "reset", HistogramBase::DoReset); + + env->set_histogram_instance_template(histogramt); +} + +} // namespace node diff --git a/src/histogram.h b/src/histogram.h index eb94af5da2a997..e92c31c4724ac6 100644 --- a/src/histogram.h +++ b/src/histogram.h @@ -4,15 +4,24 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "hdr_histogram.h" +#include "base_object.h" +#include "util.h" + #include +#include #include namespace node { +constexpr int kDefaultHistogramFigures = 3; + class Histogram { public: - inline Histogram(int64_t lowest, int64_t highest, int figures = 3); - inline virtual ~Histogram(); + Histogram( + int64_t lowest = std::numeric_limits::min(), + int64_t highest = std::numeric_limits::max(), + int figures = kDefaultHistogramFigures); + virtual ~Histogram() = default; inline bool Record(int64_t value); inline void Reset(); @@ -21,14 +30,65 @@ class Histogram { inline double Mean(); inline double Stddev(); inline double Percentile(double percentile); - inline void Percentiles(std::function fn); + + // Iterator is a function type that takes two doubles as argument, one for + // percentile and one for the value at that percentile. + template + inline void Percentiles(Iterator&& fn); size_t GetMemorySize() const { - return hdr_get_memory_size(histogram_); + return hdr_get_memory_size(histogram_.get()); } private: - hdr_histogram* histogram_; + using HistogramPointer = DeleteFnPtr; + HistogramPointer histogram_; +}; + +class HistogramBase : public BaseObject, public Histogram { + public: + virtual ~HistogramBase() = default; + + virtual void TraceDelta(int64_t delta) {} + virtual void TraceExceeds(int64_t delta) {} + + inline bool RecordDelta(); + inline void ResetState(); + + int64_t Exceeds() const { return exceeds_; } + + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(HistogramBase) + SET_SELF_SIZE(HistogramBase) + + static void GetMin(const v8::FunctionCallbackInfo& args); + static void GetMax(const v8::FunctionCallbackInfo& args); + static void GetMean(const v8::FunctionCallbackInfo& args); + static void GetExceeds(const v8::FunctionCallbackInfo& args); + static void GetStddev(const v8::FunctionCallbackInfo& args); + static void GetPercentile( + const v8::FunctionCallbackInfo& args); + static void GetPercentiles( + const v8::FunctionCallbackInfo& args); + static void DoReset(const v8::FunctionCallbackInfo& args); + static void Initialize(Environment* env); + + static BaseObjectPtr New( + Environment* env, + int64_t lowest = std::numeric_limits::min(), + int64_t highest = std::numeric_limits::max(), + int figures = kDefaultHistogramFigures); + + HistogramBase( + Environment* env, + v8::Local wrap, + int64_t lowest = std::numeric_limits::min(), + int64_t highest = std::numeric_limits::max(), + int figures = kDefaultHistogramFigures); + + private: + int64_t exceeds_ = 0; + uint64_t prev_ = 0; }; } // namespace node diff --git a/src/node_perf.h b/src/node_perf.h index c5e45261c2c6fd..d7f593e5303542 100644 --- a/src/node_perf.h +++ b/src/node_perf.h @@ -139,7 +139,7 @@ class ELDHistogram : public HandleWrap, public Histogram { exceeds_ = 0; prev_ = 0; } - int64_t Exceeds() { return exceeds_; } + int64_t Exceeds() const { return exceeds_; } void MemoryInfo(MemoryTracker* tracker) const override { tracker->TrackFieldWithSize("histogram", GetMemorySize()); From f9123eb91b9d416f297bcef90e46465229be3d04 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Fri, 28 Feb 2020 22:27:39 +0100 Subject: [PATCH 007/190] 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 6de82da455546f..be4642177a73c0 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)` ## 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.`