diff --git a/docs/rules/callback-return.md b/docs/rules/callback-return.md new file mode 100644 index 00000000..9622d65a --- /dev/null +++ b/docs/rules/callback-return.md @@ -0,0 +1,164 @@ +# node/callback-return +> require `return` statements after callbacks + +The callback pattern is at the heart of most I/O and event-driven programming + in JavaScript. + +```js +function doSomething(err, callback) { + if (err) { + return callback(err); + } + callback(); +} +``` + +To prevent calling the callback multiple times it is important to `return` anytime the callback is triggered outside + of the main function body. Neglecting this technique often leads to issues where you do something more than once. + For example, in the case of an HTTP request, you may try to send HTTP headers more than once leading Node.js to `throw` + a `Can't render headers after they are sent to the client.` error. + +## πŸ“– Rule Details + +This rule is aimed at ensuring that callbacks used outside of the main function block are always part-of or immediately +preceding a `return` statement. This rule decides what is a callback based on the name of the function being called. + +### Options + +The rule takes a single option - an array of possible callback names - which may include object methods. The default callback names are `callback`, `cb`, `next`. + +#### Default callback names + +Examples of **incorrect** code for this rule with the default `["callback", "cb", "next"]` option: + +```js +/*eslint callback-return: "error"*/ + +function foo(err, callback) { + if (err) { + callback(err); + } + callback(); +} +``` + +Examples of **correct** code for this rule with the default `["callback", "cb", "next"]` option: + +```js +/*eslint callback-return: "error"*/ + +function foo(err, callback) { + if (err) { + return callback(err); + } + callback(); +} +``` + +#### Supplied callback names + +Examples of **incorrect** code for this rule with the option `["done", "send.error", "send.success"]`: + +```js +/*eslint callback-return: ["error", ["done", "send.error", "send.success"]]*/ + +function foo(err, done) { + if (err) { + done(err); + } + done(); +} + +function bar(err, send) { + if (err) { + send.error(err); + } + send.success(); +} +``` + +Examples of **correct** code for this rule with the option `["done", "send.error", "send.success"]`: + +```js +/*eslint callback-return: ["error", ["done", "send.error", "send.success"]]*/ + +function foo(err, done) { + if (err) { + return done(err); + } + done(); +} + +function bar(err, send) { + if (err) { + return send.error(err); + } + send.success(); +} +``` + +### Known Limitations + +Because it is difficult to understand the meaning of a program through static analysis, this rule has limitations: + +* *false negatives* when this rule reports correct code, but the program calls the callback more than one time (which is incorrect behavior) +* *false positives* when this rule reports incorrect code, but the program calls the callback only one time (which is correct behavior) + +#### Passing the callback by reference + +The static analysis of this rule does not detect that the program calls the callback if it is an argument of a function (for example, `setTimeout`). + +Example of a *false negative* when this rule reports correct code: + +```js +/*eslint callback-return: "error"*/ + +function foo(err, callback) { + if (err) { + setTimeout(callback, 0); // this is bad, but WILL NOT warn + } + callback(); +} +``` + +#### Triggering the callback within a nested function + +The static analysis of this rule does not detect that the program calls the callback from within a nested function or an immediately-invoked function expression (IIFE). + +Example of a *false negative* when this rule reports correct code: + +```js +/*eslint callback-return: "error"*/ + +function foo(err, callback) { + if (err) { + process.nextTick(function() { + return callback(); // this is bad, but WILL NOT warn + }); + } + callback(); +} +``` + +#### If/else statements + +The static analysis of this rule does not detect that the program calls the callback only one time in each branch of an `if` statement. + +Example of a *false positive* when this rule reports incorrect code: + +```js +/*eslint callback-return: "error"*/ + +function foo(err, callback) { + if (err) { + callback(err); // this is fine, but WILL warn + } else { + callback(); // this is fine, but WILL warn + } +} +``` + +## πŸ”Ž Implementation + +- [Rule source](../../lib/rules/callback-return.js) +- [Test source](../../tests/lib/rules/callback-return.js) diff --git a/docs/rules/global-require.md b/docs/rules/global-require.md new file mode 100644 index 00000000..8b1bbad0 --- /dev/null +++ b/docs/rules/global-require.md @@ -0,0 +1,91 @@ +# node/global-require +> require `require()` calls to be placed at top-level module scope + +In Node.js, module dependencies are included using the `require()` function, such as: + +```js +var fs = require("fs"); +``` + +While `require()` may be called anywhere in code, some style guides prescribe that it should be called only in the top level of a module to make it easier to identify dependencies. For instance, it's arguably harder to identify dependencies when they are deeply nested inside of functions and other statements: + +```js +function foo() { + + if (condition) { + var fs = require("fs"); + } +} +``` + +Since `require()` does a synchronous load, it can cause performance problems when used in other locations. + +Further, ES6 modules mandate that `import` and `export` statements can only occur in the top level of the module's body. + +## πŸ“– Rule Details + +This rule requires all calls to `require()` to be at the top level of the module, similar to ES6 `import` and `export` statements, which also can occur only at the top level. + +Examples of **incorrect** code for this rule: + +```js +/*eslint global-require: "error"*/ +/*eslint-env es6*/ + +// calling require() inside of a function is not allowed +function readFile(filename, callback) { + var fs = require('fs'); + fs.readFile(filename, callback) +} + +// conditional requires like this are also not allowed +if (DEBUG) { require('debug'); } + +// a require() in a switch statement is also flagged +switch(x) { case '1': require('1'); break; } + +// you may not require() inside an arrow function body +var getModule = (name) => require(name); + +// you may not require() inside of a function body as well +function getModule(name) { return require(name); } + +// you may not require() inside of a try/catch block +try { + require(unsafeModule); +} catch(e) { + console.log(e); +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint global-require: "error"*/ + +// all these variations of require() are ok +require('x'); +var y = require('y'); +var z; +z = require('z').initialize(); + +// requiring a module and using it in a function is ok +var fs = require('fs'); +function readFile(filename, callback) { + fs.readFile(filename, callback) +} + +// you can use a ternary to determine which module to require +var logger = DEBUG ? require('dev-logger') : require('logger'); + +// if you want you can require() at the end of your module +function doSomethingA() {} +function doSomethingB() {} +var x = require("x"), + z = require("z"); +``` + +## πŸ”Ž Implementation + +- [Rule source](../../lib/rules/global-require.js) +- [Test source](../../tests/lib/rules/global-require.js) diff --git a/docs/rules/handle-callback-err.md b/docs/rules/handle-callback-err.md new file mode 100644 index 00000000..71673557 --- /dev/null +++ b/docs/rules/handle-callback-err.md @@ -0,0 +1,78 @@ +# node/handle-callback-err +> require error handling in callbacks + +In Node.js, a common pattern for dealing with asynchronous behavior is called the callback pattern. +This pattern expects an `Error` object or `null` as the first argument of the callback. +Forgetting to handle these errors can lead to some really strange behavior in your application. + +```js +function loadData (err, data) { + doSomething(); // forgot to handle error +} +``` + +## πŸ“– Rule Details + +This rule expects that when you're using the callback pattern in Node.js you'll handle the error. + +### Options + +The rule takes a single string option: the name of the error parameter. The default is `"err"`. + +Examples of **incorrect** code for this rule with the default `"err"` parameter name: + +```js +/*eslint handle-callback-err: "error"*/ + +function loadData (err, data) { + doSomething(); +} + +``` + +Examples of **correct** code for this rule with the default `"err"` parameter name: + +```js +/*eslint handle-callback-err: "error"*/ + +function loadData (err, data) { + if (err) { + console.log(err.stack); + } + doSomething(); +} + +function generateError (err) { + if (err) {} +} +``` + +Examples of **correct** code for this rule with a sample `"error"` parameter name: + +```js +/*eslint handle-callback-err: ["error", "error"]*/ + +function loadData (error, data) { + if (error) { + console.log(error.stack); + } + doSomething(); +} +``` + +#### Regular Expression + +Sometimes (especially in big projects) the name of the error variable is not consistent across the project, +so you need a more flexible configuration to ensure that the rule reports all unhandled errors. + +If the configured name of the error variable begins with a `^` it is considered to be a regexp pattern. + +* If the option is `"^(err|error|anySpecificError)$"`, the rule reports unhandled errors where the parameter name can be `err`, `error` or `anySpecificError`. +* If the option is `"^.+Error$"`, the rule reports unhandled errors where the parameter name ends with `Error` (for example, `connectionError` or `validationError` will match). +* If the option is `"^.*(e|E)rr"`, the rule reports unhandled errors where the parameter name matches any string that contains `err` or `Err` (for example, `err`, `error`, `anyError`, `some_err` will match). + + +## πŸ”Ž Implementation + +- [Rule source](../../lib/rules/handle-callback-err.js) +- [Test source](../../tests/lib/rules/handle-callback-err.js) diff --git a/docs/rules/no-mixed-requires.md b/docs/rules/no-mixed-requires.md new file mode 100644 index 00000000..a445804b --- /dev/null +++ b/docs/rules/no-mixed-requires.md @@ -0,0 +1,121 @@ +# node/no-mixed-requires +> disallow `require` calls to be mixed with regular variable declarations + +In the Node.js community it is often customary to separate initializations with calls to `require` modules from other variable declarations, sometimes also grouping them by the type of module. This rule helps you enforce this convention. + +## πŸ“– Rule Details + +When this rule is enabled, each `var` statement must satisfy the following conditions: + +* either none or all variable declarations must be require declarations (default) +* all require declarations must be of the same type (grouping) + +This rule distinguishes between six kinds of variable declaration types: + +* `core`: declaration of a required [core module][1] +* `file`: declaration of a required [file module][2] +* `module`: declaration of a required module from the [node_modules folder][3] +* `computed`: declaration of a required module whose type could not be determined (either because it is computed or because require was called without an argument) +* `uninitialized`: a declaration that is not initialized +* `other`: any other kind of declaration + +In this document, the first four types are summed up under the term *require declaration*. + +```js +var fs = require('fs'), // "core" \ + async = require('async'), // "module" |- these are "require declaration"s + foo = require('./foo'), // "file" | + bar = require(getName()), // "computed" / + baz = 42, // "other" + bam; // "uninitialized" +``` + +### Options + +This rule can have an object literal option whose two properties have `false` values by default. + +Configuring this rule with one boolean option `true` is deprecated. + +Examples of **incorrect** code for this rule with the default `{ "grouping": false, "allowCall": false }` options: + +```js +/*eslint no-mixed-requires: "error"*/ + +var fs = require('fs'), + i = 0; + +var async = require('async'), + debug = require('diagnostics').someFunction('my-module'), + eslint = require('eslint'); +``` + +Examples of **correct** code for this rule with the default `{ "grouping": false, "allowCall": false }` options: + +```js +/*eslint no-mixed-requires: "error"*/ + +// only require declarations (grouping off) +var eventEmitter = require('events').EventEmitter, + myUtils = require('./utils'), + util = require('util'), + bar = require(getBarModuleName()); + +// only non-require declarations +var foo = 42, + bar = 'baz'; + +// always valid regardless of grouping because all declarations are of the same type +var foo = require('foo' + VERSION), + bar = require(getBarModuleName()), + baz = require(); +``` + +#### grouping + +Examples of **incorrect** code for this rule with the `{ "grouping": true }` option: + +```js +/*eslint no-mixed-requires: ["error", { "grouping": true }]*/ + +// invalid because of mixed types "core" and "module" +var fs = require('fs'), + async = require('async'); + +// invalid because of mixed types "file" and "unknown" +var foo = require('foo'), + bar = require(getBarModuleName()); +``` + +#### allowCall + +Examples of **incorrect** code for this rule with the `{ "allowCall": true }` option: + +```js +/*eslint no-mixed-requires: ["error", { "allowCall": true }]*/ + +var async = require('async'), + debug = require('diagnostics').someFunction('my-module'), /* allowCall doesn't allow calling any function */ + eslint = require('eslint'); +``` + +Examples of **correct** code for this rule with the `{ "allowCall": true }` option: + +```js +/*eslint no-mixed-requires: ["error", { "allowCall": true }]*/ + +var async = require('async'), + debug = require('diagnostics')('my-module'), + eslint = require('eslint'); +``` + +### Known Limitations + +* The implementation is not aware of any local functions with the name `require` that may shadow Node.js' global `require`. + +* Internally, the list of core modules is retrieved via `require("repl")._builtinLibs`. If you use different versions of Node.js for ESLint and your application, the list of core modules for each version may be different. + The above mentioned `_builtinLibs` property became available in 0.8, for earlier versions a hardcoded list of module names is used as a fallback. If your version of Node.js is older than 0.6 that list may be inaccurate. + +## πŸ”Ž Implementation + +- [Rule source](../../lib/rules/no-mixed-requires.js) +- [Test source](../../tests/lib/rules/no-mixed-requires.js) diff --git a/docs/rules/no-new-require.md b/docs/rules/no-new-require.md new file mode 100644 index 00000000..9fee50a0 --- /dev/null +++ b/docs/rules/no-new-require.md @@ -0,0 +1,48 @@ +# node/no-new-require +> disallow `new` operators with calls to `require` + +The `require` function is used to include modules that exist in separate files, such as: + +```js +var appHeader = require('app-header'); +``` + +Some modules return a constructor which can potentially lead to code such as: + +```js +var appHeader = new require('app-header'); +``` + +Unfortunately, this introduces a high potential for confusion since the code author likely meant to write: + +```js +var appHeader = new (require('app-header')); +``` + +For this reason, it is usually best to disallow this particular expression. + +## πŸ“– Rule Details + +This rule aims to eliminate use of the `new require` expression. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-new-require: "error"*/ + +var appHeader = new require('app-header'); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-new-require: "error"*/ + +var AppHeader = require('app-header'); +var appHeader = new AppHeader(); +``` + +## πŸ”Ž Implementation + +- [Rule source](../../lib/rules/no-new-require.js) +- [Test source](../../tests/lib/rules/no-new-require.js) diff --git a/docs/rules/no-path-concat.md b/docs/rules/no-path-concat.md new file mode 100644 index 00000000..621e600e --- /dev/null +++ b/docs/rules/no-path-concat.md @@ -0,0 +1,52 @@ +# node/no-path-concat +> disallow string concatenation with `__dirname` and `__filename` + +In Node.js, the `__dirname` and `__filename` global variables contain the directory path and the file path of the currently executing script file, respectively. Sometimes, developers try to use these variables to create paths to other files, such as: + +```js +var fullPath = __dirname + "/foo.js"; +``` + +However, there are a few problems with this. First, you can't be sure what type of system the script is running on. Node.js can be run on any computer, including Windows, which uses a different path separator. It's very easy, therefore, to create an invalid path using string concatenation and assuming Unix-style separators. There's also the possibility of having double separators, or otherwise ending up with an invalid path. + +In order to avoid any confusion as to how to create the correct path, Node.js provides the `path` module. This module uses system-specific information to always return the correct value. So you can rewrite the previous example as: + +```js +var fullPath = path.join(__dirname, "foo.js"); +``` + +This example doesn't need to include separators as `path.join()` will do it in the most appropriate manner. Alternately, you can use `path.resolve()` to retrieve the fully-qualified path: + +```js +var fullPath = path.resolve(__dirname, "foo.js"); +``` + +Both `path.join()` and `path.resolve()` are suitable replacements for string concatenation wherever file or directory paths are being created. + +## πŸ“– Rule Details + +This rule aims to prevent string concatenation of directory paths in Node.js + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-path-concat: "error"*/ + +var fullPath = __dirname + "/foo.js"; + +var fullPath = __filename + "/foo.js"; + +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-path-concat: "error"*/ + +var fullPath = dirname + "/foo.js"; +``` + +## πŸ”Ž Implementation + +- [Rule source](../../lib/rules/no-path-concat.js) +- [Test source](../../tests/lib/rules/no-path-concat.js) diff --git a/docs/rules/no-process-env.md b/docs/rules/no-process-env.md new file mode 100644 index 00000000..4e004001 --- /dev/null +++ b/docs/rules/no-process-env.md @@ -0,0 +1,35 @@ +# node/no-process-env +> disallow the use of `process.env` + +The `process.env` object in Node.js is used to store deployment/configuration parameters. Littering it through out a project could lead to maintenance issues as it's another kind of global dependency. As such, it could lead to merge conflicts in a multi-user setup and deployment issues in a multi-server setup. Instead, one of the best practices is to define all those parameters in a single configuration/settings file which could be accessed throughout the project. + +## πŸ“– Rule Details + +This rule is aimed at discouraging use of `process.env` to avoid global dependencies. As such, it will warn whenever `process.env` is used. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-process-env: "error"*/ + +if(process.env.NODE_ENV === "development") { + //... +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-process-env: "error"*/ + +var config = require("./config"); + +if(config.env === "development") { + //... +} +``` + +## πŸ”Ž Implementation + +- [Rule source](../../lib/rules/no-process-env.js) +- [Test source](../../tests/lib/rules/no-process-env.js) diff --git a/docs/rules/no-process-exit.md b/docs/rules/no-process-exit.md new file mode 100644 index 00000000..047bc597 --- /dev/null +++ b/docs/rules/no-process-exit.md @@ -0,0 +1,50 @@ +# node/no-process-exit +> disallow the use of `process.exit()` + +The `process.exit()` method in Node.js is used to immediately stop the Node.js process and exit. This is a dangerous operation because it can occur in any method at any point in time, potentially stopping a Node.js application completely when an error occurs. For example: + +```js +if (somethingBadHappened) { + console.error("Something bad happened!"); + process.exit(1); +} +``` + +This code could appear in any module and will stop the entire application when `somethingBadHappened` is truthy. This doesn't give the application any chance to respond to the error. It's usually better to throw an error and allow the application to handle it appropriately: + +```js +if (somethingBadHappened) { + throw new Error("Something bad happened!"); +} +``` + +By throwing an error in this way, other parts of the application have an opportunity to handle the error rather than stopping the application altogether. If the error bubbles all the way up to the process without being handled, then the process will exit and a non-zero exit code will returned, so the end result is the same. + +If you are using `process.exit()` only for specifying the exit code, you can set [`process.exitCode`](https://nodejs.org/api/process.html#process_process_exitcode) (introduced in Node.js 0.11.8) instead. + +## πŸ“– Rule Details + +This rule aims to prevent the use of `process.exit()` in Node.js JavaScript. As such, it warns whenever `process.exit()` is found in code. + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-process-exit: "error"*/ + +process.exit(1); +process.exit(0); +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-process-exit: "error"*/ + +Process.exit(); +var exit = process.exit; +``` + +## πŸ”Ž Implementation + +- [Rule source](../../lib/rules/no-process-exit.js) +- [Test source](../../tests/lib/rules/no-process-exit.js) diff --git a/docs/rules/no-restricted-require.md b/docs/rules/no-restricted-require.md new file mode 100644 index 00000000..ea30f80d --- /dev/null +++ b/docs/rules/no-restricted-require.md @@ -0,0 +1,112 @@ +# node/no-restricted-require +> disallow specified modules when loaded by `require` + +A module in Node.js is a simple or complex functionality organized in a JavaScript file which can be reused throughout the Node.js +application. The keyword `require` is used in Node.js/CommonJS to import modules into an application. This way you can have dynamic loading where the loaded module name isn't predefined /static, or where you conditionally load a module only if it's "truly required". + +Why would you want to restrict a module? + +Disallowing usage of specific Node.js modules can be useful if you want to limit the available methods a developer can use. For example, you can block usage of the `fs` module if you want to disallow file system access. + +## πŸ“– Rule Details + +This rule allows you to specify modules that you don’t want to use in your application. + +### Options + +The rule takes one or more strings as options: the names of restricted modules. + +```json +"no-restricted-require": ["error", "foo-module", "bar-module"] +``` + +It can also take an object with lists of `paths` and gitignore-style `patterns` strings. + +```json +"no-restricted-require": ["error", { "paths": ["foo-module", "bar-module"] }] +``` + +```json +"no-restricted-require": ["error", { + "paths": ["foo-module", "bar-module"], + "patterns": ["foo-module/private/*", "bar-module/*","!baz-module/good"] +}] +``` + +You may also specify a custom message for any paths you want to restrict as follows: + +```json +"no-restricted-require": ["error", { + "name": "foo-module", + "message": "Please use bar-module instead." + } +] +``` + +or like this: + +```json +"no-restricted-require": ["error",{ +"paths":[{ + "name": "foo-module", + "message": "Please use bar-module instead." + }] +}] +``` + +The custom message will be appended to the default error message. Please note that you may not specify custom error messages for restricted patterns as a particular module may match more than one pattern. + + +To restrict the use of all Node.js core modules (via https://github.com/nodejs/node/tree/master/lib): + +```json +{ + "no-restricted-require": ["error", + "assert","buffer","child_process","cluster","crypto","dgram","dns","domain","events","freelist","fs","http","https","module","net","os","path","punycode","querystring","readline","repl","smalloc","stream","string_decoder","sys","timers","tls","tracing","tty","url","util","vm","zlib" + ] +} +``` + +Examples of **incorrect** code for this rule with sample `"fs", "cluster", "lodash"` restricted modules: + +```js +/*eslint no-restricted-require: ["error", "fs", "cluster"]*/ + +var fs = require('fs'); +var cluster = require('cluster'); +``` + +```js +/*eslint no-restricted-require: ["error", {"paths": ["cluster"] }]*/ + +var cluster = require('cluster'); +``` + +```js +/*eslint no-restricted-require: ["error", { "patterns": ["lodash/*"] }]*/ + +var pick = require('lodash/pick'); +``` + +Examples of **correct** code for this rule with sample `"fs", "cluster", "lodash"` restricted modules: + +```js +/*eslint no-restricted-require: ["error", "fs", "cluster"]*/ + +var crypto = require('crypto'); +``` + +```js +/*eslint no-restricted-require: ["error", { + "paths": ["fs", "cluster"], + "patterns": ["lodash/*", "!lodash/pick"] +}]*/ + +var crypto = require('crypto'); +var pick = require('lodash/pick'); +``` + +## πŸ”Ž Implementation + +- [Rule source](../../lib/rules/no-restricted-require.js) +- [Test source](../../tests/lib/rules/no-restricted-require.js) diff --git a/docs/rules/no-sync.md b/docs/rules/no-sync.md new file mode 100644 index 00000000..60052171 --- /dev/null +++ b/docs/rules/no-sync.md @@ -0,0 +1,61 @@ +# node/no-sync +> disallow synchronous methods + +In Node.js, most I/O is done through asynchronous methods. However, there are often synchronous versions of the asynchronous methods. For example, `fs.exists()` and `fs.existsSync()`. In some contexts, using synchronous operations is okay (if, as with ESLint, you are writing a command line utility). However, in other contexts the use of synchronous operations is considered a bad practice that should be avoided. For example, if you are running a high-travel web server on Node.js, you should consider carefully if you want to allow any synchronous operations that could lock up the server. + +## πŸ“– Rule Details + +This rule is aimed at preventing synchronous methods from being called in Node.js. It looks specifically for the method suffix "`Sync`" (as is the convention with Node.js operations). + +### Options + +This rule has an optional object option `{ allowAtRootLevel: }`, which determines whether synchronous methods should be allowed at the top level of a file, outside of any functions. This option defaults to `false`. + +Examples of **incorrect** code for this rule with the default `{ allowAtRootLevel: false }` option: + +```js +/*eslint no-sync: "error"*/ + +fs.existsSync(somePath); + +function foo() { + var contents = fs.readFileSync(somePath).toString(); +} +``` + +Examples of **correct** code for this rule with the default `{ allowAtRootLevel: false }` option: + +```js +/*eslint no-sync: "error"*/ + +obj.sync(); + +async(function() { + // ... +}); +``` + +Examples of **incorrect** code for this rule with the `{ allowAtRootLevel: true }` option + +```js +/*eslint no-sync: ["error", { allowAtRootLevel: true }]*/ + +function foo() { + var contents = fs.readFileSync(somePath).toString(); +} + +var bar = baz => fs.readFileSync(qux); +``` + +Examples of **correct** code for this rule with the `{ allowAtRootLevel: true }` option + +```js +/*eslint no-sync: ["error", { allowAtRootLevel: true }]*/ + +fs.readFileSync(somePath).toString(); +``` + +## πŸ”Ž Implementation + +- [Rule source](../../lib/rules/no-sync.js) +- [Test source](../../tests/lib/rules/no-sync.js) diff --git a/lib/index.js b/lib/index.js index 2d782b91..8385c5a7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,8 +10,11 @@ module.exports = { }, }, rules: { + "callback-return": require("./rules/callback-return"), "exports-style": require("./rules/exports-style"), "file-extension-in-import": require("./rules/file-extension-in-import"), + "global-require": require("./rules/global-require"), + "handle-callback-err": require("./rules/handle-callback-err"), "no-callback-literal": require("./rules/no-callback-literal"), "no-deprecated-api": require("./rules/no-deprecated-api"), "no-exports-assign": require("./rules/no-exports-assign"), @@ -19,6 +22,13 @@ module.exports = { "no-extraneous-require": require("./rules/no-extraneous-require"), "no-missing-import": require("./rules/no-missing-import"), "no-missing-require": require("./rules/no-missing-require"), + "no-mixed-requires": require("./rules/no-mixed-requires"), + "no-new-require": require("./rules/no-new-require"), + "no-path-concat": require("./rules/no-path-concat"), + "no-process-env": require("./rules/no-process-env"), + "no-process-exit": require("./rules/no-process-exit"), + "no-restricted-require": require("./rules/no-restricted-require"), + "no-sync": require("./rules/no-sync"), "no-unpublished-bin": require("./rules/no-unpublished-bin"), "no-unpublished-import": require("./rules/no-unpublished-import"), "no-unpublished-require": require("./rules/no-unpublished-require"), diff --git a/lib/rules/callback-return.js b/lib/rules/callback-return.js new file mode 100644 index 00000000..baea69a8 --- /dev/null +++ b/lib/rules/callback-return.js @@ -0,0 +1,185 @@ +/** + * @author Jamund Ferguson + * See LICENSE file in root directory for full license. + */ +"use strict" + +module.exports = { + meta: { + type: "suggestion", + docs: { + description: "require `return` statements after callbacks", + category: "Stylistic Issues", + recommended: false, + url: + "https://github.com/mysticatea/eslint-plugin-node/blob/v11.0.0/docs/rules/callback-return.md", + }, + schema: [ + { + type: "array", + items: { type: "string" }, + }, + ], + fixable: null, + messages: { + missingReturn: "Expected return with your callback function.", + }, + }, + + create(context) { + const callbacks = context.options[0] || ["callback", "cb", "next"] + const sourceCode = context.getSourceCode() + + /** + * Find the closest parent matching a list of types. + * @param {ASTNode} node The node whose parents we are searching + * @param {Array} types The node types to match + * @returns {ASTNode} The matched node or undefined. + */ + function findClosestParentOfType(node, types) { + if (!node.parent) { + return null + } + if (types.indexOf(node.parent.type) === -1) { + return findClosestParentOfType(node.parent, types) + } + return node.parent + } + + /** + * Check to see if a node contains only identifers + * @param {ASTNode} node The node to check + * @returns {boolean} Whether or not the node contains only identifers + */ + function containsOnlyIdentifiers(node) { + if (node.type === "Identifier") { + return true + } + + if (node.type === "MemberExpression") { + if (node.object.type === "Identifier") { + return true + } + if (node.object.type === "MemberExpression") { + return containsOnlyIdentifiers(node.object) + } + } + + return false + } + + /** + * Check to see if a CallExpression is in our callback list. + * @param {ASTNode} node The node to check against our callback names list. + * @returns {boolean} Whether or not this function matches our callback name. + */ + function isCallback(node) { + return ( + containsOnlyIdentifiers(node.callee) && + callbacks.indexOf(sourceCode.getText(node.callee)) > -1 + ) + } + + /** + * Determines whether or not the callback is part of a callback expression. + * @param {ASTNode} node The callback node + * @param {ASTNode} parentNode The expression node + * @returns {boolean} Whether or not this is part of a callback expression + */ + function isCallbackExpression(node, parentNode) { + // ensure the parent node exists and is an expression + if (!parentNode || parentNode.type !== "ExpressionStatement") { + return false + } + + // cb() + if (parentNode.expression === node) { + return true + } + + // special case for cb && cb() and similar + if ( + parentNode.expression.type === "BinaryExpression" || + parentNode.expression.type === "LogicalExpression" + ) { + if (parentNode.expression.right === node) { + return true + } + } + + return false + } + + return { + CallExpression(node) { + // if we're not a callback we can return + if (!isCallback(node)) { + return + } + + // find the closest block, return or loop + const closestBlock = + findClosestParentOfType(node, [ + "BlockStatement", + "ReturnStatement", + "ArrowFunctionExpression", + ]) || {} + + // if our parent is a return we know we're ok + if (closestBlock.type === "ReturnStatement") { + return + } + + // arrow functions don't always have blocks and implicitly return + if (closestBlock.type === "ArrowFunctionExpression") { + return + } + + // block statements are part of functions and most if statements + if (closestBlock.type === "BlockStatement") { + // find the last item in the block + const lastItem = + closestBlock.body[closestBlock.body.length - 1] + + // if the callback is the last thing in a block that might be ok + if (isCallbackExpression(node, lastItem)) { + const parentType = closestBlock.parent.type + + // but only if the block is part of a function + if ( + parentType === "FunctionExpression" || + parentType === "FunctionDeclaration" || + parentType === "ArrowFunctionExpression" + ) { + return + } + } + + // ending a block with a return is also ok + if (lastItem.type === "ReturnStatement") { + // but only if the callback is immediately before + if ( + isCallbackExpression( + node, + closestBlock.body[closestBlock.body.length - 2] + ) + ) { + return + } + } + } + + // as long as you're the child of a function at this point you should be asked to return + if ( + findClosestParentOfType(node, [ + "FunctionDeclaration", + "FunctionExpression", + "ArrowFunctionExpression", + ]) + ) { + context.report({ node, messageId: "missingReturn" }) + } + }, + } + }, +} diff --git a/lib/rules/global-require.js b/lib/rules/global-require.js new file mode 100644 index 00000000..dfe830e7 --- /dev/null +++ b/lib/rules/global-require.js @@ -0,0 +1,91 @@ +/** + * @author Jamund Ferguson + * See LICENSE file in root directory for full license. + */ +"use strict" + +const ACCEPTABLE_PARENTS = [ + "AssignmentExpression", + "VariableDeclarator", + "MemberExpression", + "ExpressionStatement", + "CallExpression", + "ConditionalExpression", + "Program", + "VariableDeclaration", +] + +/** + * Finds the eslint-scope reference in the given scope. + * @param {Object} scope The scope to search. + * @param {ASTNode} node The identifier node. + * @returns {Reference|null} Returns the found reference or null if none were found. + */ +function findReference(scope, node) { + const references = scope.references.filter( + reference => + reference.identifier.range[0] === node.range[0] && + reference.identifier.range[1] === node.range[1] + ) + + /* istanbul ignore else: correctly returns null */ + if (references.length === 1) { + return references[0] + } + return null +} + +/** + * Checks if the given identifier node is shadowed in the given scope. + * @param {Object} scope The current scope. + * @param {ASTNode} node The identifier node to check. + * @returns {boolean} Whether or not the name is shadowed. + */ +function isShadowed(scope, node) { + const reference = findReference(scope, node) + + return reference && reference.resolved && reference.resolved.defs.length > 0 +} + +module.exports = { + meta: { + type: "suggestion", + docs: { + description: + "require `require()` calls to be placed at top-level module scope", + category: "Stylistic Issues", + recommended: false, + url: + "https://github.com/mysticatea/eslint-plugin-node/blob/v11.0.0/docs/rules/global-require.md", + }, + fixable: null, + schema: [], + messages: { + unexpected: "Unexpected require().", + }, + }, + + create(context) { + return { + CallExpression(node) { + const currentScope = context.getScope() + + if ( + node.callee.name === "require" && + !isShadowed(currentScope, node.callee) + ) { + const isGoodRequire = context + .getAncestors() + .every( + parent => + ACCEPTABLE_PARENTS.indexOf(parent.type) > -1 + ) + + if (!isGoodRequire) { + context.report({ node, messageId: "unexpected" }) + } + } + }, + } + }, +} diff --git a/lib/rules/handle-callback-err.js b/lib/rules/handle-callback-err.js new file mode 100644 index 00000000..bf3b5235 --- /dev/null +++ b/lib/rules/handle-callback-err.js @@ -0,0 +1,94 @@ +/** + * @author Jamund Ferguson + * See LICENSE file in root directory for full license. + */ +"use strict" + +module.exports = { + meta: { + type: "suggestion", + docs: { + description: "require error handling in callbacks", + category: "Possible Errors", + recommended: false, + url: + "https://github.com/mysticatea/eslint-plugin-node/blob/v11.0.0/docs/rules/handle-callback-err.md", + }, + fixable: null, + schema: [ + { + type: "string", + }, + ], + messages: { + expected: "Expected error to be handled.", + }, + }, + + create(context) { + const errorArgument = context.options[0] || "err" + + /** + * Checks if the given argument should be interpreted as a regexp pattern. + * @param {string} stringToCheck The string which should be checked. + * @returns {boolean} Whether or not the string should be interpreted as a pattern. + */ + function isPattern(stringToCheck) { + const firstChar = stringToCheck[0] + + return firstChar === "^" + } + + /** + * Checks if the given name matches the configured error argument. + * @param {string} name The name which should be compared. + * @returns {boolean} Whether or not the given name matches the configured error variable name. + */ + function matchesConfiguredErrorName(name) { + if (isPattern(errorArgument)) { + const regexp = new RegExp(errorArgument, "u") + + return regexp.test(name) + } + return name === errorArgument + } + + /** + * Get the parameters of a given function scope. + * @param {Object} scope The function scope. + * @returns {Array} All parameters of the given scope. + */ + function getParameters(scope) { + return scope.variables.filter( + variable => + variable.defs[0] && variable.defs[0].type === "Parameter" + ) + } + + /** + * Check to see if we're handling the error object properly. + * @param {ASTNode} node The AST node to check. + * @returns {void} + */ + function checkForError(node) { + const scope = context.getScope() + const parameters = getParameters(scope) + const firstParameter = parameters[0] + + if ( + firstParameter && + matchesConfiguredErrorName(firstParameter.name) + ) { + if (firstParameter.references.length === 0) { + context.report({ node, messageId: "expected" }) + } + } + } + + return { + FunctionDeclaration: checkForError, + FunctionExpression: checkForError, + ArrowFunctionExpression: checkForError, + } + }, +} diff --git a/lib/rules/no-mixed-requires.js b/lib/rules/no-mixed-requires.js new file mode 100644 index 00000000..308bdaa3 --- /dev/null +++ b/lib/rules/no-mixed-requires.js @@ -0,0 +1,255 @@ +/** + * @author Raphael Pigulla + * See LICENSE file in root directory for full license. + */ +"use strict" + +// This list is generated using: +// `require("module").builtinModules` +// +// This was last updated using Node v13.8.0. +const BUILTIN_MODULES = [ + "_http_agent", + "_http_client", + "_http_common", + "_http_incoming", + "_http_outgoing", + "_http_server", + "_stream_duplex", + "_stream_passthrough", + "_stream_readable", + "_stream_transform", + "_stream_wrap", + "_stream_writable", + "_tls_common", + "_tls_wrap", + "assert", + "async_hooks", + "buffer", + "child_process", + "cluster", + "console", + "constants", + "crypto", + "dgram", + "dns", + "domain", + "events", + "fs", + "http", + "http2", + "https", + "inspector", + "module", + "net", + "os", + "path", + "perf_hooks", + "process", + "punycode", + "querystring", + "readline", + "repl", + "stream", + "string_decoder", + "sys", + "timers", + "tls", + "trace_events", + "tty", + "url", + "util", + "v8", + "vm", + "worker_threads", + "zlib", +] + +module.exports = { + meta: { + type: "suggestion", + docs: { + description: + "disallow `require` calls to be mixed with regular variable declarations", + category: "Stylistic Issues", + recommended: false, + url: + "https://github.com/mysticatea/eslint-plugin-node/blob/v11.0.0/docs/rules/no-mixed-requires.md", + }, + fixable: null, + schema: [ + { + oneOf: [ + { + type: "boolean", + }, + { + type: "object", + properties: { + grouping: { + type: "boolean", + }, + allowCall: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + }, + ], + messages: { + noMixRequire: "Do not mix 'require' and other declarations.", + noMixCoreModuleFileComputed: + "Do not mix core, module, file and computed requires.", + }, + }, + + create(context) { + const options = context.options[0] + let grouping = false + let allowCall = false + + if (typeof options === "object") { + grouping = options.grouping + allowCall = options.allowCall + } else { + grouping = Boolean(options) + } + + const DECL_REQUIRE = "require" + const DECL_UNINITIALIZED = "uninitialized" + const DECL_OTHER = "other" + + const REQ_CORE = "core" + const REQ_FILE = "file" + const REQ_MODULE = "module" + const REQ_COMPUTED = "computed" + + /** + * Determines the type of a declaration statement. + * @param {ASTNode} initExpression The init node of the VariableDeclarator. + * @returns {string} The type of declaration represented by the expression. + */ + function getDeclarationType(initExpression) { + if (!initExpression) { + // "var x;" + return DECL_UNINITIALIZED + } + + if ( + initExpression.type === "CallExpression" && + initExpression.callee.type === "Identifier" && + initExpression.callee.name === "require" + ) { + // "var x = require('util');" + return DECL_REQUIRE + } + if ( + allowCall && + initExpression.type === "CallExpression" && + initExpression.callee.type === "CallExpression" + ) { + // "var x = require('diagnose')('sub-module');" + return getDeclarationType(initExpression.callee) + } + if (initExpression.type === "MemberExpression") { + // "var x = require('glob').Glob;" + return getDeclarationType(initExpression.object) + } + + // "var x = 42;" + return DECL_OTHER + } + + /** + * Determines the type of module that is loaded via require. + * @param {ASTNode} initExpression The init node of the VariableDeclarator. + * @returns {string} The module type. + */ + function inferModuleType(initExpression) { + if (initExpression.type === "MemberExpression") { + // "var x = require('glob').Glob;" + return inferModuleType(initExpression.object) + } + if (initExpression.arguments.length === 0) { + // "var x = require();" + return REQ_COMPUTED + } + + const arg = initExpression.arguments[0] + + if (arg.type !== "Literal" || typeof arg.value !== "string") { + // "var x = require(42);" + return REQ_COMPUTED + } + + if (BUILTIN_MODULES.indexOf(arg.value) !== -1) { + // "var fs = require('fs');" + return REQ_CORE + } + if (/^\.{0,2}\//u.test(arg.value)) { + // "var utils = require('./utils');" + return REQ_FILE + } + + // "var async = require('async');" + return REQ_MODULE + } + + /** + * Check if the list of variable declarations is mixed, i.e. whether it + * contains both require and other declarations. + * @param {ASTNode} declarations The list of VariableDeclarators. + * @returns {boolean} True if the declarations are mixed, false if not. + */ + function isMixed(declarations) { + const contains = {} + + for (const declaration of declarations) { + const type = getDeclarationType(declaration.init) + + contains[type] = true + } + + return Boolean( + contains[DECL_REQUIRE] && + (contains[DECL_UNINITIALIZED] || contains[DECL_OTHER]) + ) + } + + /** + * Check if all require declarations in the given list are of the same + * type. + * @param {ASTNode} declarations The list of VariableDeclarators. + * @returns {boolean} True if the declarations are grouped, false if not. + */ + function isGrouped(declarations) { + const found = {} + + for (const declaration of declarations) { + if (getDeclarationType(declaration.init) === DECL_REQUIRE) { + found[inferModuleType(declaration.init)] = true + } + } + + return Object.keys(found).length <= 1 + } + + return { + VariableDeclaration(node) { + if (isMixed(node.declarations)) { + context.report({ + node, + messageId: "noMixRequire", + }) + } else if (grouping && !isGrouped(node.declarations)) { + context.report({ + node, + messageId: "noMixCoreModuleFileComputed", + }) + } + }, + } + }, +} diff --git a/lib/rules/no-new-require.js b/lib/rules/no-new-require.js new file mode 100644 index 00000000..936fa910 --- /dev/null +++ b/lib/rules/no-new-require.js @@ -0,0 +1,39 @@ +/** + * @author Wil Moore III + * See LICENSE file in root directory for full license. + */ +"use strict" + +module.exports = { + meta: { + type: "suggestion", + docs: { + description: "disallow `new` operators with calls to `require`", + category: "Possible Errors", + recommended: false, + url: + "https://github.com/mysticatea/eslint-plugin-node/blob/v11.0.0/docs/rules/no-new-require.md", + }, + fixable: null, + schema: [], + messages: { + noNewRequire: "Unexpected use of new with require.", + }, + }, + + create(context) { + return { + NewExpression(node) { + if ( + node.callee.type === "Identifier" && + node.callee.name === "require" + ) { + context.report({ + node, + messageId: "noNewRequire", + }) + } + }, + } + }, +} diff --git a/lib/rules/no-path-concat.js b/lib/rules/no-path-concat.js new file mode 100644 index 00000000..32555bdb --- /dev/null +++ b/lib/rules/no-path-concat.js @@ -0,0 +1,48 @@ +/** + * @author Nicholas C. Zakas + * See LICENSE file in root directory for full license. + */ +"use strict" + +module.exports = { + meta: { + type: "suggestion", + docs: { + description: + "disallow string concatenation with `__dirname` and `__filename`", + category: "Possible Errors", + recommended: false, + url: + "https://github.com/mysticatea/eslint-plugin-node/blob/v11.0.0/docs/rules/no-path-concat.md", + }, + fixable: null, + schema: [], + messages: { + usePathFunctions: + "Use path.join() or path.resolve() instead of + to create paths.", + }, + }, + + create(context) { + const MATCHER = /^__(?:dir|file)name$/u + + return { + BinaryExpression(node) { + const left = node.left + const right = node.right + + if ( + node.operator === "+" && + ((left.type === "Identifier" && MATCHER.test(left.name)) || + (right.type === "Identifier" && + MATCHER.test(right.name))) + ) { + context.report({ + node, + messageId: "usePathFunctions", + }) + } + }, + } + }, +} diff --git a/lib/rules/no-process-env.js b/lib/rules/no-process-env.js new file mode 100644 index 00000000..c804e649 --- /dev/null +++ b/lib/rules/no-process-env.js @@ -0,0 +1,45 @@ +/** + * @author Vignesh Anand + * See LICENSE file in root directory for full license. + */ +"use strict" + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "suggestion", + docs: { + description: "disallow the use of `process.env`", + category: "Stylistic Issues", + recommended: false, + url: + "https://github.com/mysticatea/eslint-plugin-node/blob/v11.0.0/docs/rules/no-process-env.md", + }, + fixable: null, + schema: [], + messages: { + unexpectedProcessEnv: "Unexpected use of process.env.", + }, + }, + + create(context) { + return { + MemberExpression(node) { + const objectName = node.object.name + const propertyName = node.property.name + + if ( + objectName === "process" && + !node.computed && + propertyName && + propertyName === "env" + ) { + context.report({ node, messageId: "unexpectedProcessEnv" }) + } + }, + } + }, +} diff --git a/lib/rules/no-process-exit.js b/lib/rules/no-process-exit.js new file mode 100644 index 00000000..58de6314 --- /dev/null +++ b/lib/rules/no-process-exit.js @@ -0,0 +1,36 @@ +/** + * @author Nicholas C. Zakas + * See LICENSE file in root directory for full license. + */ +"use strict" + +module.exports = { + meta: { + type: "suggestion", + docs: { + description: "disallow the use of `process.exit()`", + category: "Possible Errors", + recommended: false, + url: + "https://github.com/mysticatea/eslint-plugin-node/blob/v11.0.0/docs/rules/no-process-exit.md", + }, + fixable: null, + schema: [], + messages: { + noProcessExit: "Don't use process.exit(); throw an error instead.", + }, + }, + + create(context) { + return { + "CallExpression > MemberExpression.callee[object.name = 'process'][property.name = 'exit']"( + node + ) { + context.report({ + node: node.parent, + messageId: "noProcessExit", + }) + }, + } + }, +} diff --git a/lib/rules/no-restricted-require.js b/lib/rules/no-restricted-require.js new file mode 100644 index 00000000..27673bfc --- /dev/null +++ b/lib/rules/no-restricted-require.js @@ -0,0 +1,192 @@ +/** + * @author Christian Schulz + * See LICENSE file in root directory for full license. + */ +"use strict" + +const ignore = require("ignore") + +const arrayOfStrings = { + type: "array", + items: { type: "string" }, + uniqueItems: true, +} + +const arrayOfStringsOrObjects = { + type: "array", + items: { + anyOf: [ + { type: "string" }, + { + type: "object", + properties: { + name: { type: "string" }, + message: { + type: "string", + minLength: 1, + }, + }, + additionalProperties: false, + required: ["name"], + }, + ], + }, + uniqueItems: true, +} + +module.exports = { + meta: { + type: "suggestion", + docs: { + description: "disallow specified modules when loaded by `require`", + category: "Stylistic Issues", + recommended: false, + url: + "https://github.com/mysticatea/eslint-plugin-node/blob/v11.0.0/docs/rules/no-restricted-require.md", + }, + fixable: null, + schema: { + anyOf: [ + arrayOfStringsOrObjects, + { + type: "array", + items: { + type: "object", + properties: { + paths: arrayOfStringsOrObjects, + patterns: arrayOfStrings, + }, + additionalProperties: false, + }, + additionalItems: false, + }, + ], + }, + messages: { + defaultMessage: "'{{name}}' module is restricted from being used.", + customMessage: + // eslint-disable-next-line @mysticatea/eslint-plugin/report-message-format + "'{{name}}' module is restricted from being used. {{customMessage}}", + patternMessage: + "'{{name}}' module is restricted from being used by a pattern.", + }, + }, + + create(context) { + const options = Array.isArray(context.options) ? context.options : [] + const isPathAndPatternsObject = + typeof options[0] === "object" && + (Object.prototype.hasOwnProperty.call(options[0], "paths") || + Object.prototype.hasOwnProperty.call(options[0], "patterns")) + + const restrictedPaths = + (isPathAndPatternsObject ? options[0].paths : context.options) || [] + const restrictedPatterns = + (isPathAndPatternsObject ? options[0].patterns : []) || [] + + const restrictedPathMessages = restrictedPaths.reduce( + (memo, importName) => { + if (typeof importName === "string") { + memo[importName] = null + } else { + memo[importName.name] = importName.message + } + return memo + }, + {} + ) + + // if no imports are restricted we don"t need to check + if ( + Object.keys(restrictedPaths).length === 0 && + restrictedPatterns.length === 0 + ) { + return {} + } + + const ig = ignore().add(restrictedPatterns) + + /** + * Function to check if a node is a string literal. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a string literal. + */ + function isString(node) { + return ( + node && + node.type === "Literal" && + typeof node.value === "string" + ) + } + + /** + * Function to check if a node is a require call. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a require call. + */ + function isRequireCall(node) { + return ( + node.callee.type === "Identifier" && + node.callee.name === "require" + ) + } + + /** + * Report a restricted path. + * @param {node} node representing the restricted path reference + * @returns {void} + * @private + */ + function reportPath(node) { + const name = node.arguments[0].value.trim() + const customMessage = restrictedPathMessages[name] + const messageId = customMessage ? "customMessage" : "defaultMessage" + + context.report({ + node, + messageId, + data: { + name, + customMessage, + }, + }) + } + + /** + * Check if the given name is a restricted path name + * @param {string} name name of a variable + * @returns {boolean} whether the variable is a restricted path or not + * @private + */ + function isRestrictedPath(name) { + return Object.prototype.hasOwnProperty.call( + restrictedPathMessages, + name + ) + } + + return { + CallExpression(node) { + if (isRequireCall(node)) { + // node has arguments and first argument is string + if (node.arguments.length && isString(node.arguments[0])) { + const name = node.arguments[0].value.trim() + + // check if argument value is in restricted modules array + if (isRestrictedPath(name)) { + reportPath(node) + } + + if (restrictedPatterns.length > 0 && ig.ignores(name)) { + context.report({ + node, + messageId: "patternMessage", + data: { name }, + }) + } + } + } + }, + } + }, +} diff --git a/lib/rules/no-sync.js b/lib/rules/no-sync.js new file mode 100644 index 00000000..f434aa21 --- /dev/null +++ b/lib/rules/no-sync.js @@ -0,0 +1,53 @@ +/** + * @author Matt DuVall + * See LICENSE file in root directory for full license. + */ +"use strict" + +module.exports = { + meta: { + type: "suggestion", + docs: { + description: "disallow synchronous methods", + category: "Stylistic Issues", + recommended: false, + url: + "https://github.com/mysticatea/eslint-plugin-node/blob/v11.0.0/docs/rules/no-sync.md", + }, + fixable: null, + schema: [ + { + type: "object", + properties: { + allowAtRootLevel: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + messages: { + noSync: "Unexpected sync method: '{{propertyName}}'.", + }, + }, + + create(context) { + const selector = + context.options[0] && context.options[0].allowAtRootLevel + ? ":function MemberExpression[property.name=/.*Sync$/]" + : "MemberExpression[property.name=/.*Sync$/]" + + return { + [selector](node) { + context.report({ + node, + messageId: "noSync", + data: { + propertyName: node.property.name, + }, + }) + }, + } + }, +} diff --git a/tests/lib/rules/callback-return.js b/tests/lib/rules/callback-return.js new file mode 100644 index 00000000..aa764d7d --- /dev/null +++ b/tests/lib/rules/callback-return.js @@ -0,0 +1,527 @@ +/** + * @author Jamund Ferguson + * See LICENSE file in root directory for full license. + */ +"use strict" + +const RuleTester = require("eslint").RuleTester +const rule = require("../../../lib/rules/callback-return") + +new RuleTester().run("callback-return", rule, { + valid: [ + // callbacks inside of functions should return + "function a(err) { if (err) return callback (err); }", + "function a(err) { if (err) return callback (err); callback(); }", + "function a(err) { if (err) { return callback (err); } callback(); }", + "function a(err) { if (err) { return /* confusing comment */ callback (err); } callback(); }", + "function x(err) { if (err) { callback(); return; } }", + "function x(err) { if (err) { \n log();\n callback(); return; } }", + "function x(err) { if (err) { callback(); return; } return callback(); }", + "function x(err) { if (err) { return callback(); } else { return callback(); } }", + "function x(err) { if (err) { return callback(); } else if (x) { return callback(); } }", + "function x(err) { if (err) return callback(); else return callback(); }", + "function x(cb) { cb && cb(); }", + "function x(next) { typeof next !== 'undefined' && next(); }", + "function x(next) { if (typeof next === 'function') { return next() } }", + "function x() { switch(x) { case 'a': return next(); } }", + "function x() { for(x = 0; x < 10; x++) { return next(); } }", + "function x() { while(x) { return next(); } }", + "function a(err) { if (err) { obj.method (err); } }", + + // callback() all you want outside of a function + "callback()", + "callback(); callback();", + "while(x) { move(); }", + "for (var i = 0; i < 10; i++) { move(); }", + "for (var i = 0; i < 10; i++) move();", + "if (x) callback();", + "if (x) { callback(); }", + + // arrow functions + { + code: "var x = err => { if (err) { callback(); return; } }", + parserOptions: { ecmaVersion: 6 }, + }, + { + code: "var x = err => callback(err)", + parserOptions: { ecmaVersion: 6 }, + }, + { + code: "var x = err => { setTimeout( () => { callback(); }); }", + parserOptions: { ecmaVersion: 6 }, + }, + + // classes + { + code: "class x { horse() { callback(); } } ", + parserOptions: { ecmaVersion: 6 }, + }, + { + code: + "class x { horse() { if (err) { return callback(); } callback(); } } ", + parserOptions: { ecmaVersion: 6 }, + }, + + // options (only warns with the correct callback name) + { + code: "function a(err) { if (err) { callback(err) } }", + options: [["cb"]], + }, + { + code: "function a(err) { if (err) { callback(err) } next(); }", + options: [["cb", "next"]], + }, + { + code: + "function a(err) { if (err) { return next(err) } else { callback(); } }", + options: [["cb", "next"]], + }, + + // allow object methods (https://github.com/eslint/eslint/issues/4711) + { + code: "function a(err) { if (err) { return obj.method(err); } }", + options: [["obj.method"]], + }, + { + code: + "function a(err) { if (err) { return obj.prop.method(err); } }", + options: [["obj.prop.method"]], + }, + { + code: + "function a(err) { if (err) { return obj.prop.method(err); } otherObj.prop.method() }", + options: [["obj.prop.method", "otherObj.prop.method"]], + }, + { + code: "function a(err) { if (err) { callback(err); } }", + options: [["obj.method"]], + }, + { + code: "function a(err) { if (err) { otherObj.method(err); } }", + options: [["obj.method"]], + }, + { + code: + "function a(err) { if (err) { //comment\nreturn obj.method(err); } }", + options: [["obj.method"]], + }, + { + code: + "function a(err) { if (err) { /*comment*/return obj.method(err); } }", + options: [["obj.method"]], + }, + { + code: + "function a(err) { if (err) { return obj.method(err); //comment\n } }", + options: [["obj.method"]], + }, + { + code: + "function a(err) { if (err) { return obj.method(err); /*comment*/ } }", + options: [["obj.method"]], + }, + + // only warns if object of MemberExpression is an Identifier + { + code: "function a(err) { if (err) { obj().method(err); } }", + options: [["obj().method"]], + }, + { + code: "function a(err) { if (err) { obj.prop().method(err); } }", + options: [["obj.prop().method"]], + }, + { + code: "function a(err) { if (err) { obj().prop.method(err); } }", + options: [["obj().prop.method"]], + }, + + // does not warn if object of MemberExpression is invoked + { + code: "function a(err) { if (err) { obj().method(err); } }", + options: [["obj.method"]], + }, + { + code: + "function a(err) { if (err) { obj().method(err); } obj.method(); }", + options: [["obj.method"]], + }, + + // known bad examples that we know we are ignoring + "function x(err) { if (err) { setTimeout(callback, 0); } callback(); }", // callback() called twice + "function x(err) { if (err) { process.nextTick(function(err) { callback(); }); } callback(); }", // callback() called twice + ], + invalid: [ + { + code: "function a(err) { if (err) { callback (err); } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: + "function a(callback) { if (typeof callback !== 'undefined') { callback(); } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 63, + type: "CallExpression", + }, + ], + }, + { + code: + "function a(callback) { if (typeof callback !== 'undefined') callback(); }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 61, + type: "CallExpression", + }, + ], + }, + { + code: + "function a(callback) { if (err) { callback(); horse && horse(); } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 35, + type: "CallExpression", + }, + ], + }, + { + code: "var x = (err) => { if (err) { callback (err); } }", + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 31, + type: "CallExpression", + }, + ], + }, + { + code: "var x = { x(err) { if (err) { callback (err); } } }", + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 31, + type: "CallExpression", + }, + ], + }, + { + code: "function x(err) { if (err) {\n log();\n callback(err); } }", + errors: [ + { + messageId: "missingReturn", + line: 3, + column: 2, + type: "CallExpression", + }, + ], + }, + { + code: + "var x = { x(err) { if (err) { callback && callback (err); } } }", + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 43, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { callback (err); callback(); }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 19, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { callback (err); horse(); }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 19, + type: "CallExpression", + }, + ], + }, + { + code: + "function a(err) { if (err) { callback (err); horse(); return; } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: "var a = (err) => { callback (err); callback(); }", + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 20, + type: "CallExpression", + }, + ], + }, + { + code: + "function a(err) { if (err) { callback (err); } else if (x) { callback(err); return; } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: + "function x(err) { if (err) { return callback(); }\nelse if (abc) {\ncallback(); }\nelse {\nreturn callback(); } }", + errors: [ + { + messageId: "missingReturn", + line: 3, + column: 1, + type: "CallExpression", + }, + ], + }, + { + code: + "class x { horse() { if (err) { callback(); } callback(); } } ", + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 32, + type: "CallExpression", + }, + ], + }, + + // generally good behavior which we must not allow to keep the rule simple + { + code: + "function x(err) { if (err) { callback() } else { callback() } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + { + messageId: "missingReturn", + line: 1, + column: 50, + type: "CallExpression", + }, + ], + }, + { + code: + "function x(err) { if (err) return callback(); else callback(); }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 52, + type: "CallExpression", + }, + ], + }, + { + code: "() => { if (x) { callback(); } }", + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 18, + type: "CallExpression", + }, + ], + }, + { + code: "function b() { switch(x) { case 'horse': callback(); } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 42, + type: "CallExpression", + }, + ], + }, + { + code: "function a() { switch(x) { case 'horse': move(); } }", + options: [["move"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 42, + type: "CallExpression", + }, + ], + }, + + // loops + { + code: "var x = function() { while(x) { move(); } }", + options: [["move"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 33, + type: "CallExpression", + }, + ], + }, + { + code: "function x() { for (var i = 0; i < 10; i++) { move(); } }", + options: [["move"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 47, + type: "CallExpression", + }, + ], + }, + { + code: "var x = function() { for (var i = 0; i < 10; i++) move(); }", + options: [["move"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 51, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { if (err) { obj.method(err); } }", + options: [["obj.method"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { if (err) { obj.prop.method(err); } }", + options: [["obj.prop.method"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: + "function a(err) { if (err) { obj.prop.method(err); } otherObj.prop.method() }", + options: [["obj.prop.method", "otherObj.prop.method"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: + "function a(err) { if (err) { /*comment*/obj.method(err); } }", + options: [["obj.method"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 41, + type: "CallExpression", + }, + ], + }, + { + code: + "function a(err) { if (err) { //comment\nobj.method(err); } }", + options: [["obj.method"]], + errors: [ + { + messageId: "missingReturn", + line: 2, + column: 1, + type: "CallExpression", + }, + ], + }, + { + code: + "function a(err) { if (err) { obj.method(err); /*comment*/ } }", + options: [["obj.method"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: + "function a(err) { if (err) { obj.method(err); //comment\n } }", + options: [["obj.method"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + ], +}) diff --git a/tests/lib/rules/global-require.js b/tests/lib/rules/global-require.js new file mode 100644 index 00000000..bc4cc3a3 --- /dev/null +++ b/tests/lib/rules/global-require.js @@ -0,0 +1,68 @@ +/** + * @author Jamund Ferguson + * See LICENSE file in root directory for full license. + */ +"use strict" + +const RuleTester = require("eslint").RuleTester +const rule = require("../../../lib/rules/global-require") + +const ERROR = { messageId: "unexpected", type: "CallExpression" } + +new RuleTester().run("global-require", rule, { + valid: [ + "var x = require('y');", + "if (x) { x.require('y'); }", + "var x;\nx = require('y');", + "var x = 1, y = require('y');", + "var x = require('y'), y = require('y'), z = require('z');", + "var x = require('y').foo;", + "require('y').foo();", + "require('y');", + "function x(){}\n\n\nx();\n\n\nif (x > y) {\n\tdoSomething()\n\n}\n\nvar x = require('y').foo;", + "var logger = require(DEBUG ? 'dev-logger' : 'logger');", + "var logger = DEBUG ? require('dev-logger') : require('logger');", + "function localScopedRequire(require) { require('y'); }", + "var someFunc = require('./someFunc'); someFunc(function(require) { return('bananas'); });", + ], + invalid: [ + // block statements + { + code: + "if (process.env.NODE_ENV === 'DEVELOPMENT') {\n\trequire('debug');\n}", + errors: [ERROR], + }, + { + code: "var x; if (y) { x = require('debug'); }", + errors: [ERROR], + }, + { + code: "var x; if (y) { x = require('debug').baz; }", + errors: [ERROR], + }, + { + code: "function x() { require('y') }", + errors: [ERROR], + }, + { + code: "try { require('x'); } catch (e) { console.log(e); }", + errors: [ERROR], + }, + + // non-block statements + { + code: "var getModule = x => require(x);", + parserOptions: { ecmaVersion: 6 }, + errors: [ERROR], + }, + { + code: "var x = (x => require(x))('weird')", + parserOptions: { ecmaVersion: 6 }, + errors: [ERROR], + }, + { + code: "switch(x) { case '1': require('1'); break; }", + errors: [ERROR], + }, + ], +}) diff --git a/tests/lib/rules/handle-callback-err.js b/tests/lib/rules/handle-callback-err.js new file mode 100644 index 00000000..f1f1361f --- /dev/null +++ b/tests/lib/rules/handle-callback-err.js @@ -0,0 +1,154 @@ +/** + * @author Jamund Ferguson + * See LICENSE file in root directory for full license. + */ +"use strict" + +const RuleTester = require("eslint").RuleTester +const rule = require("../../../lib/rules/handle-callback-err") + +const EXPECTED_DECL_ERROR = { + messageId: "expected", + type: "FunctionDeclaration", +} +const EXPECTED_FUNC_ERROR = { + messageId: "expected", + type: "FunctionExpression", +} + +new RuleTester().run("handle-callback-err", rule, { + valid: [ + "function test(error) {}", + "function test(err) {console.log(err);}", + "function test(err, data) {if(err){ data = 'ERROR';}}", + "var test = function(err) {console.log(err);};", + "var test = function(err) {if(err){/* do nothing */}};", + "var test = function(err) {if(!err){doSomethingHere();}else{};}", + "var test = function(err, data) {if(!err) { good(); } else { bad(); }}", + "try { } catch(err) {}", + "getData(function(err, data) {if (err) {}getMoreDataWith(data, function(err, moreData) {if (err) {}getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});});});", + "var test = function(err) {if(! err){doSomethingHere();}};", + "function test(err, data) {if (data) {doSomething(function(err) {console.error(err);});} else if (err) {console.log(err);}}", + "function handler(err, data) {if (data) {doSomethingWith(data);} else if (err) {console.log(err);}}", + "function handler(err) {logThisAction(function(err) {if (err) {}}); console.log(err);}", + "function userHandler(err) {process.nextTick(function() {if (err) {}})}", + "function help() { function userHandler(err) {function tester() { err; process.nextTick(function() { err; }); } } }", + "function help(done) { var err = new Error('error'); done(); }", + { code: "var test = err => err;", parserOptions: { ecmaVersion: 6 } }, + { code: "var test = err => !err;", parserOptions: { ecmaVersion: 6 } }, + { + code: "var test = err => err.message;", + parserOptions: { ecmaVersion: 6 }, + }, + { + code: "var test = function(error) {if(error){/* do nothing */}};", + options: ["error"], + }, + { + code: "var test = (error) => {if(error){/* do nothing */}};", + options: ["error"], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: + "var test = function(error) {if(! error){doSomethingHere();}};", + options: ["error"], + }, + { + code: "var test = function(err) { console.log(err); };", + options: ["^(err|error)$"], + }, + { + code: "var test = function(error) { console.log(error); };", + options: ["^(err|error)$"], + }, + { + code: "var test = function(anyError) { console.log(anyError); };", + options: ["^.+Error$"], + }, + { + code: "var test = function(any_error) { console.log(anyError); };", + options: ["^.+Error$"], + }, + { + code: "var test = function(any_error) { console.log(any_error); };", + options: ["^.+(e|E)rror$"], + }, + ], + invalid: [ + { code: "function test(err) {}", errors: [EXPECTED_DECL_ERROR] }, + { code: "function test(err, data) {}", errors: [EXPECTED_DECL_ERROR] }, + { + code: "function test(err) {errorLookingWord();}", + errors: [EXPECTED_DECL_ERROR], + }, + { + code: "function test(err) {try{} catch(err) {}}", + errors: [EXPECTED_DECL_ERROR], + }, + { + code: + "function test(err, callback) { foo(function(err, callback) {}); }", + errors: [EXPECTED_DECL_ERROR, EXPECTED_FUNC_ERROR], + }, + { + code: "var test = (err) => {};", + parserOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "expected" }], + }, + { code: "var test = function(err) {};", errors: [EXPECTED_FUNC_ERROR] }, + { + code: "var test = function test(err, data) {};", + errors: [EXPECTED_FUNC_ERROR], + }, + { + code: "var test = function test(err) {/* if(err){} */};", + errors: [EXPECTED_FUNC_ERROR], + }, + { + code: + "function test(err) {doSomethingHere(function(err){console.log(err);})}", + errors: [EXPECTED_DECL_ERROR], + }, + { + code: "function test(error) {}", + options: ["error"], + errors: [EXPECTED_DECL_ERROR], + }, + { + code: + "getData(function(err, data) {getMoreDataWith(data, function(err, moreData) {if (err) {}getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});}); });", + errors: [EXPECTED_FUNC_ERROR], + }, + { + code: + "getData(function(err, data) {getMoreDataWith(data, function(err, moreData) {getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});}); });", + errors: [EXPECTED_FUNC_ERROR, EXPECTED_FUNC_ERROR], + }, + { + code: + "function userHandler(err) {logThisAction(function(err) {if (err) { console.log(err); } })}", + errors: [EXPECTED_DECL_ERROR], + }, + { + code: + "function help() { function userHandler(err) {function tester(err) { err; process.nextTick(function() { err; }); } } }", + errors: [EXPECTED_DECL_ERROR], + }, + { + code: "var test = function(anyError) { console.log(otherError); };", + options: ["^.+Error$"], + errors: [EXPECTED_FUNC_ERROR], + }, + { + code: "var test = function(anyError) { };", + options: ["^.+Error$"], + errors: [EXPECTED_FUNC_ERROR], + }, + { + code: "var test = function(err) { console.log(error); };", + options: ["^(err|error)$"], + errors: [EXPECTED_FUNC_ERROR], + }, + ], +}) diff --git a/tests/lib/rules/no-mixed-requires.js b/tests/lib/rules/no-mixed-requires.js new file mode 100644 index 00000000..69a090d9 --- /dev/null +++ b/tests/lib/rules/no-mixed-requires.js @@ -0,0 +1,168 @@ +/** + * @author Raphael Pigulla + * See LICENSE file in root directory for full license. + */ +"use strict" + +const RuleTester = require("eslint").RuleTester +const rule = require("../../../lib/rules/no-mixed-requires") + +new RuleTester().run("no-mixed-requires", rule, { + valid: [ + { code: "var a, b = 42, c = doStuff()", options: [false] }, + { + code: + "var a = require(42), b = require(), c = require('y'), d = require(doStuff())", + options: [false], + }, + { + code: "var fs = require('fs'), foo = require('foo')", + options: [false], + }, + { + code: + "var exec = require('child_process').exec, foo = require('foo')", + options: [false], + }, + { + code: "var fs = require('fs'), foo = require('./foo')", + options: [false], + }, + { + code: "var foo = require('foo'), foo2 = require('./foo')", + options: [false], + }, + { + code: + "var emitter = require('events').EventEmitter, fs = require('fs')", + options: [false], + }, + { + code: "var foo = require(42), bar = require(getName())", + options: [false], + }, + { + code: "var foo = require(42), bar = require(getName())", + options: [true], + }, + { + code: "var fs = require('fs'), foo = require('./foo')", + options: [{ grouping: false }], + }, + { + code: "var foo = require('foo'), bar = require(getName())", + options: [false], + }, + { code: "var a;", options: [true] }, + { + code: + "var async = require('async'), debug = require('diagnostics')('my-module')", + options: [{ allowCall: true }], + }, + ], + invalid: [ + { + code: "var fs = require('fs'), foo = 42", + options: [false], + errors: [ + { + messageId: "noMixRequire", + type: "VariableDeclaration", + }, + ], + }, + { + code: "var fs = require('fs'), foo", + options: [false], + errors: [ + { + messageId: "noMixRequire", + type: "VariableDeclaration", + }, + ], + }, + { + code: + "var a = require(42), b = require(), c = require('y'), d = require(doStuff())", + options: [true], + errors: [ + { + messageId: "noMixCoreModuleFileComputed", + type: "VariableDeclaration", + }, + ], + }, + { + code: "var fs = require('fs'), foo = require('foo')", + options: [true], + errors: [ + { + messageId: "noMixCoreModuleFileComputed", + type: "VariableDeclaration", + }, + ], + }, + { + code: "var fs = require('fs'), foo = require('foo')", + options: [{ grouping: true }], + errors: [ + { + messageId: "noMixCoreModuleFileComputed", + type: "VariableDeclaration", + }, + ], + }, + { + code: + "var exec = require('child_process').exec, foo = require('foo')", + options: [true], + errors: [ + { + messageId: "noMixCoreModuleFileComputed", + type: "VariableDeclaration", + }, + ], + }, + { + code: "var fs = require('fs'), foo = require('./foo')", + options: [true], + errors: [ + { + messageId: "noMixCoreModuleFileComputed", + type: "VariableDeclaration", + }, + ], + }, + { + code: "var foo = require('foo'), foo2 = require('./foo')", + options: [true], + errors: [ + { + messageId: "noMixCoreModuleFileComputed", + type: "VariableDeclaration", + }, + ], + }, + { + code: "var foo = require('foo'), bar = require(getName())", + options: [true], + errors: [ + { + messageId: "noMixCoreModuleFileComputed", + type: "VariableDeclaration", + }, + ], + }, + { + code: + "var async = require('async'), debug = require('diagnostics').someFun('my-module')", + options: [{ allowCall: true }], + errors: [ + { + messageId: "noMixRequire", + type: "VariableDeclaration", + }, + ], + }, + ], +}) diff --git a/tests/lib/rules/no-new-require.js b/tests/lib/rules/no-new-require.js new file mode 100644 index 00000000..9aca0c32 --- /dev/null +++ b/tests/lib/rules/no-new-require.js @@ -0,0 +1,36 @@ +/** + * @author Wil Moore III + * See LICENSE file in root directory for full license. + */ +"use strict" + +const RuleTester = require("eslint").RuleTester +const rule = require("../../../lib/rules/no-new-require") + +new RuleTester().run("no-new-require", rule, { + valid: [ + "var appHeader = require('app-header')", + "var AppHeader = new (require('app-header'))", + "var AppHeader = new (require('headers').appHeader)", + ], + invalid: [ + { + code: "var appHeader = new require('app-header')", + errors: [ + { + messageId: "noNewRequire", + type: "NewExpression", + }, + ], + }, + { + code: "var appHeader = new require('headers').appHeader", + errors: [ + { + messageId: "noNewRequire", + type: "NewExpression", + }, + ], + }, + ], +}) diff --git a/tests/lib/rules/no-path-concat.js b/tests/lib/rules/no-path-concat.js new file mode 100644 index 00000000..d52d8f4f --- /dev/null +++ b/tests/lib/rules/no-path-concat.js @@ -0,0 +1,56 @@ +/** + * @author Nicholas C. Zakas + * See LICENSE file in root directory for full license. + */ +"use strict" + +const RuleTester = require("eslint").RuleTester +const rule = require("../../../lib/rules/no-path-concat") + +new RuleTester().run("no-path-concat", rule, { + valid: [ + 'var fullPath = dirname + "foo.js";', + 'var fullPath = __dirname == "foo.js";', + "if (fullPath === __dirname) {}", + "if (__dirname === fullPath) {}", + ], + + invalid: [ + { + code: 'var fullPath = __dirname + "/foo.js";', + errors: [ + { + messageId: "usePathFunctions", + type: "BinaryExpression", + }, + ], + }, + { + code: 'var fullPath = __filename + "/foo.js";', + errors: [ + { + messageId: "usePathFunctions", + type: "BinaryExpression", + }, + ], + }, + { + code: 'var fullPath = "/foo.js" + __filename;', + errors: [ + { + messageId: "usePathFunctions", + type: "BinaryExpression", + }, + ], + }, + { + code: 'var fullPath = "/foo.js" + __dirname;', + errors: [ + { + messageId: "usePathFunctions", + type: "BinaryExpression", + }, + ], + }, + ], +}) diff --git a/tests/lib/rules/no-process-env.js b/tests/lib/rules/no-process-env.js new file mode 100644 index 00000000..e870b5e8 --- /dev/null +++ b/tests/lib/rules/no-process-env.js @@ -0,0 +1,47 @@ +/** + * @author Vignesh Anand + * See LICENSE file in root directory for full license. + */ +"use strict" + +const RuleTester = require("eslint").RuleTester +const rule = require("../../../lib/rules/no-process-env") + +new RuleTester().run("no-process-env", rule, { + valid: [ + "Process.env", + "process[env]", + "process.nextTick", + "process.execArgv", + ], + + invalid: [ + { + code: "process.env", + errors: [ + { + messageId: "unexpectedProcessEnv", + type: "MemberExpression", + }, + ], + }, + { + code: "process.env.ENV", + errors: [ + { + messageId: "unexpectedProcessEnv", + type: "MemberExpression", + }, + ], + }, + { + code: "f(process.env)", + errors: [ + { + messageId: "unexpectedProcessEnv", + type: "MemberExpression", + }, + ], + }, + ], +}) diff --git a/tests/lib/rules/no-process-exit.js b/tests/lib/rules/no-process-exit.js new file mode 100644 index 00000000..1c1c03de --- /dev/null +++ b/tests/lib/rules/no-process-exit.js @@ -0,0 +1,42 @@ +/** + * @author Nicholas C. Zakas + * See LICENSE file in root directory for full license. + */ +"use strict" + +const RuleTester = require("eslint").RuleTester +const rule = require("../../../lib/rules/no-process-exit") + +new RuleTester().run("no-process-exit", rule, { + valid: ["Process.exit()", "var exit = process.exit;", "f(process.exit)"], + + invalid: [ + { + code: "process.exit(0);", + errors: [ + { + messageId: "noProcessExit", + type: "CallExpression", + }, + ], + }, + { + code: "process.exit(1);", + errors: [ + { + messageId: "noProcessExit", + type: "CallExpression", + }, + ], + }, + { + code: "f(process.exit(1));", + errors: [ + { + messageId: "noProcessExit", + type: "CallExpression", + }, + ], + }, + ], +}) diff --git a/tests/lib/rules/no-restricted-require.js b/tests/lib/rules/no-restricted-require.js new file mode 100644 index 00000000..b1068d32 --- /dev/null +++ b/tests/lib/rules/no-restricted-require.js @@ -0,0 +1,178 @@ +/** + * @author Christian Schulz + * See LICENSE file in root directory for full license. + */ +"use strict" + +const RuleTester = require("eslint").RuleTester +const rule = require("../../../lib/rules/no-restricted-require") + +new RuleTester().run("no-restricted-require", rule, { + valid: [ + { code: 'require("fs")', options: ["crypto"] }, + { code: 'require("path")', options: ["crypto", "stream", "os"] }, + 'require("fs ")', + { code: "require(2)", options: ["crypto"] }, + { code: "require(foo)", options: ["crypto"] }, + { code: "var foo = bar('crypto');", options: ["crypto"] }, + { code: 'require("foo/bar");', options: ["foo"] }, + { + code: 'var withPaths = require("foo/bar");', + options: [{ paths: ["foo", "bar"] }], + }, + { + code: 'var withPatterns = require("foo/bar");', + options: [{ patterns: ["foo/c*"] }], + }, + { + code: 'var withPatternsAndPaths = require("foo/bar");', + options: [{ paths: ["foo"], patterns: ["foo/c*"] }], + }, + { + code: 'var withGitignores = require("foo/bar");', + options: [{ paths: ["foo"], patterns: ["foo/*", "!foo/bar"] }], + }, + ], + invalid: [ + { + code: 'require("fs")', + options: ["fs"], + errors: [ + { + messageId: "defaultMessage", + data: { name: "fs" }, + type: "CallExpression", + }, + ], + }, + { + code: 'require("os ")', + options: ["fs", "crypto ", "stream", "os"], + errors: [ + { + messageId: "defaultMessage", + data: { name: "os" }, + type: "CallExpression", + }, + ], + }, + { + code: 'require("foo/bar");', + options: ["foo/bar"], + errors: [ + { + messageId: "defaultMessage", + data: { name: "foo/bar" }, + type: "CallExpression", + }, + ], + }, + { + code: 'var withPaths = require("foo/bar");', + options: [{ paths: ["foo/bar"] }], + errors: [ + { + messageId: "defaultMessage", + data: { name: "foo/bar" }, + type: "CallExpression", + }, + ], + }, + { + code: 'var withPatterns = require("foo/bar");', + options: [{ patterns: ["foo/*"] }], + errors: [ + { + messageId: "patternMessage", + data: { name: "foo/bar" }, + type: "CallExpression", + }, + ], + }, + { + code: 'var withPatternsAndPaths = require("foo/bar");', + options: [{ patterns: ["foo/*"], paths: ["foo"] }], + errors: [ + { + messageId: "patternMessage", + data: { name: "foo/bar" }, + type: "CallExpression", + }, + ], + }, + { + code: 'var withGitignores = require("foo/bar");', + options: [{ patterns: ["foo/*", "!foo/baz"], paths: ["foo"] }], + errors: [ + { + messageId: "patternMessage", + data: { name: "foo/bar" }, + type: "CallExpression", + }, + ], + }, + { + code: 'var withGitignores = require("foo");', + options: [ + { + name: "foo", + message: "Please use 'bar' module instead.", + }, + ], + errors: [ + { + messageId: "customMessage", + data: { + name: "foo", + customMessage: "Please use 'bar' module instead.", + }, + type: "CallExpression", + }, + ], + }, + { + code: 'var withGitignores = require("bar");', + options: [ + "foo", + { + name: "bar", + message: "Please use 'baz' module instead.", + }, + "baz", + ], + errors: [ + { + messageId: "customMessage", + data: { + name: "bar", + customMessage: "Please use 'baz' module instead.", + }, + type: "CallExpression", + }, + ], + }, + { + code: 'var withGitignores = require("foo");', + options: [ + { + paths: [ + { + name: "foo", + message: "Please use 'bar' module instead.", + }, + ], + }, + ], + errors: [ + { + messageId: "customMessage", + data: { + name: "foo", + customMessage: "Please use 'bar' module instead.", + }, + type: "CallExpression", + }, + ], + }, + ], +}) diff --git a/tests/lib/rules/no-sync.js b/tests/lib/rules/no-sync.js new file mode 100644 index 00000000..aaf97bb7 --- /dev/null +++ b/tests/lib/rules/no-sync.js @@ -0,0 +1,97 @@ +/** + * @author Matt DuVall + * See LICENSE file in root directory for full license. + */ +"use strict" + +const RuleTester = require("eslint").RuleTester +const rule = require("../../../lib/rules/no-sync") + +new RuleTester().run("no-sync", rule, { + valid: [ + "var foo = fs.foo.foo();", + { + code: "var foo = fs.fooSync;", + options: [{ allowAtRootLevel: true }], + }, + { + code: "if (true) {fs.fooSync();}", + options: [{ allowAtRootLevel: true }], + }, + ], + invalid: [ + { + code: "var foo = fs.fooSync();", + errors: [ + { + messageId: "noSync", + data: { propertyName: "fooSync" }, + type: "MemberExpression", + }, + ], + }, + { + code: "var foo = fs.fooSync();", + options: [{ allowAtRootLevel: false }], + errors: [ + { + messageId: "noSync", + data: { propertyName: "fooSync" }, + type: "MemberExpression", + }, + ], + }, + { + code: "if (true) {fs.fooSync();}", + errors: [ + { + messageId: "noSync", + data: { propertyName: "fooSync" }, + type: "MemberExpression", + }, + ], + }, + { + code: "var foo = fs.fooSync;", + errors: [ + { + messageId: "noSync", + data: { propertyName: "fooSync" }, + type: "MemberExpression", + }, + ], + }, + { + code: "function someFunction() {fs.fooSync();}", + errors: [ + { + messageId: "noSync", + data: { propertyName: "fooSync" }, + type: "MemberExpression", + }, + ], + }, + { + code: "function someFunction() {fs.fooSync();}", + options: [{ allowAtRootLevel: true }], + errors: [ + { + messageId: "noSync", + data: { propertyName: "fooSync" }, + type: "MemberExpression", + }, + ], + }, + { + code: "var a = function someFunction() {fs.fooSync();}", + options: [{ allowAtRootLevel: true }], + errors: [ + { + messageId: "noSync", + data: { propertyName: "fooSync" }, + type: "MemberExpression", + }, + ], + }, + ], +})