From c8fbf000e337d3b099e89465adda3be8e0541554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Mon, 18 Nov 2024 20:20:07 +0800 Subject: [PATCH] feat(no-sync): Add ignores option (#386) Co-authored-by: scagood <2230835+scagood@users.noreply.github.com> --- docs/rules/no-sync.md | 22 ++++++++++++++++++++++ lib/rules/no-sync.js | 38 +++++++++++++++++++------------------- tests/lib/rules/no-sync.js | 24 ++++++++++++++++++++---- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/docs/rules/no-sync.md b/docs/rules/no-sync.md index 30789ca7..dc400974 100644 --- a/docs/rules/no-sync.md +++ b/docs/rules/no-sync.md @@ -10,6 +10,8 @@ This rule is aimed at preventing synchronous methods from being called in Node.j ### Options +#### allowAtRootLevel + This rule has an optional object option `{ allowAtRootLevel: }`, which determines whether synchronous methods should be allowed at the top level of a file, outside of any functions. This option defaults to `false`. Examples of **incorrect** code for this rule with the default `{ allowAtRootLevel: false }` option: @@ -56,6 +58,26 @@ Examples of **correct** code for this rule with the `{ allowAtRootLevel: true }` fs.readFileSync(somePath).toString(); ``` +#### ignores + +You can `ignore` specific function names using this option. + +Examples of **incorrect** code for this rule with the `{ ignores: ['readFileSync'] }` option: + +```js +/*eslint n/no-sync: ["error", { ignores: ['readFileSync'] }] */ + +fs.readdirSync(somePath); +``` + +Examples of **correct** code for this rule with the `{ ignores: ['readFileSync'] }` option: + +```js +/*eslint n/no-sync: ["error", { ignores: ['readFileSync'] }] */ + +fs.readFileSync(somePath); +``` + ## 🔎 Implementation - [Rule source](../../lib/rules/no-sync.js) diff --git a/lib/rules/no-sync.js b/lib/rules/no-sync.js index f32f0fed..fb0de8e6 100644 --- a/lib/rules/no-sync.js +++ b/lib/rules/no-sync.js @@ -4,23 +4,13 @@ */ "use strict" -const allowedAtRootLevelSelector = [ +const selectors = [ // fs.readFileSync() - ":function MemberExpression > Identifier[name=/Sync$/]", // readFileSync.call(null, 'path') - ":function MemberExpression > Identifier[name=/Sync$/]", + "CallExpression > MemberExpression.callee Identifier[name=/Sync$/]", // readFileSync() - ":function :not(MemberExpression) > Identifier[name=/Sync$/]", -].join(",") - -const disallowedAtRootLevelSelector = [ - // fs.readFileSync() - "MemberExpression > Identifier[name=/Sync$/]", - // readFileSync.call(null, 'path') - "MemberExpression > Identifier[name=/Sync$/]", - // readFileSync() - ":not(MemberExpression) > Identifier[name=/Sync$/]", -].join(",") + "CallExpression > Identifier[name=/Sync$/]", +] /** @type {import('eslint').Rule.RuleModule} */ module.exports = { @@ -40,6 +30,11 @@ module.exports = { type: "boolean", default: false, }, + ignores: { + type: "array", + items: { type: "string" }, + default: [], + }, }, additionalProperties: false, }, @@ -50,17 +45,22 @@ module.exports = { }, create(context) { - const selector = context.options[0]?.allowAtRootLevel - ? allowedAtRootLevelSelector - : disallowedAtRootLevelSelector + const options = context.options[0] ?? {} + const ignores = options.ignores ?? [] + const selector = options.allowAtRootLevel + ? selectors.map(selector => `:function ${selector}`) + : selectors return { /** - * [node description] * @param {import('estree').Identifier & {parent: import('estree').Node}} node * @returns {void} */ - [selector](node) { + [selector.join(",")](node) { + if (ignores.includes(node.name)) { + return + } + context.report({ node: node.parent, messageId: "noSync", diff --git a/tests/lib/rules/no-sync.js b/tests/lib/rules/no-sync.js index 0de41ef5..0c7141ee 100644 --- a/tests/lib/rules/no-sync.js +++ b/tests/lib/rules/no-sync.js @@ -10,6 +10,10 @@ const rule = require("../../../lib/rules/no-sync") new RuleTester().run("no-sync", rule, { valid: [ "var foo = fs.foo.foo();", + // Allow non-function called to be ignored + "fs.fooSync;", + "fooSync;", + "() => fooSync;", { code: "var foo = fs.fooSync;", options: [{ allowAtRootLevel: true }], @@ -26,6 +30,11 @@ new RuleTester().run("no-sync", rule, { code: "if (true) {fooSync();}", options: [{ allowAtRootLevel: true }], }, + // ignores + { + code: "fooSync();", + options: [{ ignores: ["fooSync"] }], + }, ], invalid: [ { @@ -90,7 +99,7 @@ new RuleTester().run("no-sync", rule, { ], }, { - code: "var foo = fs.fooSync;", + code: "function someFunction() {fs.fooSync();}", errors: [ { messageId: "noSync", @@ -101,6 +110,7 @@ new RuleTester().run("no-sync", rule, { }, { code: "function someFunction() {fs.fooSync();}", + options: [{ allowAtRootLevel: true }], errors: [ { messageId: "noSync", @@ -110,7 +120,7 @@ new RuleTester().run("no-sync", rule, { ], }, { - code: "function someFunction() {fs.fooSync();}", + code: "var a = function someFunction() {fs.fooSync();}", options: [{ allowAtRootLevel: true }], errors: [ { @@ -120,9 +130,15 @@ new RuleTester().run("no-sync", rule, { }, ], }, + // ignores { - code: "var a = function someFunction() {fs.fooSync();}", - options: [{ allowAtRootLevel: true }], + code: "() => {fs.fooSync();}", + options: [ + { + allowAtRootLevel: true, + ignores: ["barSync"], + }, + ], errors: [ { messageId: "noSync",