Socket
Socket
Sign inDemoInstall

eslint-plugin-promise

Package Overview
Dependencies
102
Maintainers
3
Versions
40
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 6.0.1 to 6.1.0

rules/lib/is-promise-constructor.js

34

CHANGELOG.md

@@ -0,1 +1,5 @@

## 6.0.2
- Added tests for @typescript-eslint/parser support
## 6.0.1

@@ -24,3 +28,3 @@

- Exempted array methods in prefer-await-to-callbacks
([#212](https://github.com/xjamundx/eslint-plugin-promise/issues/212))
([#212](https://github.com/eslint-community/eslint-plugin-promise/issues/212))

@@ -37,4 +41,4 @@ ## 5.0.0

- https://github.com/xjamundx/eslint-plugin-promise/pull/202
- Udpated jest
- https://github.com/eslint-community/eslint-plugin-promise/pull/202
- Updated jest

@@ -53,3 +57,3 @@ ## 4.2.2

- Remove `promise/param-names` fixer
([#146](https://github.com/xjamundx/eslint-plugin-promise/pull/146))
([#146](https://github.com/eslint-community/eslint-plugin-promise/pull/146))

@@ -59,5 +63,5 @@ ## 4.0.0

- Added fixer for `promise/no-new-statics` rule
([#133](https://github.com/xjamundx/eslint-plugin-promise/pull/133))
([#133](https://github.com/eslint-community/eslint-plugin-promise/pull/133))
- Support ESLint v5
([#144](https://github.com/xjamundx/eslint-plugin-promise/pull/144))
([#144](https://github.com/eslint-community/eslint-plugin-promise/pull/144))

@@ -70,10 +74,10 @@ This is a breaking change that drops support for Node v4. In order to use ESLint

- Removed `promise/avoid-new` from recommended configuration
([#119](https://github.com/xjamundx/eslint-plugin-promise/pull/119))
([#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/xjamundx/eslint-plugin-promise/pull/117))
([#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/xjamundx/eslint-plugin-promise/pull/126))
([#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/xjamundx/eslint-plugin-promise/pull/120))
([#121](https://github.com/xjamundx/eslint-plugin-promise/pull/121))
([#120](https://github.com/eslint-community/eslint-plugin-promise/pull/120))
([#121](https://github.com/eslint-community/eslint-plugin-promise/pull/121))

@@ -83,9 +87,9 @@ ## 3.7.0

- Added `promise/valid-params` rule
([#85](https://github.com/xjamundx/eslint-plugin-promise/pull/85))
([#85](https://github.com/eslint-community/eslint-plugin-promise/pull/85))
- Added `promise/no-new-statics` rule
([#82](https://github.com/xjamundx/eslint-plugin-promise/pull/82))
([#82](https://github.com/eslint-community/eslint-plugin-promise/pull/82))
- Added fixer for `promise/param-names` rule
([#99](https://github.com/xjamundx/eslint-plugin-promise/pull/99))
([#99](https://github.com/eslint-community/eslint-plugin-promise/pull/99))
- Added rule documentation to each rule
([#91](https://github.com/xjamundx/eslint-plugin-promise/pull/91))
([#91](https://github.com/eslint-community/eslint-plugin-promise/pull/91))

@@ -92,0 +96,0 @@ ## 3.6.0

@@ -19,2 +19,3 @@ 'use strict'

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

@@ -21,0 +22,0 @@ rulesConfig: {

{
"name": "eslint-plugin-promise",
"version": "6.0.1",
"version": "6.1.0",
"description": "Enforce best practices for JavaScript promises",

@@ -17,8 +17,11 @@ "keywords": [

],
"repository": "https://github.com/xjamundx/eslint-plugin-promise",
"homepage": "https://github.com/xjamundx/eslint-plugin-promise",
"bugs": "https://github.com/xjamundx/eslint-plugin-promise/issues",
"repository": {
"type": "git",
"url": "https://github.com/eslint-community/eslint-plugin-promise"
},
"homepage": "https://github.com/eslint-community/eslint-plugin-promise",
"bugs": "https://github.com/eslint-community/eslint-plugin-promise/issues",
"scripts": {
"format": "prettier --write .",
"lint": "eslint .",
"lint": "eslint --report-unused-disable-directives .",
"prepare": "husky install",

@@ -28,14 +31,15 @@ "test": "jest --coverage"

"devDependencies": {
"doctoc": "^2.1.0",
"eslint": "^8.5.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-eslint-plugin": "^4.1.0",
"eslint-plugin-jest": "^25.3.0",
"@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.0.0",
"eslint-plugin-prettier": "^4.2.1",
"husky": "^7.0.4",
"jest": "^27.4.5",
"jest-runner-eslint": "^1.0.0",
"lint-staged": "^12.1.2",
"prettier": "^2.5.1"
"jest": "^28.1.3",
"lint-staged": "^12.5.0",
"prettier": "^2.7.1",
"typescript": "^4.8.4"
},

@@ -55,3 +59,3 @@ "peerDependencies": {

"prettier --write",
"eslint --fix"
"eslint --report-unused-disable-directives --fix"
],

@@ -75,19 +79,4 @@ "*.+(json|md)": [

}
},
"projects": [
{
"displayName": "test",
"testEnvironment": "node"
},
{
"runner": "jest-runner-eslint",
"displayName": "lint",
"testMatch": [
"<rootDir>/rules/**/*.js",
"<rootDir>/__tests__/**/*.js",
"<rootDir>/index.js"
]
}
]
}
}
}

@@ -5,3 +5,3 @@ # 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)

@@ -96,2 +96,3 @@ [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)

| [`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 | | |

@@ -132,2 +133,3 @@ **Key**

[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

@@ -134,0 +136,0 @@ [pify]: https://www.npmjs.com/package/pify

@@ -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,20 +66,62 @@ )

function hasParentReturnStatement(node) {
// istanbul ignore else -- not reachable given not checking `Program`
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 -- not reachable given not checking `Program`
// istanbul ignore next
return false
}
/**
* @template T
* @param {T[]} arr
* @returns {T}
*/
function peek(arr) {

@@ -69,34 +137,55 @@ return arr[arr.length - 1]

},
schema: [],
schema: [
{
type: 'object',
properties: {
ignoreLastCallback: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
},
create(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 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
// 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> } } } ]
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 = []

@@ -114,5 +203,9 @@

return {
ReturnStatement: markCurrentBranchAsGood,
ThrowStatement: markCurrentBranchAsGood,
'ReturnStatement:exit': markCurrentBranchAsGood,
'ThrowStatement:exit': markCurrentBranchAsGood,
/**
* @param {CodePathSegment} segment
* @param {Node} node
*/
onCodePathSegmentStart(segment, node) {

@@ -136,2 +229,6 @@ const funcInfo = peek(funcInfoStack)

/**
* @param {CodePath} path
* @param {Node} node
*/
onCodePathEnd(path, node) {

@@ -144,2 +241,6 @@ const funcInfo = funcInfoStack.pop()

if (ignoreLastCallback && isLastCallback(node)) {
return
}
path.finalSegments.forEach((segment) => {

@@ -149,6 +250,2 @@ const id = segment.id

if (!branch.good) {
if (hasParentReturnStatement(branch.node)) {
return
}
context.report({

@@ -155,0 +252,0 @@ message: 'Each then() should return a value or throw',

'use strict'
const REPO_URL = 'https://github.com/xjamundx/eslint-plugin-promise'
const REPO_URL = 'https://github.com/eslint-community/eslint-plugin-promise'

@@ -5,0 +5,0 @@ /**

@@ -9,2 +9,22 @@ /**

/**
* @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) {

@@ -11,0 +31,0 @@ // istanbul ignore if -- only being called within `CallExpression`

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

all: true,
allSettled: true,
any: true,
race: true,

@@ -7,0 +9,0 @@ reject: true,

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

*
* @param {Scope} scope - an escope Scope object
* @param {Scope} scope - an eslint-scope Scope object
* @returns {void}

@@ -51,4 +51,4 @@ * @private

scope.implicit.left ||
/* istanbul ignore next
* Fixes https://github.com/xjamundx/eslint-plugin-promise/issues/205.
/**
* Fixes https://github.com/eslint-community/eslint-plugin-promise/issues/205.
* The problem was that @typescript-eslint has a scope manager

@@ -55,0 +55,0 @@ * which has `leftToBeResolved` instead of the default `left`.

@@ -21,8 +21,96 @@ /**

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
}
}
}
return {
':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.',
})
},

@@ -29,0 +117,0 @@ }

'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const {
isPromiseConstructorWithInlineExecutor,
} = require('./lib/is-promise-constructor')

@@ -11,8 +14,24 @@ module.exports = {

},
schema: [],
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

@@ -24,14 +43,24 @@

if (
(params[0].name !== 'resolve' && params[0].name !== '_resolve') ||
(params[1] &&
params[1].name !== 'reject' &&
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',
'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,
},
})
}
}

@@ -38,0 +67,0 @@ },

@@ -38,3 +38,3 @@ /**

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

@@ -41,0 +41,0 @@ return

@@ -51,2 +51,4 @@ 'use strict'

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

@@ -53,0 +55,0 @@ case 'finally':

SocketSocket SOC 2 Logo

Product

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

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc