From 936802dab8a65a5a82614499e73aa5c3ed96d626 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Tue, 23 Jul 2024 17:56:25 +0800 Subject: [PATCH 1/2] feat: new rule `spec-only` to check for non-spec Promise methods; fixes #46 --- README.md | 1 + __tests__/spec-only.js | 56 +++++++++++++++++++++++++++++++++++++++++ docs/rules/spec-only.md | 24 ++++++++++++++++++ index.js | 1 + rules/spec-only.js | 51 +++++++++++++++++++++++++++++++++++++ 5 files changed, 133 insertions(+) create mode 100644 __tests__/spec-only.js create mode 100644 docs/rules/spec-only.md create mode 100644 rules/spec-only.js diff --git a/README.md b/README.md index 85d6f281..c3f4d28d 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ or start with the recommended rule set: | [param-names](docs/rules/param-names.md) | Enforce consistent param names and ordering when creating new promises. | ✅ | | | | | [prefer-await-to-callbacks](docs/rules/prefer-await-to-callbacks.md) | Prefer `async`/`await` to the callback pattern. | | | | | | [prefer-await-to-then](docs/rules/prefer-await-to-then.md) | Prefer `await` to `then()`/`catch()`/`finally()` for reading Promise values. | | | | | +| [spec-only](docs/rules/spec-only.md) | Disallow use of non-standard Promise static methods. | | | | | | [valid-params](docs/rules/valid-params.md) | Enforces the proper number of arguments are passed to Promise functions. | | ✅ | | | diff --git a/__tests__/spec-only.js b/__tests__/spec-only.js new file mode 100644 index 00000000..cd30ec32 --- /dev/null +++ b/__tests__/spec-only.js @@ -0,0 +1,56 @@ +'use strict' + +const rule = require('../rules/spec-only') +const { RuleTester } = require('./rule-tester') +const ruleTester = new RuleTester() + +ruleTester.run('spec-only', rule, { + valid: [ + 'Promise.resolve()', + 'Promise.reject()', + 'Promise.all()', + 'Promise.race()', + 'new Promise(function (resolve, reject) {})', + 'SomeClass.resolve()', + 'doSomething(Promise.all)', + { + code: 'Promise.permittedMethod()', + options: [ + { + allowedMethods: ['permittedMethod'], + }, + ], + }, + ], + invalid: [ + { + code: 'Promise.done()', + errors: [{ message: "Avoid using non-standard 'Promise.done'" }], + }, + { + code: 'Promise.something()', + errors: [{ message: "Avoid using non-standard 'Promise.something'" }], + }, + { + code: 'new Promise.done()', + errors: [{ message: "Avoid using non-standard 'Promise.done'" }], + }, + { + code: ` + function foo() { + var a = getA() + return Promise.done(a) + } + `, + errors: [{ message: "Avoid using non-standard 'Promise.done'" }], + }, + { + code: ` + function foo() { + getA(Promise.done) + } + `, + errors: [{ message: "Avoid using non-standard 'Promise.done'" }], + }, + ], +}) diff --git a/docs/rules/spec-only.md b/docs/rules/spec-only.md new file mode 100644 index 00000000..d46228c5 --- /dev/null +++ b/docs/rules/spec-only.md @@ -0,0 +1,24 @@ +# Disallow use of non-standard Promise static methods (`promise/spec-only`) + + + +It may become difficult to migrate code depending on non-standard Promise +extensions. This rule reports any such method usage. + +## Valid + +```js +const x = Promise.resolve('good') +``` + +## Invalid + +```js +const x = Promise.done('bad') +``` + +## Options + +### `allowedMethods` + +An array of allowed non-standard methods. Defaults to an empty array. diff --git a/index.js b/index.js index 41ff2f3a..79eeae9a 100644 --- a/index.js +++ b/index.js @@ -32,6 +32,7 @@ const pluginPromise = { 'no-return-in-finally': require('./rules/no-return-in-finally'), 'valid-params': require('./rules/valid-params'), 'no-multiple-resolved': require('./rules/no-multiple-resolved'), + 'spec-only': require('./rules/spec-only'), }, rulesConfig: { 'param-names': 1, diff --git a/rules/spec-only.js b/rules/spec-only.js new file mode 100644 index 00000000..e5ab90a9 --- /dev/null +++ b/rules/spec-only.js @@ -0,0 +1,51 @@ +'use strict' + +const PROMISE_STATICS = require('./lib/promise-statics') +const getDocsUrl = require('./lib/get-docs-url') + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Disallow use of non-standard Promise static methods.', + url: getDocsUrl('spec-only'), + }, + schema: [ + { + type: 'object', + properties: { + allowedMethods: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + additionalProperties: false, + }, + ], + messages: { + avoidNonStandard: "Avoid using non-standard 'Promise.{{ name }}'", + }, + }, + create(context) { + const { allowedMethods = [] } = context.options[0] || {} + + return { + MemberExpression(node) { + if ( + node.object.type === 'Identifier' && + node.object.name === 'Promise' && + !(node.property.name in PROMISE_STATICS) && + !allowedMethods.includes(node.property.name) + ) { + context.report({ + node, + messageId: 'avoidNonStandard', + data: { name: node.property.name }, + }) + } + }, + } + }, +} From 854b837d745ffbaab6ffa23b2ac7219828211f04 Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Wed, 24 Jul 2024 07:53:45 +0800 Subject: [PATCH 2/2] feat: allow `withResolvers` --- __tests__/no-new-statics.js | 6 ++++++ __tests__/spec-only.js | 1 + rules/lib/is-promise.js | 3 ++- rules/lib/promise-statics.js | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/__tests__/no-new-statics.js b/__tests__/no-new-statics.js index 96abe1ec..4ced5e87 100644 --- a/__tests__/no-new-statics.js +++ b/__tests__/no-new-statics.js @@ -10,6 +10,7 @@ ruleTester.run('no-new-statics', rule, { 'Promise.reject()', 'Promise.all()', 'Promise.race()', + 'Promise.withResolvers()', 'new Promise(function (resolve, reject) {})', 'new SomeClass()', 'SomeClass.resolve()', @@ -46,6 +47,11 @@ ruleTester.run('no-new-statics', rule, { output: 'Promise.race()', errors: [{ message: "Avoid calling 'new' on 'Promise.race()'" }], }, + { + code: 'new Promise.withResolvers()', + output: 'Promise.withResolvers()', + errors: [{ message: "Avoid calling 'new' on 'Promise.withResolvers()'" }], + }, { code: [ 'function foo() {', diff --git a/__tests__/spec-only.js b/__tests__/spec-only.js index cd30ec32..ea93a378 100644 --- a/__tests__/spec-only.js +++ b/__tests__/spec-only.js @@ -10,6 +10,7 @@ ruleTester.run('spec-only', rule, { 'Promise.reject()', 'Promise.all()', 'Promise.race()', + 'Promise.withResolvers()', 'new Promise(function (resolve, reject) {})', 'SomeClass.resolve()', 'doSomething(Promise.all)', diff --git a/rules/lib/is-promise.js b/rules/lib/is-promise.js index 857e3464..840dfb2f 100644 --- a/rules/lib/is-promise.js +++ b/rules/lib/is-promise.js @@ -29,7 +29,8 @@ function isPromise(expression) { expression.callee.type === 'MemberExpression' && expression.callee.object.type === 'Identifier' && expression.callee.object.name === 'Promise' && - PROMISE_STATICS[expression.callee.property.name]) + PROMISE_STATICS[expression.callee.property.name] && + expression.callee.property.name !== 'withResolvers') ) } diff --git a/rules/lib/promise-statics.js b/rules/lib/promise-statics.js index 6aa8646e..e42205a0 100644 --- a/rules/lib/promise-statics.js +++ b/rules/lib/promise-statics.js @@ -7,4 +7,5 @@ module.exports = { race: true, reject: true, resolve: true, + withResolvers: true, }