tools,lib: forbid native Error constructors

This adds a rule that forbids the use of native Error constructors in
the `lib` directory. This is to encourage use of the `internal/errors`
mechanism. The rule is disabled for errors that are not created with
the `internal/errors` module but are still assigned an error code.

PR-URL: https://github.com/nodejs/node/pull/19373
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Michaël Zasso 2018-03-15 14:22:43 +01:00
parent ab8bf26994
commit 6a9f049968
No known key found for this signature in database
GPG Key ID: 770F7A9A5AE15600
16 changed files with 39 additions and 0 deletions

View File

@ -148,6 +148,7 @@ module.exports = {
} }
], ],
/* eslint-disable max-len, quotes */ /* eslint-disable max-len, quotes */
// If this list is modified, please copy the change to lib/.eslintrc.yaml
'no-restricted-syntax': [ 'no-restricted-syntax': [
'error', 'error',
{ {

View File

@ -1,4 +1,22 @@
rules: rules:
no-restricted-syntax:
# Config copied from .eslintrc.js
- error
- selector: "CallExpression[callee.object.name='assert'][callee.property.name='doesNotThrow']"
message: "Please replace `assert.doesNotThrow()` and add a comment next to the code instead."
- selector: "CallExpression[callee.object.name='assert'][callee.property.name='throws'][arguments.1.type='Literal']:not([arguments.1.regex])"
message: "use a regular expression for second argument of assert.throws()"
- selector: "CallExpression[callee.object.name='assert'][callee.property.name='throws'][arguments.length<2]"
message: "assert.throws() must be invoked with at least two arguments."
- selector: "CallExpression[callee.name='setTimeout'][arguments.length<2]"
message: "setTimeout() must be invoked with at least two arguments."
- selector: "CallExpression[callee.name='setInterval'][arguments.length<2]"
message: "setInterval() must be invoked with at least 2 arguments."
- selector: "ThrowStatement > CallExpression[callee.name=/Error$/]"
message: "Use new keyword when throwing an Error."
# Config specific to lib
- selector: "NewExpression[callee.name=/Error$/]:not([callee.name=/^(AssertionError|NghttpError)$/])"
message: "Use an error exported by the internal/errors module."
# Custom rules in tools/eslint-rules # Custom rules in tools/eslint-rules
node-core/require-buffer: error node-core/require-buffer: error
node-core/buffer-constructor: error node-core/buffer-constructor: error

View File

@ -307,6 +307,7 @@ function emitAbortNT() {
function createHangUpError() { function createHangUpError() {
// eslint-disable-next-line no-restricted-syntax
var error = new Error('socket hang up'); var error = new Error('socket hang up');
error.code = 'ECONNRESET'; error.code = 'ECONNRESET';
return error; return error;

View File

@ -775,6 +775,7 @@ function onSocketClose(err) {
// Emit ECONNRESET // Emit ECONNRESET
if (!this._controlReleased && !this[kErrorEmitted]) { if (!this._controlReleased && !this[kErrorEmitted]) {
this[kErrorEmitted] = true; this[kErrorEmitted] = true;
// eslint-disable-next-line no-restricted-syntax
const connReset = new Error('socket hang up'); const connReset = new Error('socket hang up');
connReset.code = 'ECONNRESET'; connReset.code = 'ECONNRESET';
this._tlsOptions.server.emit('tlsClientError', connReset, this); this._tlsOptions.server.emit('tlsClientError', connReset, this);
@ -1103,6 +1104,7 @@ function onConnectEnd() {
if (!this._hadError) { if (!this._hadError) {
const options = this[kConnectOptions]; const options = this[kConnectOptions];
this._hadError = true; this._hadError = true;
// eslint-disable-next-line no-restricted-syntax
const error = new Error('Client network socket disconnected before ' + const error = new Error('Client network socket disconnected before ' +
'secure TLS connection was established'); 'secure TLS connection was established');
error.code = 'ECONNRESET'; error.code = 'ECONNRESET';

View File

@ -216,6 +216,7 @@ function innerOk(fn, argLen, value, message) {
} else if (message == null) { } else if (message == null) {
// Use the call as error message if possible. // Use the call as error message if possible.
// This does not work with e.g. the repl. // This does not work with e.g. the repl.
// eslint-disable-next-line no-restricted-syntax
const err = new Error(); const err = new Error();
// Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it // Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it
// does to much work. // does to much work.

View File

@ -1073,6 +1073,7 @@ if (process.binding('config').hasIntl) {
return result; return result;
const code = icuErrName(result); const code = icuErrName(result);
// eslint-disable-next-line no-restricted-syntax
const err = new Error(`Unable to transcode Buffer [${code}]`); const err = new Error(`Unable to transcode Buffer [${code}]`);
err.code = code; err.code = code;
err.errno = result; err.errno = result;

View File

@ -278,6 +278,7 @@ exports.execFile = function(file /*, args, options, callback*/) {
cmd += ` ${args.join(' ')}`; cmd += ` ${args.join(' ')}`;
if (!ex) { if (!ex) {
// eslint-disable-next-line no-restricted-syntax
ex = new Error('Command failed: ' + cmd + '\n' + stderr); ex = new Error('Command failed: ' + cmd + '\n' + stderr);
ex.killed = child.killed || killed; ex.killed = child.killed || killed;
ex.code = code < 0 ? getSystemErrorName(code) : code; ex.code = code < 0 ? getSystemErrorName(code) : code;
@ -588,6 +589,7 @@ function checkExecSyncError(ret, args, cmd) {
msg += cmd || args.join(' '); msg += cmd || args.join(' ');
if (ret.stderr && ret.stderr.length > 0) if (ret.stderr && ret.stderr.length > 0)
msg += `\n${ret.stderr.toString()}`; msg += `\n${ret.stderr.toString()}`;
// eslint-disable-next-line no-restricted-syntax
err = new Error(msg); err = new Error(msg);
} }
if (err) { if (err) {

View File

@ -89,6 +89,7 @@ if (process.hasUncaughtExceptionCaptureCallback()) {
} }
// Get the stack trace at the point where `domain` was required. // Get the stack trace at the point where `domain` was required.
// eslint-disable-next-line no-restricted-syntax
const domainRequireStack = new Error('require(`domain`) at this point').stack; const domainRequireStack = new Error('require(`domain`) at this point').stack;
const { setUncaughtExceptionCaptureCallback } = process; const { setUncaughtExceptionCaptureCallback } = process;

View File

@ -240,6 +240,7 @@ function _addListener(target, type, listener, prepend) {
if (m && m > 0 && existing.length > m) { if (m && m > 0 && existing.length > m) {
existing.warned = true; existing.warned = true;
// No error code for this since it is a Warning // No error code for this since it is a Warning
// eslint-disable-next-line no-restricted-syntax
const w = new Error('Possible EventEmitter memory leak detected. ' + const w = new Error('Possible EventEmitter memory leak detected. ' +
`${existing.length} ${String(type)} listeners ` + `${existing.length} ${String(type)} listeners ` +
'added. Use emitter.setMaxListeners() to ' + 'added. Use emitter.setMaxListeners() to ' +

View File

@ -127,6 +127,7 @@
// Model the error off the internal/errors.js model, but // Model the error off the internal/errors.js model, but
// do not use that module given that it could actually be // do not use that module given that it could actually be
// the one causing the error if there's a bug in Node.js // the one causing the error if there's a bug in Node.js
// eslint-disable-next-line no-restricted-syntax
const err = new Error(`No such built-in module: ${id}`); const err = new Error(`No such built-in module: ${id}`);
err.code = 'ERR_UNKNOWN_BUILTIN_MODULE'; err.code = 'ERR_UNKNOWN_BUILTIN_MODULE';
err.name = 'Error [ERR_UNKNOWN_BUILTIN_MODULE]'; err.name = 'Error [ERR_UNKNOWN_BUILTIN_MODULE]';

View File

@ -444,6 +444,7 @@ function uvException(ctx) {
// Pass the message to the constructor instead of setting it on the object // Pass the message to the constructor instead of setting it on the object
// to make sure it is the same as the one created in C++ // to make sure it is the same as the one created in C++
// eslint-disable-next-line no-restricted-syntax
const err = new Error(message); const err = new Error(message);
for (const prop of Object.keys(ctx)) { for (const prop of Object.keys(ctx)) {
@ -482,6 +483,7 @@ function errnoException(err, syscall, original) {
const message = original ? const message = original ?
`${syscall} ${code} ${original}` : `${syscall} ${code}`; `${syscall} ${code} ${original}` : `${syscall} ${code}`;
// eslint-disable-next-line no-restricted-syntax
const ex = new Error(message); const ex = new Error(message);
// TODO(joyeecheung): errno is supposed to err, like in uvException // TODO(joyeecheung): errno is supposed to err, like in uvException
ex.code = ex.errno = code; ex.code = ex.errno = code;
@ -517,6 +519,7 @@ function exceptionWithHostPort(err, syscall, address, port, additional) {
details += ` - Local (${additional})`; details += ` - Local (${additional})`;
} }
// eslint-disable-next-line no-restricted-syntax
const ex = new Error(`${syscall} ${code}${details}`); const ex = new Error(`${syscall} ${code}${details}`);
// TODO(joyeecheung): errno is supposed to err, like in uvException // TODO(joyeecheung): errno is supposed to err, like in uvException
ex.code = ex.errno = code; ex.code = ex.errno = code;
@ -537,6 +540,7 @@ function exceptionWithHostPort(err, syscall, address, port, additional) {
* @returns {Error} * @returns {Error}
*/ */
function dnsException(err, syscall, hostname) { function dnsException(err, syscall, hostname) {
// eslint-disable-next-line no-restricted-syntax
const ex = new Error(); const ex = new Error();
// FIXME(bnoordhuis) Remove this backwards compatibility nonsense and pass // FIXME(bnoordhuis) Remove this backwards compatibility nonsense and pass
// the true error to the user. ENOTFOUND is not even a proper POSIX error! // the true error to the user. ENOTFOUND is not even a proper POSIX error!

View File

@ -566,6 +566,7 @@ Module._resolveFilename = function(request, parent, isMain, options) {
// look up the filename first, since that's the cache key. // look up the filename first, since that's the cache key.
var filename = Module._findPath(request, paths, isMain); var filename = Module._findPath(request, paths, isMain);
if (!filename) { if (!filename) {
// eslint-disable-next-line no-restricted-syntax
var err = new Error(`Cannot find module '${request}'`); var err = new Error(`Cannot find module '${request}'`);
err.code = 'MODULE_NOT_FOUND'; err.code = 'MODULE_NOT_FOUND';
throw err; throw err;

View File

@ -30,6 +30,7 @@ function handledRejection(promise) {
if (promiseInfo.warned) { if (promiseInfo.warned) {
const { uid } = promiseInfo; const { uid } = promiseInfo;
// Generate the warning object early to get a good stack trace. // Generate the warning object early to get a good stack trace.
// eslint-disable-next-line no-restricted-syntax
const warning = new Error('Promise rejection was handled ' + const warning = new Error('Promise rejection was handled ' +
`asynchronously (rejection id: ${uid})`); `asynchronously (rejection id: ${uid})`);
warning.name = 'PromiseRejectionHandledWarning'; warning.name = 'PromiseRejectionHandledWarning';
@ -53,6 +54,7 @@ function emitWarning(uid, reason) {
// ignored // ignored
} }
// eslint-disable-next-line no-restricted-syntax
const warning = new Error( const warning = new Error(
'Unhandled promise rejection. This error originated either by ' + 'Unhandled promise rejection. This error originated either by ' +
'throwing inside of an async function without a catch block, ' + 'throwing inside of an async function without a catch block, ' +

View File

@ -126,6 +126,7 @@ function setupProcessWarnings() {
if (type !== undefined && typeof type !== 'string') if (type !== undefined && typeof type !== 'string')
throw new ERR_INVALID_ARG_TYPE('type', 'string'); throw new ERR_INVALID_ARG_TYPE('type', 'string');
if (warning === undefined || typeof warning === 'string') { if (warning === undefined || typeof warning === 'string') {
// eslint-disable-next-line no-restricted-syntax
warning = new Error(warning); warning = new Error(warning);
warning.name = String(type || 'Warning'); warning.name = String(type || 'Warning');
if (code !== undefined) warning.code = code; if (code !== undefined) warning.code = code;

View File

@ -385,6 +385,7 @@ function writeAfterFIN(chunk, encoding, cb) {
encoding = null; encoding = null;
} }
// eslint-disable-next-line no-restricted-syntax
var er = new Error('This socket has been ended by the other party'); var er = new Error('This socket has been ended by the other party');
er.code = 'EPIPE'; er.code = 'EPIPE';
// TODO: defer error events consistently everywhere, not just the cb // TODO: defer error events consistently everywhere, not just the cb

View File

@ -144,6 +144,7 @@ function zlibOnError(message, errno) {
_close(self); _close(self);
self._hadError = true; self._hadError = true;
// eslint-disable-next-line no-restricted-syntax
const error = new Error(message); const error = new Error(message);
error.errno = errno; error.errno = errno;
error.code = codes[errno]; error.code = codes[errno];