This commit is contained in:
darenhsu
2022-07-17 13:16:16 +08:00
parent 84759556ff
commit befd344ab0
28070 changed files with 4008428 additions and 1 deletions
+143
View File
@@ -0,0 +1,143 @@
## 4.2.1
- Added more use cases to `no-return-wrap`
## 4.0.1
- Remove `promise/param-names` fixer
([#146](https://github.com/xjamundx/eslint-plugin-promise/pull/146))
## 4.0.0
- Added fixer for `promise/no-new-statics` rule
([#133](https://github.com/xjamundx/eslint-plugin-promise/pull/133))
- Support ESLint v5
([#144](https://github.com/xjamundx/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))
## 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))
## 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`
## 3.5.0
- Added support for recommended settings using
`extends: plugin:promise/recommended`
## 3.4.2
- Fixed always return false positive with ternary (#31)
## 3.4.1
- 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
## 3.3.2
- Removed eslint from peerDeps
## 3.3.1
- 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
## 3.2.1
- Fix: `no-return-wrap` rule missing from index.js
## 3.2.0
- Added `no-return-wrap` rule
## 3.1.0
- Added multiple terminationMethods
## 3.0.1
- 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
## 2.0.1
- Fixed type in docs
## 2.0.0
- ESLint 3.0 Support
## 1.3.2
- Updated tests to run on eslint 2.0
- Fixed some issues with `no-native` rule
## 1.3.1
- Actually added `no-native` rule
## 1.3.0
- Added `no-native` rule
## 1.2.0
- Allow `throw` in `always-return` rule
- Added `terminationMethod` option to `catch-or-return` rule
## 1.1.0
- Added `catch-or-return` rule
## 1.0.8
- Fixed crash issues
## 1.0.0 - 1.0.7
- Lots of basic feature updates and doc changes
+132
View File
@@ -0,0 +1,132 @@
# eslint-plugin-promise
Enforce best practices for JavaScript promises.
[![travis-ci](https://travis-ci.org/xjamundx/eslint-plugin-promise.svg)](https://travis-ci.org/xjamundx/eslint-plugin-promise)
[![npm version](https://badge.fury.io/js/eslint-plugin-promise.svg)](https://www.npmjs.com/package/eslint-plugin-promise)
[![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)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation
You'll first need to install [ESLint](http://eslint.org):
```
$ npm install eslint --save-dev
```
Next, install `eslint-plugin-promise`:
```
$ npm install eslint-plugin-promise --save-dev
```
**Note:** If you installed ESLint globally (using the `-g` flag) then you must
also install `eslint-plugin-promise` globally.
## Usage
Add `promise` to the plugins section of your `.eslintrc.json` configuration
file. You can omit the `eslint-plugin-` prefix:
```json
{
"plugins": ["promise"]
}
```
Then configure the rules you want to use under the rules section.
```json
{
"rules": {
"promise/always-return": "error",
"promise/no-return-wrap": "error",
"promise/param-names": "error",
"promise/catch-or-return": "error",
"promise/no-native": "off",
"promise/no-nesting": "warn",
"promise/no-promise-in-callback": "warn",
"promise/no-callback-in-promise": "warn",
"promise/avoid-new": "warn",
"promise/no-new-statics": "error",
"promise/no-return-in-finally": "warn",
"promise/valid-params": "warn"
}
}
```
or start with the recommended rule set:
```json
{
"extends": ["plugin:promise/recommended"]
}
```
## Rules
| rule | description | recommended | fixable |
| -------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------- | -------- |
| [`catch-or-return`][catch-or-return] | Enforces the use of `catch()` on un-returned promises. | :bangbang: | |
| [`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 and ordering when creating new promises. | :bangbang: | |
| [`always-return`][always-return] | Return inside each `then()` to create readable and reusable Promise chains. | :bangbang: | |
| [`no-native`][no-native] | In an ES5 environment, make sure to create a `Promise` constructor before using. | | |
| [`no-nesting`][no-nesting] | Avoid nested `then()` or `catch()` statements | :warning: | |
| [`no-promise-in-callback`][no-promise-in-callback] | Avoid using promises inside of callbacks | :warning: | |
| [`no-callback-in-promise`][no-callback-in-promise] | Avoid calling `cb()` inside of a `then()` (use [nodeify][] instead) | :warning: | |
| [`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: | :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-callbacks`][prefer-await-to-callbacks] | Prefer async/await to the callback pattern | :seven: | |
**Key**
| icon | description |
| ---------- | ----------------------------------------------- |
| :bangbang: | Reports as error in recommended configuration |
| :warning: | Reports as warning in recommended configuration |
| :seven: | ES2017 Async Await rules |
| :wrench: | Rule is fixable with `eslint --fix` |
## Maintainers
- Jamund Ferguson - [@xjamundx][]
- Macklin Underdown - [@macklinu][]
## License
- (c) MMXV jden <mailto:jason@denizac.org> - ISC license.
- (c) 2016 Jamund Ferguson <mailto:jamund@gmail.com> - ISC license.
[catch-or-return]: docs/rules/catch-or-return.md
[no-return-wrap]: docs/rules/no-return-wrap.md
[param-names]: docs/rules/param-names.md
[always-return]: docs/rules/always-return.md
[no-native]: docs/rules/no-native.md
[no-nesting]: docs/rules/no-nesting.md
[no-promise-in-callback]: docs/rules/no-promise-in-callback.md
[no-callback-in-promise]: docs/rules/no-callback-in-promise.md
[avoid-new]: docs/rules/avoid-new.md
[no-new-statics]: docs/rules/no-new-statics.md
[no-return-in-finally]: docs/rules/no-return-in-finally.md
[valid-params]: docs/rules/valid-params.md
[prefer-await-to-then]: docs/rules/prefer-await-to-then.md
[prefer-await-to-callbacks]: docs/rules/prefer-await-to-callbacks.md
[nodeify]: https://www.npmjs.com/package/nodeify
[pify]: https://www.npmjs.com/package/pify
[@macklinu]: https://github.com/macklinu
[@xjamundx]: https://github.com/xjamundx
+46
View File
@@ -0,0 +1,46 @@
'use strict'
module.exports = {
rules: {
'param-names': require('./rules/param-names'),
'no-return-wrap': require('./rules/no-return-wrap'),
'always-return': require('./rules/always-return'),
'catch-or-return': require('./rules/catch-or-return'),
'prefer-await-to-callbacks': require('./rules/prefer-await-to-callbacks'),
'prefer-await-to-then': require('./rules/prefer-await-to-then'),
'no-native': require('./rules/no-native'),
'no-callback-in-promise': require('./rules/no-callback-in-promise'),
'no-promise-in-callback': require('./rules/no-promise-in-callback'),
'no-nesting': require('./rules/no-nesting'),
'avoid-new': require('./rules/avoid-new'),
'no-new-statics': require('./rules/no-new-statics'),
'no-return-in-finally': require('./rules/no-return-in-finally'),
'valid-params': require('./rules/valid-params')
},
rulesConfig: {
'param-names': 1,
'always-return': 1,
'no-return-wrap': 1,
'no-native': 0,
'catch-or-return': 1
},
configs: {
recommended: {
plugins: ['promise'],
rules: {
'promise/always-return': 'error',
'promise/no-return-wrap': 'error',
'promise/param-names': 'error',
'promise/catch-or-return': 'error',
'promise/no-native': 'off',
'promise/no-nesting': 'warn',
'promise/no-promise-in-callback': 'warn',
'promise/no-callback-in-promise': 'warn',
'promise/avoid-new': 'off',
'promise/no-new-statics': 'error',
'promise/no-return-in-finally': 'warn',
'promise/valid-params': 'warn'
}
}
}
}
+116
View File
@@ -0,0 +1,116 @@
{
"_args": [
[
"eslint-plugin-promise@4.2.1",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_development": true,
"_from": "eslint-plugin-promise@4.2.1",
"_id": "eslint-plugin-promise@4.2.1",
"_inBundle": false,
"_integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==",
"_location": "/eslint-plugin-promise",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "eslint-plugin-promise@4.2.1",
"name": "eslint-plugin-promise",
"escapedName": "eslint-plugin-promise",
"rawSpec": "4.2.1",
"saveSpec": null,
"fetchSpec": "4.2.1"
},
"_requiredBy": [
"/@nuxtjs/eslint-config"
],
"_resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz",
"_spec": "4.2.1",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "jden",
"email": "jason@denizac.org"
},
"bugs": {
"url": "https://github.com/xjamundx/eslint-plugin-promise/issues"
},
"description": "Enforce best practices for JavaScript promises",
"devDependencies": {
"doctoc": "^1.3.0",
"eslint": "^5.16.0",
"eslint-config-prettier": "^3.0.1",
"eslint-plugin-eslint-plugin": "^1.4.0",
"eslint-plugin-jest": "^21.21.0",
"eslint-plugin-node": "^7.0.1",
"eslint-plugin-prettier": "^2.6.2",
"husky": "^0.14.3",
"jest": "^24.5.0",
"jest-runner-eslint": "^0.7.3",
"lint-staged": "^7.2.2",
"prettier": "^1.14.2"
},
"engines": {
"node": ">=6"
},
"homepage": "https://github.com/xjamundx/eslint-plugin-promise#readme",
"jest": {
"projects": [
{
"displayName": "test",
"testEnvironment": "node"
},
{
"runner": "jest-runner-eslint",
"displayName": "lint",
"testMatch": [
"<rootDir>/rules/**/*.js",
"<rootDir>/__tests__/**/*.js",
"<rootDir>/index.js"
]
}
]
},
"keywords": [
"eslint",
"eslintplugin",
"eslint-plugin",
"promise",
"promises"
],
"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"
]
}
},
"name": "eslint-plugin-promise",
"prettier": {
"semi": false,
"singleQuote": true,
"proseWrap": "always"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/xjamundx/eslint-plugin-promise.git"
},
"scripts": {
"lint": "eslint rules __tests__ index.js",
"precommit": "lint-staged",
"test": "jest"
},
"version": "4.2.1"
}
+157
View File
@@ -0,0 +1,157 @@
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
function isFunctionWithBlockStatement(node) {
if (node.type === 'FunctionExpression') {
return true
}
if (node.type === 'ArrowFunctionExpression') {
return node.body.type === 'BlockStatement'
}
return false
}
function isThenCallExpression(node) {
return (
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.property.name === 'then'
)
}
function isFirstArgument(node) {
return (
node.parent && node.parent.arguments && node.parent.arguments[0] === node
)
}
function isInlineThenFunctionExpression(node) {
return (
isFunctionWithBlockStatement(node) &&
isThenCallExpression(node.parent) &&
isFirstArgument(node)
)
}
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') {
return true
}
return hasParentReturnStatement(node.parent)
}
return false
}
function peek(arr) {
return arr[arr.length - 1]
}
module.exports = {
meta: {
docs: {
url: getDocsUrl('always-return')
}
},
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 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> } } } ]
const funcInfoStack = []
function markCurrentBranchAsGood() {
const funcInfo = peek(funcInfoStack)
const currentBranchID = peek(funcInfo.branchIDStack)
if (funcInfo.branchInfoMap[currentBranchID]) {
funcInfo.branchInfoMap[currentBranchID].good = true
}
// else unreachable code
}
return {
ReturnStatement: markCurrentBranchAsGood,
ThrowStatement: markCurrentBranchAsGood,
onCodePathSegmentStart(segment, node) {
const funcInfo = peek(funcInfoStack)
funcInfo.branchIDStack.push(segment.id)
funcInfo.branchInfoMap[segment.id] = { good: false, node }
},
onCodePathSegmentEnd() {
const funcInfo = peek(funcInfoStack)
funcInfo.branchIDStack.pop()
},
onCodePathStart() {
funcInfoStack.push({
branchIDStack: [],
branchInfoMap: {}
})
},
onCodePathEnd(path, node) {
const funcInfo = funcInfoStack.pop()
if (!isInlineThenFunctionExpression(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
})
}
})
}
}
}
}
+25
View File
@@ -0,0 +1,25 @@
/**
* Rule: avoid-new
* Avoid creating new promises outside of utility libraries.
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
module.exports = {
meta: {
docs: {
url: getDocsUrl('avoid-new')
}
},
create(context) {
return {
NewExpression(node) {
if (node.callee.name === 'Promise') {
context.report({ node, message: 'Avoid creating new promises.' })
}
}
}
}
}
+95
View File
@@ -0,0 +1,95 @@
/**
* Rule: catch-or-return
* Ensures that promises either include a catch() handler
* or are returned (to be handled upstream)
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const isPromise = require('./lib/is-promise')
module.exports = {
meta: {
docs: {
url: getDocsUrl('catch-or-return')
},
messages: {
terminationMethod: 'Expected {{ terminationMethod }}() or return'
}
},
create(context) {
const options = context.options[0] || {}
const allowThen = options.allowThen
const allowFinally = options.allowFinally
let terminationMethod = options.terminationMethod || 'catch'
if (typeof terminationMethod === 'string') {
terminationMethod = [terminationMethod]
}
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(node) {
if (!isPromise(node.expression)) {
return
}
if (isAllowedPromiseTermination(node.expression)) {
return
}
context.report({
node,
messageId: 'terminationMethod',
data: { terminationMethod }
})
}
}
}
}
+19
View File
@@ -0,0 +1,19 @@
'use strict'
const pkg = require('../../package')
const REPO_URL = 'https://github.com/xjamundx/eslint-plugin-promise'
/**
* Generates the URL to documentation for the given rule name. It uses the
* package version to build the link to a tagged version of the
* documentation file.
*
* @param {string} ruleName - Name of the eslint rule
* @returns {string} URL to the documentation for the given rule
*/
function getDocsUrl(ruleName) {
return `${REPO_URL}/tree/v${pkg.version}/docs/rules/${ruleName}.md`
}
module.exports = getDocsUrl
+16
View File
@@ -0,0 +1,16 @@
/**
* Library: Has Promis eCallback
* Makes sure that an Expression node is part of a promise
* with callback functions (like then() or catch())
*/
'use strict'
function hasPromiseCallback(node) {
if (node.type !== 'CallExpression') return
if (node.callee.type !== 'MemberExpression') return
const propertyName = node.callee.property.name
return propertyName === 'then' || propertyName === 'catch'
}
module.exports = hasPromiseCallback
+13
View File
@@ -0,0 +1,13 @@
'use strict'
const isNamedCallback = require('./is-named-callback')
function isCallingBack(node, exceptions) {
const isCallExpression = node.type === 'CallExpression'
const callee = node.callee || {}
const nameIsCallback = isNamedCallback(callee.name, exceptions)
const isCB = isCallExpression && nameIsCallback
return isCB
}
module.exports = isCallingBack
+20
View File
@@ -0,0 +1,20 @@
'use strict'
const isInsidePromise = require('./is-inside-promise')
function isInsideCallback(node) {
const isCallExpression =
node.type === 'FunctionExpression' ||
node.type === 'ArrowFunctionExpression' ||
node.type === 'FunctionDeclaration' // this may be controversial
// it's totally fine to use promises inside promises
if (isInsidePromise(node)) return
const name = node.params && node.params[0] && node.params[0].name
const firstArgIsError = name === 'err' || name === 'error'
const isInACallback = isCallExpression && firstArgIsError
return isInACallback
}
module.exports = isInsideCallback
+15
View File
@@ -0,0 +1,15 @@
'use strict'
function isInsidePromise(node) {
const isFunctionExpression =
node.type === 'FunctionExpression' ||
node.type === 'ArrowFunctionExpression'
const parent = node.parent || {}
const callee = parent.callee || {}
const name = (callee.property && callee.property.name) || ''
const parentIsPromise = name === 'then' || name === 'catch'
const isInCB = isFunctionExpression && parentIsPromise
return isInCB
}
module.exports = isInsidePromise
+14
View File
@@ -0,0 +1,14 @@
'use strict'
let callbacks = ['done', 'cb', 'callback', 'next']
module.exports = function isNamedCallback(potentialCallbackName, exceptions) {
for (let i = 0; i < exceptions.length; i++) {
callbacks = callbacks.filter(item => {
return item !== exceptions[i]
})
}
return callbacks.some(trueCallbackName => {
return potentialCallbackName === trueCallbackName
})
}
+36
View File
@@ -0,0 +1,36 @@
/**
* Library: isPromise
* Makes sure that an Expression node is part of a promise.
*/
'use strict'
const PROMISE_STATICS = require('./promise-statics')
function isPromise(expression) {
return (
// hello.then()
(expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.property.name === 'then') ||
// hello.catch()
(expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.property.name === 'catch') ||
// hello.finally()
(expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.property.name === 'finally') ||
// somePromise.ANYTHING()
(expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
isPromise(expression.callee.object)) ||
// Promise.STATIC_METHOD()
(expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.object.type === 'Identifier' &&
expression.callee.object.name === 'Promise' &&
PROMISE_STATICS[expression.callee.property.name])
)
}
module.exports = isPromise
+8
View File
@@ -0,0 +1,8 @@
'use strict'
module.exports = {
all: true,
race: true,
reject: true,
resolve: true
}
+56
View File
@@ -0,0 +1,56 @@
/**
* Rule: no-callback-in-promise
* Avoid calling back inside of a promise
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const hasPromiseCallback = require('./lib/has-promise-callback')
const isInsidePromise = require('./lib/is-inside-promise')
const isCallback = require('./lib/is-callback')
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-callback-in-promise')
},
messages: {
callback: 'Avoid calling back inside of a promise.'
}
},
create(context) {
return {
CallExpression(node) {
const options = context.options[0] || {}
const exceptions = options.exceptions || []
if (!isCallback(node, exceptions)) {
// in general we send you packing if you're not a callback
// but we also need to watch out for whatever.then(cb)
if (hasPromiseCallback(node)) {
const name =
node.arguments && node.arguments[0] && node.arguments[0].name
if (
name === 'callback' ||
name === 'cb' ||
name === 'next' ||
name === 'done'
) {
context.report({
node: node.arguments[0],
messageId: 'callback'
})
}
}
return
}
if (context.getAncestors().some(isInsidePromise)) {
context.report({
node,
messageId: 'callback'
})
}
}
}
}
}
+59
View File
@@ -0,0 +1,59 @@
// Borrowed from here:
// https://github.com/colonyamerican/eslint-plugin-cah/issues/3
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
function isDeclared(scope, ref) {
return scope.variables.some(variable => {
if (variable.name !== ref.identifier.name) {
return false
}
if (!variable.defs || !variable.defs.length) {
return false
}
return true
})
}
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-native')
},
messages: {
name: '"{{name}}" is not defined.'
}
},
create(context) {
/**
* Checks for and reports reassigned constants
*
* @param {Scope} scope - an escope Scope object
* @returns {void}
* @private
*/
return {
'Program:exit'() {
const scope = context.getScope()
scope.implicit.left.forEach(ref => {
if (ref.identifier.name !== 'Promise') {
return
}
if (!isDeclared(scope, ref)) {
context.report({
node: ref.identifier,
messageId: 'name',
data: { name: ref.identifier.name }
})
}
})
}
}
}
}
+28
View File
@@ -0,0 +1,28 @@
/**
* Rule: no-nesting
* Avoid nesting your promises.
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const hasPromiseCallback = require('./lib/has-promise-callback')
const isInsidePromise = require('./lib/is-inside-promise')
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-nesting')
}
},
create(context) {
return {
CallExpression(node) {
if (!hasPromiseCallback(node)) return
if (context.getAncestors().some(isInsidePromise)) {
context.report({ node, message: 'Avoid nesting promises.' })
}
}
}
}
}
+36
View File
@@ -0,0 +1,36 @@
'use strict'
const PROMISE_STATICS = require('./lib/promise-statics')
const getDocsUrl = require('./lib/get-docs-url')
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-new-statics')
},
fixable: 'code'
},
create(context) {
return {
NewExpression(node) {
if (
node.callee.type === 'MemberExpression' &&
node.callee.object.name === 'Promise' &&
PROMISE_STATICS[node.callee.property.name]
) {
context.report({
node,
message: "Avoid calling 'new' on 'Promise.{{ name }}()'",
data: { name: node.callee.property.name },
fix(fixer) {
return fixer.replaceTextRange(
[node.start, node.start + 'new '.length],
''
)
}
})
}
}
}
}
}
+39
View File
@@ -0,0 +1,39 @@
/**
* Rule: no-promise-in-callback
* Discourage using promises inside of callbacks.
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const isPromise = require('./lib/is-promise')
const isInsideCallback = require('./lib/is-inside-callback')
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-promise-in-callback')
}
},
create(context) {
return {
CallExpression(node) {
if (!isPromise(node)) return
// if i'm returning the promise, it's probably not really a callback
// function, and I should be okay....
if (node.parent.type === 'ReturnStatement') return
// what about if the parent is an ArrowFunctionExpression
// would that imply an implicit return?
if (context.getAncestors().some(isInsideCallback)) {
context.report({
node: node.callee,
message: 'Avoid using promises inside of callbacks.'
})
}
}
}
}
}
+43
View File
@@ -0,0 +1,43 @@
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const isPromise = require('./lib/is-promise')
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-return-in-finally')
}
},
create(context) {
return {
CallExpression(node) {
if (isPromise(node)) {
if (
node.callee &&
node.callee.property &&
node.callee.property.name === 'finally'
) {
if (
node.arguments &&
node.arguments[0] &&
node.arguments[0].body &&
node.arguments[0].body.body
) {
if (
node.arguments[0].body.body.some(statement => {
return statement.type === 'ReturnStatement'
})
) {
context.report({
node: node.callee.property,
message: 'No return in finally'
})
}
}
}
}
}
}
}
}
+75
View File
@@ -0,0 +1,75 @@
/**
* Rule: no-return-wrap function
* Prevents uneccessary wrapping of results in Promise.resolve
* or Promise.reject as the Promise will do that for us
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const isPromise = require('./lib/is-promise')
function isInPromise(context) {
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)
}
module.exports = {
meta: {
docs: {
url: getDocsUrl('no-return-wrap')
},
messages: {
resolve: 'Avoid wrapping return values in Promise.resolve',
reject: 'Expected throw instead of Promise.reject'
}
},
create(context) {
const options = context.options[0] || {}
const allowReject = options.allowReject
return {
ReturnStatement(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, messageId: 'resolve' })
} else if (
!allowReject &&
node.argument.callee.property.name === 'reject'
) {
context.report({ node, messageId: 'reject' })
}
}
}
}
}
}
}
}
}
}
+36
View File
@@ -0,0 +1,36 @@
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
module.exports = {
meta: {
docs: {
url: getDocsUrl('param-names')
},
fixable: 'code'
},
create(context) {
return {
NewExpression(node) {
if (node.callee.name === 'Promise' && node.arguments.length === 1) {
const params = node.arguments[0].params
if (!params || !params.length) {
return
}
if (
params[0].name !== 'resolve' ||
(params[1] && params[1].name !== 'reject')
) {
context.report({
node,
message:
'Promise constructor parameters must be named resolve, reject'
})
}
}
}
}
}
}
+64
View File
@@ -0,0 +1,64 @@
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
module.exports = {
meta: {
docs: {
url: getDocsUrl('prefer-await-to-callbacks')
},
messages: {
error: 'Avoid callbacks. Prefer Async/Await.'
}
},
create(context) {
function checkLastParamsForCallback(node) {
const lastParam = node.params[node.params.length - 1] || {}
if (lastParam.name === 'callback' || lastParam.name === 'cb') {
context.report({ node: lastParam, messageId: 'error' })
}
}
function isInsideYieldOrAwait() {
return context.getAncestors().some(parent => {
return (
parent.type === 'AwaitExpression' || parent.type === 'YieldExpression'
)
})
}
return {
CallExpression(node) {
// Callbacks aren't allowed.
if (node.callee.name === 'cb' || node.callee.name === 'callback') {
context.report({ node, messageId: 'error' })
return
}
// Then-ables aren't allowed either.
const args = node.arguments
const lastArgIndex = args.length - 1
const arg = lastArgIndex > -1 && node.arguments[lastArgIndex]
if (
(arg && arg.type === 'FunctionExpression') ||
arg.type === 'ArrowFunctionExpression'
) {
// Ignore event listener callbacks.
if (
node.callee.property &&
(node.callee.property.name === 'on' ||
node.callee.property.name === 'once')
) {
return
}
if (arg.params && arg.params[0] && arg.params[0].name === 'err') {
if (!isInsideYieldOrAwait()) {
context.report({ node: arg, messageId: 'error' })
}
}
}
},
FunctionDeclaration: checkLastParamsForCallback,
FunctionExpression: checkLastParamsForCallback,
ArrowFunctionExpression: checkLastParamsForCallback
}
}
}
+51
View File
@@ -0,0 +1,51 @@
/**
* Rule: prefer-await-to-then
* Discourage using then() and instead use async/await.
*/
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
module.exports = {
meta: {
docs: {
url: getDocsUrl('prefer-await-to-then')
}
},
create(context) {
/** Returns true if node is inside yield or await expression. */
function isInsideYieldOrAwait() {
return context.getAncestors().some(parent => {
return (
parent.type === 'AwaitExpression' || parent.type === 'YieldExpression'
)
})
}
/**
* Returns true if node is created at the top-level scope.
* Await statements are not allowed at the top level,
* only within function declarations.
*/
function isTopLevelScoped() {
return context.getScope().block.type === 'Program'
}
return {
MemberExpression(node) {
if (isTopLevelScoped() || isInsideYieldOrAwait()) {
return
}
// if you're a then expression then you're probably a promise
if (node.property && node.property.name === 'then') {
context.report({
node: node.property,
message: 'Prefer await to then().'
})
}
}
}
}
}
+65
View File
@@ -0,0 +1,65 @@
'use strict'
const getDocsUrl = require('./lib/get-docs-url')
const isPromise = require('./lib/is-promise')
module.exports = {
meta: {
docs: {
description:
'Ensures the proper number of arguments are passed to Promise functions',
url: getDocsUrl('valid-params')
}
},
create(context) {
return {
CallExpression(node) {
if (!isPromise(node)) {
return
}
const name = node.callee.property.name
const numArgs = node.arguments.length
switch (name) {
case 'resolve':
case 'reject':
if (numArgs > 1) {
context.report({
node,
message:
'Promise.{{ name }}() requires 0 or 1 arguments, but received {{ numArgs }}',
data: { name, numArgs }
})
}
break
case 'then':
if (numArgs < 1 || numArgs > 2) {
context.report({
node,
message:
'Promise.{{ name }}() requires 1 or 2 arguments, but received {{ numArgs }}',
data: { name, numArgs }
})
}
break
case 'race':
case 'all':
case 'catch':
case 'finally':
if (numArgs !== 1) {
context.report({
node,
message:
'Promise.{{ name }}() requires 1 argument, but received {{ numArgs }}',
data: { name, numArgs }
})
}
break
default:
break
}
}
}
}
}