Socket
Socket
Sign inDemoInstall

eslint-plugin-promise

Package Overview
Dependencies
Maintainers
3
Versions
47
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-promise - npm Package Compare versions

Comparing version 3.8.0 to 6.1.1

LICENSE.md

170

CHANGELOG.md

@@ -0,35 +1,101 @@

## 6.0.2
- Added tests for @typescript-eslint/parser support
## 6.0.1
- Fixed @typescript-eslint/parser issue #331, #205
## 6.0.0
- Dropped node 10 from engines #231
- Updated a ton of deps #236, #237, #235, #234
- ESLint 8 support #219
## 5.2.0
- Updated `param-names` rule to allow for unused params
## 5.1.1
- Updated docs to include `no-callback-in-promise` reasons #215
## 5.1.0
- Included `catch()` and `finally()` in `prefer-await-to-then` #196
- Added some additional tests and upgraded some dev deps #196
- Exempted array methods in prefer-await-to-callbacks
([#212](https://github.com/eslint-community/eslint-plugin-promise/issues/212))
## 5.0.0
- ESLint 7.0 Support
## 4.3.1.
- Updated and applied prettier
## 4.3.0
- https://github.com/eslint-community/eslint-plugin-promise/pull/202
- Updated jest
## 4.2.2
- Added license
- Dependabot security updates
## 4.2.1
- Added more use cases to `no-return-wrap`
## 4.0.1
- Remove `promise/param-names` fixer
([#146](https://github.com/eslint-community/eslint-plugin-promise/pull/146))
## 4.0.0
- Added fixer for `promise/no-new-statics` rule
([#133](https://github.com/eslint-community/eslint-plugin-promise/pull/133))
- Support ESLint v5
([#144](https://github.com/eslint-community/eslint-plugin-promise/pull/144))
This is a breaking change that drops support for Node v4. In order to use ESLint
v5 and eslint-plugin-promise v4, you must use Node >=6.
## 3.8.0
* Removed `promise/avoid-new` from recommended configuration
([#119](https://github.com/xjamundx/eslint-plugin-promise/pull/119))
* Ignored event listener callbacks in `promise/prefer-await-to-callbacks`
([#117](https://github.com/xjamundx/eslint-plugin-promise/pull/117))
* Ignored top-level awaits in `promise/prefer-await-to-then`
([#126](https://github.com/xjamundx/eslint-plugin-promise/pull/126))
* Added docs for `promise/no-nesting` and `promise/prefer-await-to-then`
([#120](https://github.com/xjamundx/eslint-plugin-promise/pull/120))
([#121](https://github.com/xjamundx/eslint-plugin-promise/pull/121))
- Removed `promise/avoid-new` from recommended configuration
([#119](https://github.com/eslint-community/eslint-plugin-promise/pull/119))
- Ignored event listener callbacks in `promise/prefer-await-to-callbacks`
([#117](https://github.com/eslint-community/eslint-plugin-promise/pull/117))
- Ignored top-level awaits in `promise/prefer-await-to-then`
([#126](https://github.com/eslint-community/eslint-plugin-promise/pull/126))
- Added docs for `promise/no-nesting` and `promise/prefer-await-to-then`
([#120](https://github.com/eslint-community/eslint-plugin-promise/pull/120))
([#121](https://github.com/eslint-community/eslint-plugin-promise/pull/121))
## 3.7.0
* Added `promise/valid-params` rule
([#85](https://github.com/xjamundx/eslint-plugin-promise/pull/85))
* Added `promise/no-new-statics` rule
([#82](https://github.com/xjamundx/eslint-plugin-promise/pull/82))
* Added fixer for `promise/param-names` rule
([#99](https://github.com/xjamundx/eslint-plugin-promise/pull/99))
* Added rule documentation to each rule
([#91](https://github.com/xjamundx/eslint-plugin-promise/pull/91))
- Added `promise/valid-params` rule
([#85](https://github.com/eslint-community/eslint-plugin-promise/pull/85))
- Added `promise/no-new-statics` rule
([#82](https://github.com/eslint-community/eslint-plugin-promise/pull/82))
- Added fixer for `promise/param-names` rule
([#99](https://github.com/eslint-community/eslint-plugin-promise/pull/99))
- Added rule documentation to each rule
([#91](https://github.com/eslint-community/eslint-plugin-promise/pull/91))
## 3.6.0
* Added `['catch']` support in `catch-or-return`
* Added `no-return-in-finally` rule
* Fixed some formatting in the docs
* Added `allowReject` option to `no-return-wrap`
* Added exceptions for `no-callback-in-promise`
- Added `['catch']` support in `catch-or-return`
- Added `no-return-in-finally` rule
- Fixed some formatting in the docs
- Added `allowReject` option to `no-return-wrap`
- Added exceptions for `no-callback-in-promise`
## 3.5.0
* Added support for recommended settings using
- Added support for recommended settings using
`extends: plugin:promise/recommended`

@@ -39,87 +105,87 @@

* Fixed always return false positive with ternary (#31)
- Fixed always return false positive with ternary (#31)
## 3.4.1
* fixed #49
- fixed #49
## 3.4.0
* new rule: avoid-new
* new rule: no-promise-in-callback
* new rule: no-callback-in-promise
* new rule: no-nesting
- new rule: avoid-new
- new rule: no-promise-in-callback
- new rule: no-callback-in-promise
- new rule: no-nesting
## 3.3.2
* Removed eslint from peerDeps
- Removed eslint from peerDeps
## 3.3.1
* Updated engines with proper stuff
* Fixed bug for unreachable code
- Updated engines with proper stuff
- Fixed bug for unreachable code
## 3.3.0
* Rule: `prefer-async-to-callbacks` added
* Rule: `prefer-async-to-then` added
- Rule: `prefer-async-to-callbacks` added
- Rule: `prefer-async-to-then` added
## 3.2.1
* Fix: `no-return-wrap` rule missing from index.js
- Fix: `no-return-wrap` rule missing from index.js
## 3.2.0
* Added `no-return-wrap` rule
- Added `no-return-wrap` rule
## 3.1.0
* Added multiple terminationMethods
- Added multiple terminationMethods
## 3.0.1
* Removed deprecated `always-catch` rule
* FIX: always-return error with "fn && fn()"
- Removed deprecated `always-catch` rule
- FIX: always-return error with "fn && fn()"
## 3.0.0
* Updated column and line numbers
* Added flow analysis for better handling of if statements
- Updated column and line numbers
- Added flow analysis for better handling of if statements
## 2.0.1
* Fixed type in docs
- Fixed type in docs
## 2.0.0
* ESLint 3.0 Support
- ESLint 3.0 Support
## 1.3.2
* Updated tests to run on eslint 2.0
* Fixed some issues with `no-native` rule
- Updated tests to run on eslint 2.0
- Fixed some issues with `no-native` rule
## 1.3.1
* Actually added `no-native` rule
- Actually added `no-native` rule
## 1.3.0
* Added `no-native` rule
- Added `no-native` rule
## 1.2.0
* Allow `throw` in `always-return` rule
* Added `terminationMethod` option to `catch-or-return` rule
- Allow `throw` in `always-return` rule
- Added `terminationMethod` option to `catch-or-return` rule
## 1.1.0
* Added `catch-or-return` rule
- Added `catch-or-return` rule
## 1.0.8
* Fixed crash issues
- Fixed crash issues
## 1.0.0 - 1.0.7
* Lots of basic feature updates and doc changes
- Lots of basic feature updates and doc changes

@@ -18,3 +18,4 @@ 'use strict'

'no-return-in-finally': require('./rules/no-return-in-finally'),
'valid-params': require('./rules/valid-params')
'valid-params': require('./rules/valid-params'),
'no-multiple-resolved': require('./rules/no-multiple-resolved'),
},

@@ -26,6 +27,7 @@ rulesConfig: {

'no-native': 0,
'catch-or-return': 1
'catch-or-return': 1,
},
configs: {
recommended: {
plugins: ['promise'],
rules: {

@@ -43,6 +45,6 @@ 'promise/always-return': 'error',

'promise/no-return-in-finally': 'warn',
'promise/valid-params': 'warn'
}
}
}
'promise/valid-params': 'warn',
},
},
},
}
{
"name": "eslint-plugin-promise",
"version": "3.8.0",
"version": "6.1.1",
"description": "Enforce best practices for JavaScript promises",

@@ -12,64 +12,68 @@ "keywords": [

],
"homepage": "https://github.com/eslint-community/eslint-plugin-promise",
"bugs": "https://github.com/eslint-community/eslint-plugin-promise/issues",
"repository": {
"type": "git",
"url": "https://github.com/eslint-community/eslint-plugin-promise"
},
"license": "ISC",
"author": "jden <jason@denizac.org>",
"repository": "git@github.com:xjamundx/eslint-plugin-promise.git",
"contributors": [
"Brett Zamir",
"Aadit M Shah <aaditmshah@aadit.codes> (https://aadit.codes/)"
],
"scripts": {
"precommit": "lint-staged",
"test": "jest",
"lint": "eslint index.js rules __tests__ --ignore-pattern '**/*.json'"
"format": "prettier --write .",
"lint": "eslint --report-unused-disable-directives .",
"prepare": "husky install",
"test": "jest --coverage"
},
"devDependencies": {
"doctoc": "^1.3.0",
"eslint": "^4.17.0",
"eslint-config-prettier": "^2.9.0",
"eslint-config-standard": "^11.0.0-beta.0",
"eslint-plugin-eslint-plugin": "^1.4.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-jest": "^21.12.2",
"eslint-plugin-node": "^6.0.0",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-promise": "./",
"eslint-plugin-standard": "^3.0.1",
"husky": "^0.14.3",
"jest": "^22.4.2",
"jest-runner-eslint": "^0.4.0",
"lint-staged": "^6.1.0",
"prettier": "^1.10.2"
},
"engines": {
"node": ">=4"
},
"license": "ISC",
"lint-staged": {
"concurrent": false,
"linters": {
"{README.md,CONTRIBUTING.md}": [
"doctoc --maxlevel 3 --notitle",
"git add"
],
"*.js": ["prettier --write", "eslint --fix", "git add"],
"*.+(json|md)": ["prettier --write", "git add"]
}
"{README.md,CONTRIBUTING.md}": [
"doctoc --maxlevel 3 --notitle"
],
"*.js": [
"prettier --write",
"eslint --report-unused-disable-directives --fix"
],
"*.+(json|md)": [
"prettier --write"
]
},
"prettier": {
"proseWrap": "always",
"semi": false,
"singleQuote": true,
"proseWrap": "always"
"singleQuote": true
},
"jest": {
"projects": [
{
"displayName": "test",
"testEnvironment": "node"
},
{
"runner": "jest-runner-eslint",
"displayName": "lint",
"testMatch": [
"<rootDir>/rules/**/*.js",
"<rootDir>/__tests__/**/*.js",
"<rootDir>/index.js"
]
"coverageThreshold": {
"global": {
"branches": 100,
"functions": 100,
"lines": 100,
"statements": 100
}
]
}
},
"devDependencies": {
"@typescript-eslint/parser": "^5.40.0",
"doctoc": "^2.2.1",
"eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-eslint-plugin": "^4.4.1",
"eslint-plugin-jest": "^26.9.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1",
"husky": "^7.0.4",
"jest": "^28.1.3",
"lint-staged": "^12.5.0",
"prettier": "^2.7.1",
"typescript": "^4.8.4"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
}

@@ -5,16 +5,14 @@ # eslint-plugin-promise

[![travis-ci](https://travis-ci.org/xjamundx/eslint-plugin-promise.svg)](https://travis-ci.org/xjamundx/eslint-plugin-promise)
[![CI](https://github.com/eslint-community/eslint-plugin-promise/actions/workflows/CI.yml/badge.svg)](https://github.com/eslint-community/eslint-plugin-promise/actions/workflows/CI.yml)
[![npm version](https://badge.fury.io/js/eslint-plugin-promise.svg)](https://www.npmjs.com/package/eslint-plugin-promise)
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
* [Installation](#installation)
* [Usage](#usage)
* [Rules](#rules)
* [Maintainers](#maintainers)
* [License](#license)
- [Installation](#installation)
- [Usage](#usage)
- [Rules](#rules)
- [Maintainers](#maintainers)
- [License](#license)

@@ -42,4 +40,4 @@ <!-- END doctoc generated TOC please keep comment here to allow auto update -->

Add `promise` to the plugins section of your `.eslintrc` configuration file. You
can omit the `eslint-plugin-` prefix:
Add `promise` to the plugins section of your `.eslintrc.json` configuration
file. You can omit the `eslint-plugin-` prefix:

@@ -73,3 +71,3 @@ ```json

or start with the recommended rule set
or start with the recommended rule set:

@@ -88,3 +86,3 @@ ```json

| [`no-return-wrap`][no-return-wrap] | Avoid wrapping values in `Promise.resolve` or `Promise.reject` when not needed. | :bangbang: | |
| [`param-names`][param-names] | Enforce consistent param names when creating new promises. | :bangbang: | :wrench: |
| [`param-names`][param-names] | Enforce consistent param names and ordering when creating new promises. | :bangbang: | |
| [`always-return`][always-return] | Return inside each `then()` to create readable and reusable Promise chains. | :bangbang: | |

@@ -96,7 +94,8 @@ | [`no-native`][no-native] | In an ES5 environment, make sure to create a `Promise` constructor before using. | | |

| [`avoid-new`][avoid-new] | Avoid creating `new` promises outside of utility libs (use [pify][] instead) | | |
| [`no-new-statics`][no-new-statics] | Avoid calling `new` on a Promise static method | :bangbang: | |
| [`no-new-statics`][no-new-statics] | Avoid calling `new` on a Promise static method | :bangbang: | :wrench: |
| [`no-return-in-finally`][no-return-in-finally] | Disallow return statements in `finally()` | :warning: | |
| [`valid-params`][valid-params] | Ensures the proper number of arguments are passed to Promise functions | :warning: | |
| [`prefer-await-to-then`][prefer-await-to-then] | Prefer `await` to `then()` for reading Promise values | :seven: | |
| [`prefer-await-to-then`][prefer-await-to-then] | Prefer `await` to `then()`/`catch()`/`finally()` for reading Promise values | :seven: | |
| [`prefer-await-to-callbacks`][prefer-await-to-callbacks] | Prefer async/await to the callback pattern | :seven: | |
| [`no-multiple-resolved`][no-multiple-resolved] | Disallow creating new promises with paths that resolve multiple times | | |

@@ -114,9 +113,10 @@ **Key**

* Jamund Ferguson - [@xjamundx][]
* Macklin Underdown - [@macklinu][]
- Jamund Ferguson - [@xjamundx][]
- Macklin Underdown - [@macklinu][]
- Aadit M Shah - [@aaditmshah][]
## License
* (c) MMXV jden <mailto:jason@denizac.org> - ISC license.
* (c) 2016 Jamund Ferguson <mailto:jamund@gmail.com> - ISC license.
- (c) MMXV jden <mailto:jason@denizac.org> - ISC license.
- (c) 2016 Jamund Ferguson <mailto:jamund@gmail.com> - ISC license.

@@ -137,5 +137,7 @@ [catch-or-return]: docs/rules/catch-or-return.md

[prefer-await-to-callbacks]: docs/rules/prefer-await-to-callbacks.md
[no-multiple-resolved]: docs/rules/no-multiple-resolved.md
[nodeify]: https://www.npmjs.com/package/nodeify
[pify]: https://www.npmjs.com/package/pify
[@aaditmshah]: https://github.com/aaditmshah
[@macklinu]: https://github.com/macklinu
[@xjamundx]: https://github.com/xjamundx

@@ -5,2 +5,16 @@ 'use strict'

/**
* @typedef {import('estree').Node} Node
* @typedef {import('estree').SimpleCallExpression} CallExpression
* @typedef {import('estree').FunctionExpression} FunctionExpression
* @typedef {import('estree').ArrowFunctionExpression} ArrowFunctionExpression
* @typedef {import('eslint').Rule.CodePath} CodePath
* @typedef {import('eslint').Rule.CodePathSegment} CodePathSegment
*/
/**
* @typedef { (FunctionExpression | ArrowFunctionExpression) & { parent: CallExpression }} InlineThenFunctionExpression
*/
/** @param {Node} node */
function isFunctionWithBlockStatement(node) {

@@ -16,12 +30,20 @@ if (node.type === 'FunctionExpression') {

function isThenCallExpression(node) {
/**
* @param {string} memberName
* @param {Node} node
* @returns {node is CallExpression}
*/
function isMemberCall(memberName, node) {
return (
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.property.name === 'then'
!node.callee.computed &&
node.callee.property.type === 'Identifier' &&
node.callee.property.name === memberName
)
}
/** @param {Node} node */
function isFirstArgument(node) {
return (
return Boolean(
node.parent && node.parent.arguments && node.parent.arguments[0] === node

@@ -31,6 +53,10 @@ )

/**
* @param {Node} node
* @returns {node is InlineThenFunctionExpression}
*/
function isInlineThenFunctionExpression(node) {
return (
isFunctionWithBlockStatement(node) &&
isThenCallExpression(node.parent) &&
isMemberCall('then', node.parent) &&
isFirstArgument(node)

@@ -40,18 +66,62 @@ )

function hasParentReturnStatement(node) {
if (node && node.parent && node.parent.type) {
// if the parent is a then, and we haven't returned anything, fail
if (isThenCallExpression(node.parent)) {
return false
}
if (node.parent.type === 'ReturnStatement') {
/**
* Checks whether the given node is the last `then()` callback in a promise chain.
* @param {InlineThenFunctionExpression} node
*/
function isLastCallback(node) {
/** @type {Node} */
let target = node.parent
/** @type {Node | undefined} */
let parent = target.parent
while (parent) {
if (parent.type === 'ExpressionStatement') {
// e.g. { promise.then(() => value) }
return true
}
return hasParentReturnStatement(node.parent)
if (parent.type === 'UnaryExpression') {
// e.g. void promise.then(() => value)
return parent.operator === 'void'
}
/** @type {Node | null} */
let nextTarget = null
if (parent.type === 'SequenceExpression') {
if (peek(parent.expressions) !== target) {
// e.g. (promise?.then(() => value), expr)
return true
}
nextTarget = parent
} else if (
// e.g. promise?.then(() => value)
parent.type === 'ChainExpression' ||
// e.g. await promise.then(() => value)
parent.type === 'AwaitExpression'
) {
nextTarget = parent
} else if (parent.type === 'MemberExpression') {
if (
parent.parent &&
(isMemberCall('catch', parent.parent) ||
isMemberCall('finally', parent.parent))
) {
// e.g. promise.then(() => value).catch(e => {})
nextTarget = parent.parent
}
}
if (nextTarget) {
target = nextTarget
parent = target.parent
continue
}
return false
}
// istanbul ignore next
return false
}
/**
* @template T
* @param {T[]} arr
* @returns {T}
*/
function peek(arr) {

@@ -63,36 +133,59 @@ return arr[arr.length - 1]

meta: {
type: 'problem',
docs: {
url: getDocsUrl('always-return')
}
url: getDocsUrl('always-return'),
},
schema: [
{
type: 'object',
properties: {
ignoreLastCallback: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
},
create: function(context) {
// funcInfoStack is a stack representing the stack of currently executing
// functions
// funcInfoStack[i].branchIDStack is a stack representing the currently
// executing branches ("codePathSegment"s) within the given function
// funcInfoStack[i].branchInfoMap is an object representing information
// about all branches within the given function
// funcInfoStack[i].branchInfoMap[j].good is a boolean representing whether
// the given branch explictly `return`s or `throw`s. It starts as `false`
// for every branch and is updated to `true` if a `return` or `throw`
// statement is found
// funcInfoStack[i].branchInfoMap[j].loc is a eslint SourceLocation object
// for the given branch
// example:
// funcInfoStack = [ { branchIDStack: [ 's1_1' ],
// branchInfoMap:
// { s1_1:
// { good: false,
// loc: <loc> } } },
// { branchIDStack: ['s2_1', 's2_4'],
// branchInfoMap:
// { s2_1:
// { good: false,
// loc: <loc> },
// s2_2:
// { good: true,
// loc: <loc> },
// s2_4:
// { good: false,
// loc: <loc> } } } ]
create(context) {
const options = context.options[0] || {}
const ignoreLastCallback = !!options.ignoreLastCallback
/**
* @typedef {object} FuncInfo
* @property {string[]} branchIDStack This is a stack representing the currently
* executing branches ("codePathSegment"s) within the given function
* @property {Record<string, BranchInfo | undefined>} branchInfoMap This is an object representing information
* about all branches within the given function
*
* @typedef {object} BranchInfo
* @property {boolean} good This is a boolean representing whether
* the given branch explicitly `return`s or `throw`s. It starts as `false`
* for every branch and is updated to `true` if a `return` or `throw`
* statement is found
* @property {Node} node This is a estree Node object
* for the given branch
*/
/**
* funcInfoStack is a stack representing the stack of currently executing
* functions
* example:
* funcInfoStack = [ { branchIDStack: [ 's1_1' ],
* branchInfoMap:
* { s1_1:
* { good: false,
* loc: <loc> } } },
* { branchIDStack: ['s2_1', 's2_4'],
* branchInfoMap:
* { s2_1:
* { good: false,
* loc: <loc> },
* s2_2:
* { good: true,
* loc: <loc> },
* s2_4:
* { good: false,
* loc: <loc> } } } ]
* @type {FuncInfo[]}
*/
const funcInfoStack = []

@@ -110,12 +203,16 @@

return {
ReturnStatement: markCurrentBranchAsGood,
ThrowStatement: markCurrentBranchAsGood,
'ReturnStatement:exit': markCurrentBranchAsGood,
'ThrowStatement:exit': markCurrentBranchAsGood,
onCodePathSegmentStart: function(segment, node) {
/**
* @param {CodePathSegment} segment
* @param {Node} node
*/
onCodePathSegmentStart(segment, node) {
const funcInfo = peek(funcInfoStack)
funcInfo.branchIDStack.push(segment.id)
funcInfo.branchInfoMap[segment.id] = { good: false, node: node }
funcInfo.branchInfoMap[segment.id] = { good: false, node }
},
onCodePathSegmentEnd: function(segment, node) {
onCodePathSegmentEnd() {
const funcInfo = peek(funcInfoStack)

@@ -125,10 +222,14 @@ funcInfo.branchIDStack.pop()

onCodePathStart: function(path, node) {
onCodePathStart() {
funcInfoStack.push({
branchIDStack: [],
branchInfoMap: {}
branchInfoMap: {},
})
},
onCodePathEnd: function(path, node) {
/**
* @param {CodePath} path
* @param {Node} node
*/
onCodePathEnd(path, node) {
const funcInfo = funcInfoStack.pop()

@@ -140,26 +241,19 @@

path.finalSegments.forEach(segment => {
if (ignoreLastCallback && isLastCallback(node)) {
return
}
path.finalSegments.forEach((segment) => {
const id = segment.id
const branch = funcInfo.branchInfoMap[id]
if (!branch.good) {
if (hasParentReturnStatement(branch.node)) {
return
}
// check shortcircuit syntax like `x && x()` and `y || x()``
const prevSegments = segment.prevSegments
for (let ii = prevSegments.length - 1; ii >= 0; --ii) {
const prevSegment = prevSegments[ii]
if (funcInfo.branchInfoMap[prevSegment.id].good) return
}
context.report({
message: 'Each then() should return a value or throw',
node: branch.node
node: branch.node,
})
}
})
}
},
}
}
},
}

@@ -12,15 +12,17 @@ /**

meta: {
type: 'suggestion',
docs: {
url: getDocsUrl('avoid-new')
}
url: getDocsUrl('avoid-new'),
},
schema: [],
},
create: function(context) {
create(context) {
return {
NewExpression: function(node) {
NewExpression(node) {
if (node.callee.name === 'Promise') {
context.report({ node, message: 'Avoid creating new promises.' })
}
}
},
}
}
},
}

@@ -14,9 +14,39 @@ /**

meta: {
type: 'problem',
docs: {
url: getDocsUrl('catch-or-return')
}
url: getDocsUrl('catch-or-return'),
},
messages: {
terminationMethod: 'Expected {{ terminationMethod }}() or return',
},
schema: [
{
type: 'object',
properties: {
allowFinally: {
type: 'boolean',
},
allowThen: {
type: 'boolean',
},
terminationMethod: {
oneOf: [
{ type: 'string' },
{
type: 'array',
items: {
type: 'string',
},
},
],
},
},
additionalProperties: false,
},
],
},
create: function(context) {
create(context) {
const options = context.options[0] || {}
const allowThen = options.allowThen
const allowFinally = options.allowFinally
let terminationMethod = options.terminationMethod || 'catch'

@@ -28,4 +58,50 @@

function isAllowedPromiseTermination(expression) {
// somePromise.then(a, b)
if (
allowThen &&
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.property.name === 'then' &&
expression.arguments.length === 2
) {
return true
}
// somePromise.catch().finally(fn)
if (
allowFinally &&
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.property.name === 'finally' &&
isPromise(expression.callee.object) &&
isAllowedPromiseTermination(expression.callee.object)
) {
return true
}
// somePromise.catch()
if (
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
terminationMethod.indexOf(expression.callee.property.name) !== -1
) {
return true
}
// somePromise['catch']()
if (
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.property.type === 'Literal' &&
expression.callee.property.value === 'catch'
) {
return true
}
return false
}
return {
ExpressionStatement: function(node) {
ExpressionStatement(node) {
if (!isPromise(node.expression)) {

@@ -35,40 +111,14 @@ return

// somePromise.then(a, b)
if (
allowThen &&
node.expression.type === 'CallExpression' &&
node.expression.callee.type === 'MemberExpression' &&
node.expression.callee.property.name === 'then' &&
node.expression.arguments.length === 2
) {
if (isAllowedPromiseTermination(node.expression)) {
return
}
// somePromise.catch()
if (
node.expression.type === 'CallExpression' &&
node.expression.callee.type === 'MemberExpression' &&
terminationMethod.indexOf(node.expression.callee.property.name) !== -1
) {
return
}
// somePromise['catch']()
if (
node.expression.type === 'CallExpression' &&
node.expression.callee.type === 'MemberExpression' &&
node.expression.callee.property.type === 'Literal' &&
node.expression.callee.property.value === 'catch'
) {
return
}
context.report({
node,
message: 'Expected {{ terminationMethod }}() or return',
data: { terminationMethod }
messageId: 'terminationMethod',
data: { terminationMethod },
})
}
},
}
}
},
}
'use strict'
const pkg = require('../../package')
const REPO_URL = 'https://github.com/eslint-community/eslint-plugin-promise'
const REPO_URL = 'https://github.com/xjamundx/eslint-plugin-promise'
/**

@@ -16,5 +14,5 @@ * Generates the URL to documentation for the given rule name. It uses the

function getDocsUrl(ruleName) {
return `${REPO_URL}/tree/v${pkg.version}/docs/rules/${ruleName}.md`
return `${REPO_URL}/blob/main/docs/rules/${ruleName}.md`
}
module.exports = getDocsUrl
/**
* Library: Has Promis eCallback
* Library: Has Promise Callback
* Makes sure that an Expression node is part of a promise

@@ -9,3 +9,24 @@ * with callback functions (like then() or catch())

/**
* @typedef {import('estree').SimpleCallExpression} CallExpression
* @typedef {import('estree').MemberExpression} MemberExpression
* @typedef {import('estree').Identifier} Identifier
*
* @typedef {object} NameIsThenOrCatch
* @property {'then' | 'catch'} name
*
* @typedef {object} PropertyIsThenOrCatch
* @property {Identifier & NameIsThenOrCatch} property
*
* @typedef {object} CalleeIsPromiseCallback
* @property {MemberExpression & PropertyIsThenOrCatch} callee
*
* @typedef {CallExpression & CalleeIsPromiseCallback} HasPromiseCallback
*/
/**
* @param {import('estree').Node} node
* @returns {node is HasPromiseCallback}
*/
function hasPromiseCallback(node) {
// istanbul ignore if -- only being called within `CallExpression`
if (node.type !== 'CallExpression') return

@@ -12,0 +33,0 @@ if (node.callee.type !== 'MemberExpression') return

@@ -5,4 +5,5 @@ 'use strict'

function isCallingBack(node, exceptions) {
function isCallback(node, exceptions) {
const isCallExpression = node.type === 'CallExpression'
// istanbul ignore next -- always invoked on `CallExpression`
const callee = node.callee || {}

@@ -14,2 +15,2 @@ const nameIsCallback = isNamedCallback(callee.name, exceptions)

module.exports = isCallingBack
module.exports = isCallback

@@ -7,9 +7,9 @@ 'use strict'

for (let i = 0; i < exceptions.length; i++) {
callbacks = callbacks.filter(function(item) {
callbacks = callbacks.filter((item) => {
return item !== exceptions[i]
})
}
return callbacks.some(function(trueCallbackName) {
return callbacks.some((trueCallbackName) => {
return potentialCallbackName === trueCallbackName
})
}

@@ -5,5 +5,7 @@ 'use strict'

all: true,
allSettled: true,
any: true,
race: true,
reject: true,
resolve: true
resolve: true,
}

@@ -15,9 +15,27 @@ /**

meta: {
type: 'suggestion',
docs: {
url: getDocsUrl('no-callback-in-promise')
}
url: getDocsUrl('no-callback-in-promise'),
},
messages: {
callback: 'Avoid calling back inside of a promise.',
},
schema: [
{
type: 'object',
properties: {
exceptions: {
type: 'array',
items: {
type: 'string',
},
},
},
additionalProperties: false,
},
],
},
create: function(context) {
create(context) {
return {
CallExpression: function(node) {
CallExpression(node) {
const options = context.options[0] || {}

@@ -39,3 +57,3 @@ const exceptions = options.exceptions || []

node: node.arguments[0],
message: 'Avoid calling back inside of a promise.'
messageId: 'callback',
})

@@ -49,8 +67,8 @@ }

node,
message: 'Avoid calling back inside of a promise.'
messageId: 'callback',
})
}
}
},
}
}
},
}

@@ -9,3 +9,3 @@ // Borrowed from here:

function isDeclared(scope, ref) {
return scope.variables.some(function(variable) {
return scope.variables.some((variable) => {
if (variable.name !== ref.identifier.name) {

@@ -15,2 +15,5 @@ return false

// Presumably can't pass this since the implicit `Promise` global
// being checked here would always lack `defs`
// istanbul ignore else
if (!variable.defs || !variable.defs.length) {

@@ -20,2 +23,3 @@ return false

// istanbul ignore next
return true

@@ -27,13 +31,16 @@ })

meta: {
type: 'suggestion',
docs: {
url: getDocsUrl('no-native')
}
url: getDocsUrl('no-native'),
},
messages: {
name: '"{{name}}" is not defined.',
},
schema: [],
},
create: function(context) {
const MESSAGE = '"{{name}}" is not defined.'
create(context) {
/**
* Checks for and reports reassigned constants
*
* @param {Scope} scope - an escope Scope object
* @param {Scope} scope - an eslint-scope Scope object
* @returns {void}

@@ -43,6 +50,14 @@ * @private

return {
'Program:exit': function() {
'Program:exit'() {
const scope = context.getScope()
const leftToBeResolved =
scope.implicit.left ||
/**
* Fixes https://github.com/eslint-community/eslint-plugin-promise/issues/205.
* The problem was that @typescript-eslint has a scope manager
* which has `leftToBeResolved` instead of the default `left`.
*/
scope.implicit.leftToBeResolved
scope.implicit.left.forEach(function(ref) {
leftToBeResolved.forEach((ref) => {
if (ref.identifier.name !== 'Promise') {

@@ -52,13 +67,14 @@ return

// istanbul ignore else
if (!isDeclared(scope, ref)) {
context.report({
node: ref.identifier,
message: MESSAGE,
data: { name: ref.identifier.name }
messageId: 'name',
data: { name: ref.identifier.name },
})
}
})
}
},
}
}
},
}

@@ -14,16 +14,106 @@ /**

meta: {
type: 'suggestion',
docs: {
url: getDocsUrl('no-nesting')
url: getDocsUrl('no-nesting'),
},
schema: [],
},
create(context) {
/**
* Array of callback function scopes.
* Scopes are in order closest to the current node.
* @type {import('eslint').Scope.Scope[]}
*/
const callbackScopes = []
/**
* @param {import('eslint').Scope.Scope} scope
* @returns {Iterable<import('eslint').Scope.Reference>}
*/
function* iterateDefinedReferences(scope) {
for (const variable of scope.variables) {
for (const reference of variable.references) {
yield reference
}
}
}
},
create: function(context) {
return {
CallExpression: function(node) {
':function'(node) {
if (isInsidePromise(node)) {
callbackScopes.unshift(context.getScope())
}
},
':function:exit'(node) {
if (isInsidePromise(node)) {
callbackScopes.shift()
}
},
CallExpression(node) {
if (!hasPromiseCallback(node)) return
if (context.getAncestors().some(isInsidePromise)) {
context.report({ node, message: 'Avoid nesting promises.' })
if (!callbackScopes.length) {
// The node is not in the callback function.
return
}
}
// Checks if the argument callback uses variables defined in the closest callback function scope.
//
// e.g.
// ```
// doThing()
// .then(a => getB(a)
// .then(b => getC(a, b))
// )
// ```
//
// In the above case, Since the variables it references are undef,
// we cannot refactor the nesting like following:
// ```
// doThing()
// .then(a => getB(a))
// .then(b => getC(a, b))
// ```
//
// However, `getD` can be refactored in the following:
// ```
// doThing()
// .then(a => getB(a)
// .then(b => getC(a, b)
// .then(c => getD(a, c))
// )
// )
// ```
// ↓
// ```
// doThing()
// .then(a => getB(a)
// .then(b => getC(a, b))
// .then(c => getD(a, c))
// )
// ```
// This is why we only check the closest callback function scope.
//
const closestCallbackScope = callbackScopes[0]
for (const reference of iterateDefinedReferences(
closestCallbackScope
)) {
if (
node.arguments.some(
(arg) =>
arg.range[0] <= reference.identifier.range[0] &&
reference.identifier.range[1] <= arg.range[1]
)
) {
// Argument callbacks refer to variables defined in the callback function.
return
}
}
context.report({
node: node.callee.property,
message: 'Avoid nesting promises.',
})
},
}
}
},
}

@@ -8,5 +8,8 @@ 'use strict'

meta: {
type: 'problem',
docs: {
url: getDocsUrl('no-new-statics')
}
url: getDocsUrl('no-new-statics'),
},
fixable: 'code',
schema: [],
},

@@ -24,8 +27,14 @@ create(context) {

message: "Avoid calling 'new' on 'Promise.{{ name }}()'",
data: { name: node.callee.property.name }
data: { name: node.callee.property.name },
fix(fixer) {
return fixer.replaceTextRange(
[node.range[0], node.range[0] + 'new '.length],
''
)
},
})
}
}
},
}
}
},
}

@@ -14,9 +14,11 @@ /**

meta: {
type: 'suggestion',
docs: {
url: getDocsUrl('no-promise-in-callback')
}
url: getDocsUrl('no-promise-in-callback'),
},
schema: [],
},
create: function(context) {
create(context) {
return {
CallExpression: function(node) {
CallExpression(node) {
if (!isPromise(node)) return

@@ -34,8 +36,8 @@

node: node.callee,
message: 'Avoid using promises inside of callbacks.'
message: 'Avoid using promises inside of callbacks.',
})
}
}
},
}
}
},
}

@@ -8,9 +8,11 @@ 'use strict'

meta: {
type: 'problem',
docs: {
url: getDocsUrl('no-return-in-finally')
}
url: getDocsUrl('no-return-in-finally'),
},
schema: [],
},
create: function(context) {
create(context) {
return {
CallExpression: function(node) {
CallExpression(node) {
if (isPromise(node)) {

@@ -22,2 +24,3 @@ if (

) {
// istanbul ignore else -- passing `isPromise` means should have a body
if (

@@ -30,3 +33,3 @@ node.arguments &&

if (
node.arguments[0].body.body.some(function(statement) {
node.arguments[0].body.body.some((statement) => {
return statement.type === 'ReturnStatement'

@@ -37,3 +40,3 @@ })

node: node.callee.property,
message: 'No return in finally'
message: 'No return in finally',
})

@@ -44,5 +47,5 @@ }

}
}
},
}
}
},
}
/**
* Rule: no-return-wrap function
* Prevents uneccessary wrapping of results in Promise.resolve
* Prevents unnecessary wrapping of results in Promise.resolve
* or Promise.reject as the Promise will do that for us

@@ -11,10 +11,27 @@ */

const isPromise = require('./lib/is-promise')
const rejectMessage = 'Expected throw instead of Promise.reject'
const resolveMessage = 'Avoid wrapping return values in Promise.resolve'
function isInPromise(context) {
const expression = context.getAncestors().filter(function(node) {
return node.type === 'ExpressionStatement'
})[0]
return expression && expression.expression && isPromise(expression.expression)
let functionNode = context
.getAncestors()
.filter((node) => {
return (
node.type === 'ArrowFunctionExpression' ||
node.type === 'FunctionExpression'
)
})
.reverse()[0]
while (
functionNode &&
functionNode.parent &&
functionNode.parent.type === 'MemberExpression' &&
functionNode.parent.object === functionNode &&
functionNode.parent.property.type === 'Identifier' &&
functionNode.parent.property.name === 'bind' &&
functionNode.parent.parent &&
functionNode.parent.parent.type === 'CallExpression' &&
functionNode.parent.parent.callee === functionNode.parent
) {
functionNode = functionNode.parent.parent
}
return functionNode && functionNode.parent && isPromise(functionNode.parent)
}

@@ -24,33 +41,56 @@

meta: {
type: 'suggestion',
docs: {
url: getDocsUrl('no-return-wrap')
}
url: getDocsUrl('no-return-wrap'),
},
messages: {
resolve: 'Avoid wrapping return values in Promise.resolve',
reject: 'Expected throw instead of Promise.reject',
},
schema: [
{
type: 'object',
properties: {
allowReject: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
},
create: function(context) {
create(context) {
const options = context.options[0] || {}
const allowReject = options.allowReject
return {
ReturnStatement: function(node) {
if (isInPromise(context)) {
if (node.argument) {
if (node.argument.type === 'CallExpression') {
if (node.argument.callee.type === 'MemberExpression') {
if (node.argument.callee.object.name === 'Promise') {
if (node.argument.callee.property.name === 'resolve') {
context.report({ node, message: resolveMessage })
} else if (
!allowReject &&
node.argument.callee.property.name === 'reject'
) {
context.report({ node, message: rejectMessage })
}
}
}
}
}
/**
* Checks a call expression, reporting if necessary.
* @param callExpression The call expression.
* @param node The node to report.
*/
function checkCallExpression({ callee }, node) {
if (
isInPromise(context) &&
callee.type === 'MemberExpression' &&
callee.object.name === 'Promise'
) {
if (callee.property.name === 'resolve') {
context.report({ node, messageId: 'resolve' })
} else if (!allowReject && callee.property.name === 'reject') {
context.report({ node, messageId: 'reject' })
}
}
}
}
return {
ReturnStatement(node) {
if (node.argument && node.argument.type === 'CallExpression') {
checkCallExpression(node.argument, node)
}
},
'ArrowFunctionExpression > CallExpression'(node) {
checkCallExpression(node, node)
},
}
},
}
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const {
isPromiseConstructorWithInlineExecutor,
} = require('./lib/is-promise-constructor')
module.exports = {
meta: {
type: 'suggestion',
docs: {
url: getDocsUrl('param-names')
url: getDocsUrl('param-names'),
},
fixable: 'code'
schema: [
{
type: 'object',
properties: {
resolvePattern: { type: 'string' },
rejectPattern: { type: 'string' },
},
additionalProperties: false,
},
],
},
create(context) {
const options = context.options[0] || {}
const resolvePattern = new RegExp(
options.resolvePattern || '^_?resolve$',
'u'
)
const rejectPattern = new RegExp(options.rejectPattern || '^_?reject$', 'u')
return {
NewExpression(node) {
if (node.callee.name === 'Promise' && node.arguments.length === 1) {
if (isPromiseConstructorWithInlineExecutor(node)) {
const params = node.arguments[0].params

@@ -22,22 +42,28 @@

if (
params[0].name !== 'resolve' ||
(params[1] && params[1].name !== 'reject')
) {
const resolveParamName = params[0] && params[0].name
if (resolveParamName && !resolvePattern.test(resolveParamName)) {
context.report({
node,
node: params[0],
message:
'Promise constructor parameters must be named resolve, reject',
fix(fixer) {
return [
fixer.replaceText(params[0], 'resolve'),
params[1] && fixer.replaceText(params[1], 'reject')
].filter(Boolean)
}
'Promise constructor parameters must be named to match "{{ resolvePattern }}"',
data: {
resolvePattern: resolvePattern.source,
},
})
}
const rejectParamName = params[1] && params[1].name
if (rejectParamName && !rejectPattern.test(rejectParamName)) {
context.report({
node: params[1],
message:
'Promise constructor parameters must be named to match "{{ rejectPattern }}"',
data: {
rejectPattern: rejectPattern.source,
},
})
}
}
}
},
}
}
},
}

@@ -5,9 +5,12 @@ 'use strict'

const errorMessage = 'Avoid callbacks. Prefer Async/Await.'
module.exports = {
meta: {
type: 'suggestion',
docs: {
url: getDocsUrl('prefer-await-to-callbacks')
}
url: getDocsUrl('prefer-await-to-callbacks'),
},
messages: {
error: 'Avoid callbacks. Prefer Async/Await.',
},
schema: [],
},

@@ -18,7 +21,7 @@ create(context) {

if (lastParam.name === 'callback' || lastParam.name === 'cb') {
context.report({ node: lastParam, message: errorMessage })
context.report({ node: lastParam, messageId: 'error' })
}
}
function isInsideYieldOrAwait() {
return context.getAncestors().some(parent => {
return context.getAncestors().some((parent) => {
return (

@@ -33,3 +36,3 @@ parent.type === 'AwaitExpression' || parent.type === 'YieldExpression'

if (node.callee.name === 'cb' || node.callee.name === 'callback') {
context.report({ node, message: errorMessage })
context.report({ node, messageId: 'error' })
return

@@ -54,5 +57,34 @@ }

}
if (arg.params && arg.params[0] && arg.params[0].name === 'err') {
// carve out exemption for map/filter/etc
const arrayMethods = [
'map',
'every',
'forEach',
'some',
'find',
'filter',
]
const isLodash =
node.callee.object &&
['lodash', 'underscore', '_'].includes(node.callee.object.name)
const callsArrayMethod =
node.callee.property &&
arrayMethods.includes(node.callee.property.name) &&
(node.arguments.length === 1 ||
(node.arguments.length === 2 && isLodash))
const isArrayMethod =
node.callee.name &&
arrayMethods.includes(node.callee.name) &&
node.arguments.length === 2
if (callsArrayMethod || isArrayMethod) return
// actually check for callbacks (I know this is the worst)
if (
arg.params &&
arg.params[0] &&
(arg.params[0].name === 'err' || arg.params[0].name === 'error')
) {
if (!isInsideYieldOrAwait()) {
context.report({ node: arg, message: errorMessage })
context.report({ node: arg, messageId: 'error' })
}

@@ -64,5 +96,5 @@ }

FunctionExpression: checkLastParamsForCallback,
ArrowFunctionExpression: checkLastParamsForCallback
ArrowFunctionExpression: checkLastParamsForCallback,
}
}
},
}
/**
* Rule: prefer-await-to-then
* Discourage using then() and instead use async/await.
* Discourage using then()/catch()/finally() and instead use async/await.
*/

@@ -12,10 +12,12 @@

meta: {
type: 'suggestion',
docs: {
url: getDocsUrl('prefer-await-to-then')
}
url: getDocsUrl('prefer-await-to-then'),
},
schema: [],
},
create: function(context) {
create(context) {
/** Returns true if node is inside yield or await expression. */
function isInsideYieldOrAwait() {
return context.getAncestors().some(parent => {
return context.getAncestors().some((parent) => {
return (

@@ -37,3 +39,3 @@ parent.type === 'AwaitExpression' || parent.type === 'YieldExpression'

return {
MemberExpression: function(node) {
'CallExpression > MemberExpression.callee'(node) {
if (isTopLevelScoped() || isInsideYieldOrAwait()) {

@@ -43,12 +45,17 @@ return

// if you're a then expression then you're probably a promise
if (node.property && node.property.name === 'then') {
// if you're a then/catch/finally expression then you're probably a promise
if (
node.property &&
(node.property.name === 'then' ||
node.property.name === 'catch' ||
node.property.name === 'finally')
) {
context.report({
node: node.property,
message: 'Prefer await to then().'
message: 'Prefer await to then()/catch()/finally().',
})
}
}
},
}
}
},
}

@@ -8,7 +8,9 @@ 'use strict'

meta: {
type: 'problem',
docs: {
description:
'Ensures the proper number of arguments are passed to Promise functions',
url: getDocsUrl('valid-params')
}
url: getDocsUrl('valid-params'),
},
schema: [],
},

@@ -25,2 +27,3 @@ create(context) {

// istanbul ignore next -- `isPromise` filters out others
switch (name) {

@@ -34,3 +37,3 @@ case 'resolve':

'Promise.{{ name }}() requires 0 or 1 arguments, but received {{ numArgs }}',
data: { name, numArgs }
data: { name, numArgs },
})

@@ -45,3 +48,3 @@ }

'Promise.{{ name }}() requires 1 or 2 arguments, but received {{ numArgs }}',
data: { name, numArgs }
data: { name, numArgs },
})

@@ -52,2 +55,4 @@ }

case 'all':
case 'allSettled':
case 'any':
case 'catch':

@@ -60,3 +65,3 @@ case 'finally':

'Promise.{{ name }}() requires 1 argument, but received {{ numArgs }}',
data: { name, numArgs }
data: { name, numArgs },
})

@@ -66,7 +71,8 @@ }

default:
// istanbul ignore next -- `isPromise` filters out others
break
}
}
},
}
}
},
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc