From 125bdfe90af58d9606172a92fc96e1db2006524d Mon Sep 17 00:00:00 2001 From: scagood <2230835+scagood@users.noreply.github.com> Date: Fri, 26 Jan 2024 19:13:36 +0000 Subject: [PATCH 1/9] feat: Add shebangs to all ignored executable files --- lib/rules/shebang.js | 45 ++++++++++++++++---- tests/fixtures/shebang/ignored/executable.js | 2 + tests/fixtures/shebang/ignored/normal.js | 1 + tests/fixtures/shebang/ignored/package.json | 7 +++ tests/lib/rules/shebang.js | 24 +++++++++++ 5 files changed, 70 insertions(+), 9 deletions(-) create mode 100755 tests/fixtures/shebang/ignored/executable.js create mode 100644 tests/fixtures/shebang/ignored/normal.js create mode 100644 tests/fixtures/shebang/ignored/package.json diff --git a/lib/rules/shebang.js b/lib/rules/shebang.js index 179f347c..d99fbee8 100644 --- a/lib/rules/shebang.js +++ b/lib/rules/shebang.js @@ -5,8 +5,11 @@ "use strict" const path = require("path") +const { accessSync, constants } = require("node:fs") + const getConvertPath = require("../util/get-convert-path") const getPackageJson = require("../util/get-package-json") +const getNpmignore = require("../util/get-npmignore") const NODE_SHEBANG = "#!/usr/bin/env node\n" const SHEBANG_PATTERN = /^(#!.+?)?(\r)?\n/u @@ -21,6 +24,15 @@ function simulateNodeResolutionAlgorithm(filePath, binField) { return possibilities.includes(binField) } +function isExecutable(path) { + try { + accessSync(path, constants.X_OK) + return true + } catch (error) { + return false + } +} + /** * Checks whether or not a given path is a `bin` file. * @@ -95,26 +107,41 @@ module.exports = { }, create(context) { const sourceCode = context.sourceCode ?? context.getSourceCode() // TODO: just use context.sourceCode when dropping eslint < v9 - let filePath = context.filename ?? context.getFilename() + const filePath = context.filename ?? context.getFilename() if (filePath === "") { return {} } - filePath = path.resolve(filePath) const p = getPackageJson(filePath) if (!p) { return {} } - const basedir = path.dirname(p.filePath) - filePath = path.join( - basedir, - getConvertPath(context)( - path.relative(basedir, filePath).replace(/\\/gu, "/") - ) + const packageDirectory = path.dirname(p.filePath) + + const originalAbsolutePath = path.resolve(filePath) + const originalRelativePath = path + .relative(packageDirectory, originalAbsolutePath) + .replace(/\\/gu, "/") + + const convertedRelativePath = + getConvertPath(context)(originalRelativePath) + const convertedAbsolutePath = path.resolve( + packageDirectory, + convertedRelativePath + ) + + const npmignore = getNpmignore(convertedAbsolutePath) + + const isFileBin = isBinFile( + convertedAbsolutePath, + p.bin, + packageDirectory ) + const isFileIgnored = npmignore.match(convertedRelativePath) + const isFileExecutable = isExecutable(originalAbsolutePath) - const needsShebang = isBinFile(filePath, p.bin, basedir) + const needsShebang = isFileBin || (isFileIgnored && isFileExecutable) const info = getShebangInfo(sourceCode) return { diff --git a/tests/fixtures/shebang/ignored/executable.js b/tests/fixtures/shebang/ignored/executable.js new file mode 100755 index 00000000..06bcbf4e --- /dev/null +++ b/tests/fixtures/shebang/ignored/executable.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +module.exports = {} diff --git a/tests/fixtures/shebang/ignored/normal.js b/tests/fixtures/shebang/ignored/normal.js new file mode 100644 index 00000000..4ba52ba2 --- /dev/null +++ b/tests/fixtures/shebang/ignored/normal.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/tests/fixtures/shebang/ignored/package.json b/tests/fixtures/shebang/ignored/package.json new file mode 100644 index 00000000..f564f113 --- /dev/null +++ b/tests/fixtures/shebang/ignored/package.json @@ -0,0 +1,7 @@ +{ + "name": "test", + "version": "0.0.0", + "files": [ + "./bin/test.js" + ] +} diff --git a/tests/lib/rules/shebang.js b/tests/lib/rules/shebang.js index 47d9460c..9ad76e49 100644 --- a/tests/lib/rules/shebang.js +++ b/tests/lib/rules/shebang.js @@ -124,6 +124,16 @@ ruleTester.run("shebang", rule, { filename: fixture("object-bin/bin/index.js"), code: "#!/usr/bin/env node\nhello();", }, + + // ignored files are executable + { + filename: fixture("ignored/executable.js"), + code: "#!/usr/bin/env node\nhello();", + }, + { + filename: fixture("ignored/normal.js"), + code: "hello();", + }, ], invalid: [ { @@ -319,5 +329,19 @@ ruleTester.run("shebang", rule, { output: "#!/usr/bin/env node\nhello();", errors: ['This file needs shebang "#!/usr/bin/env node".'], }, + + // ignored files are executable + { + filename: fixture("ignored/executable.js"), + code: "hello();", + output: "#!/usr/bin/env node\nhello();", + errors: ['This file needs shebang "#!/usr/bin/env node".'], + }, + { + filename: fixture("ignored/normal.js"), + code: "#!/usr/bin/env node\nhello();", + output: "hello();", + errors: ["This file needs no shebang."], + }, ], }) From b2bf1c5a6df5be1f8230c10843cb45f63b90019c Mon Sep 17 00:00:00 2001 From: scagood <2230835+scagood@users.noreply.github.com> Date: Fri, 26 Jan 2024 20:06:36 +0000 Subject: [PATCH 2/9] chore: Add names to shebang tests --- tests/lib/rules/shebang.js | 56 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/tests/lib/rules/shebang.js b/tests/lib/rules/shebang.js index 9ad76e49..865e2782 100644 --- a/tests/lib/rules/shebang.js +++ b/tests/lib/rules/shebang.js @@ -17,63 +17,82 @@ function fixture(name) { return path.resolve(__dirname, "../../fixtures/shebang", name) } +/** @type {import('eslint').RuleTester} */ const ruleTester = new RuleTester() ruleTester.run("shebang", rule, { valid: [ { + name: "string-bin/bin/test.js", filename: fixture("string-bin/bin/test.js"), code: "#!/usr/bin/env node\nhello();", }, { + name: "string-bin/lib/test.js", filename: fixture("string-bin/lib/test.js"), code: "hello();", }, { + name: "object-bin/bin/a.js", filename: fixture("object-bin/bin/a.js"), code: "#!/usr/bin/env node\nhello();", }, { + name: "object-bin/bin/b.js", filename: fixture("object-bin/bin/b.js"), code: "#!/usr/bin/env node\nhello();", }, { + name: "object-bin/bin/c.js", filename: fixture("object-bin/bin/c.js"), code: "hello();", }, { + name: "no-bin-field/lib/test.js", filename: fixture("no-bin-field/lib/test.js"), code: "hello();", }, - "#!/usr/bin/env node\nhello();", - "hello();", + { + name: " with shebang", + code: "#!/usr/bin/env node\nhello();", + }, + { + name: " without shebang", + code: "hello();", + }, // convertPath { + name: "convertPath - string-bin/src/bin/test.js", filename: fixture("string-bin/src/bin/test.js"), code: "#!/usr/bin/env node\nhello();", options: [{ convertPath: { "src/**": ["^src/(.+)$", "$1"] } }], }, { + name: "convertPath - string-bin/src/lib/test.js", filename: fixture("string-bin/src/lib/test.js"), code: "hello();", options: [{ convertPath: { "src/**": ["^src/(.+)$", "$1"] } }], }, { + name: "convertPath - object-bin/src/bin/a.js", filename: fixture("object-bin/src/bin/a.js"), code: "#!/usr/bin/env node\nhello();", options: [{ convertPath: { "src/**": ["^src/(.+)$", "$1"] } }], }, { + name: "convertPath - object-bin/src/bin/b.js", filename: fixture("object-bin/src/bin/b.js"), code: "#!/usr/bin/env node\nhello();", options: [{ convertPath: { "src/**": ["^src/(.+)$", "$1"] } }], }, { + name: "convertPath - object-bin/src/bin/c.js", filename: fixture("object-bin/src/bin/c.js"), code: "hello();", options: [{ convertPath: { "src/**": ["^src/(.+)$", "$1"] } }], }, { + name: "convertPath - no-bin-field/src/lib/test.js", filename: fixture("no-bin-field/src/lib/test.js"), code: "hello();", options: [{ convertPath: { "src/**": ["^src/(.+)$", "$1"] } }], @@ -81,98 +100,116 @@ ruleTester.run("shebang", rule, { // Should work fine if the filename is relative. { + name: "relative path - string-bin/bin/test.js", filename: "tests/fixtures/shebang/string-bin/bin/test.js", code: "#!/usr/bin/env node\nhello();", }, { + name: "relative path - string-bin/lib/test.js", filename: "tests/fixtures/shebang/string-bin/lib/test.js", code: "hello();", }, // BOM and \r\n { + name: "BOM without newline", filename: fixture("string-bin/lib/test.js"), code: "\uFEFFhello();", }, { + name: "BOM with newline", filename: fixture("string-bin/lib/test.js"), code: "\uFEFFhello();\n", }, { + name: "with windows newline", filename: fixture("string-bin/lib/test.js"), code: "hello();\r\n", }, { + name: "BOM with windows newline", filename: fixture("string-bin/lib/test.js"), code: "\uFEFFhello();\r\n", }, // blank lines on the top of files. { + name: "blank lines on the top of files.", filename: fixture("string-bin/lib/test.js"), code: "\n\n\nhello();", }, // https://github.com/mysticatea/eslint-plugin-node/issues/51 { + name: "Shebang with CLI flags", filename: fixture("string-bin/bin/test.js"), code: "#!/usr/bin/env node --harmony\nhello();", }, // use node resolution { + name: "use node resolution", filename: fixture("object-bin/bin/index.js"), code: "#!/usr/bin/env node\nhello();", }, // ignored files are executable { + name: "ignored file is executable", filename: fixture("ignored/executable.js"), code: "#!/usr/bin/env node\nhello();", }, { + name: "ignored file is not executable", filename: fixture("ignored/normal.js"), code: "hello();", }, ], invalid: [ { + name: "bin: string - match - no shebang", filename: fixture("string-bin/bin/test.js"), code: "hello();", output: "#!/usr/bin/env node\nhello();", errors: ['This file needs shebang "#!/usr/bin/env node".'], }, { + name: "bin: string - match - incorrect shebang", filename: fixture("string-bin/bin/test.js"), code: "#!/usr/bin/node\nhello();", output: "#!/usr/bin/env node\nhello();", errors: ['This file needs shebang "#!/usr/bin/env node".'], }, { + name: "bin: string - no match - with shebang", filename: fixture("string-bin/lib/test.js"), code: "#!/usr/bin/env node\nhello();", output: "hello();", errors: ["This file needs no shebang."], }, { + name: 'bin: {a: "./bin/a.js"} - match - no shebang', filename: fixture("object-bin/bin/a.js"), code: "hello();", output: "#!/usr/bin/env node\nhello();", errors: ['This file needs shebang "#!/usr/bin/env node".'], }, { + name: 'bin: {b: "./bin/b.js"} - match - no shebang', filename: fixture("object-bin/bin/b.js"), code: "#!/usr/bin/node\nhello();", output: "#!/usr/bin/env node\nhello();", errors: ['This file needs shebang "#!/usr/bin/env node".'], }, { + name: 'bin: {c: "./bin"} - no match - with shebang', filename: fixture("object-bin/bin/c.js"), code: "#!/usr/bin/env node\nhello();", output: "hello();", errors: ["This file needs no shebang."], }, { + name: "bin: undefined - no match - with shebang", filename: fixture("no-bin-field/lib/test.js"), code: "#!/usr/bin/env node\nhello();", output: "hello();", @@ -181,6 +218,7 @@ ruleTester.run("shebang", rule, { // convertPath { + name: "convertPath in options", filename: fixture("string-bin/src/bin/test.js"), code: "hello();", output: "#!/usr/bin/env node\nhello();", @@ -188,6 +226,7 @@ ruleTester.run("shebang", rule, { errors: ['This file needs shebang "#!/usr/bin/env node".'], }, { + name: "convertPath in settings", filename: fixture("string-bin/src/bin/test.js"), code: "hello();", output: "#!/usr/bin/env node\nhello();", @@ -197,6 +236,7 @@ ruleTester.run("shebang", rule, { }, }, { + name: "converted path - string-bin/src/bin/test.js", filename: fixture("string-bin/src/bin/test.js"), code: "#!/usr/bin/node\nhello();", output: "#!/usr/bin/env node\nhello();", @@ -204,6 +244,7 @@ ruleTester.run("shebang", rule, { errors: ['This file needs shebang "#!/usr/bin/env node".'], }, { + name: "converted path - string-bin/src/lib/test.js", filename: fixture("string-bin/src/lib/test.js"), code: "#!/usr/bin/env node\nhello();", output: "hello();", @@ -211,6 +252,7 @@ ruleTester.run("shebang", rule, { errors: ["This file needs no shebang."], }, { + name: "converted path - object-bin/src/bin/a.js", filename: fixture("object-bin/src/bin/a.js"), code: "hello();", output: "#!/usr/bin/env node\nhello();", @@ -218,6 +260,7 @@ ruleTester.run("shebang", rule, { errors: ['This file needs shebang "#!/usr/bin/env node".'], }, { + name: "converted path - object-bin/src/bin/b.js", filename: fixture("object-bin/src/bin/b.js"), code: "#!/usr/bin/node\nhello();", output: "#!/usr/bin/env node\nhello();", @@ -225,6 +268,7 @@ ruleTester.run("shebang", rule, { errors: ['This file needs shebang "#!/usr/bin/env node".'], }, { + name: "converted path - object-bin/src/bin/c.js", filename: fixture("object-bin/src/bin/c.js"), code: "#!/usr/bin/env node\nhello();", output: "hello();", @@ -232,6 +276,7 @@ ruleTester.run("shebang", rule, { errors: ["This file needs no shebang."], }, { + name: "converted path - no-bin-field/src/lib/test.js", filename: fixture("no-bin-field/src/lib/test.js"), code: "#!/usr/bin/env node\nhello();", output: "hello();", @@ -241,12 +286,14 @@ ruleTester.run("shebang", rule, { // Should work fine if the filename is relative. { + name: "relative path - string-bin/bin/test.js", filename: "tests/fixtures/shebang/string-bin/bin/test.js", code: "hello();", output: "#!/usr/bin/env node\nhello();", errors: ['This file needs shebang "#!/usr/bin/env node".'], }, { + name: "relative path - string-bin/lib/test.js", filename: "tests/fixtures/shebang/string-bin/lib/test.js", code: "#!/usr/bin/env node\nhello();", output: "hello();", @@ -255,6 +302,7 @@ ruleTester.run("shebang", rule, { // header comments { + name: "header comments", filename: fixture("string-bin/bin/test.js"), code: "/* header */\nhello();", output: "#!/usr/bin/env node\n/* header */\nhello();", @@ -316,6 +364,7 @@ ruleTester.run("shebang", rule, { // https://github.com/mysticatea/eslint-plugin-node/issues/51 { + name: "Shebang with CLI flags", filename: fixture("string-bin/lib/test.js"), code: "#!/usr/bin/env node --harmony\nhello();", output: "hello();", @@ -324,6 +373,7 @@ ruleTester.run("shebang", rule, { // use node resolution { + name: "use node resolution", filename: fixture("object-bin/bin/index.js"), code: "hello();", output: "#!/usr/bin/env node\nhello();", @@ -332,12 +382,14 @@ ruleTester.run("shebang", rule, { // ignored files are executable { + name: "ignored files is executable", filename: fixture("ignored/executable.js"), code: "hello();", output: "#!/usr/bin/env node\nhello();", errors: ['This file needs shebang "#!/usr/bin/env node".'], }, { + name: "ignored files is not executable", filename: fixture("ignored/normal.js"), code: "#!/usr/bin/env node\nhello();", output: "hello();", From 1d9f1f795d709ea9ce08652608ed676a6e2dc5e2 Mon Sep 17 00:00:00 2001 From: scagood <2230835+scagood@users.noreply.github.com> Date: Sat, 27 Jan 2024 22:08:08 +0000 Subject: [PATCH 3/9] fix: Ignore shebangs for all files not published --- lib/rules/shebang.js | 20 +++++--------------- tests/lib/rules/shebang.js | 18 +----------------- 2 files changed, 6 insertions(+), 32 deletions(-) diff --git a/lib/rules/shebang.js b/lib/rules/shebang.js index d99fbee8..5ee6da2c 100644 --- a/lib/rules/shebang.js +++ b/lib/rules/shebang.js @@ -5,7 +5,6 @@ "use strict" const path = require("path") -const { accessSync, constants } = require("node:fs") const getConvertPath = require("../util/get-convert-path") const getPackageJson = require("../util/get-package-json") @@ -24,15 +23,6 @@ function simulateNodeResolutionAlgorithm(filePath, binField) { return possibilities.includes(binField) } -function isExecutable(path) { - try { - accessSync(path, constants.X_OK) - return true - } catch (error) { - return false - } -} - /** * Checks whether or not a given path is a `bin` file. * @@ -133,15 +123,15 @@ module.exports = { const npmignore = getNpmignore(convertedAbsolutePath) - const isFileBin = isBinFile( + if (npmignore.match(convertedRelativePath)) { + return {} + } + + const needsShebang = isBinFile( convertedAbsolutePath, p.bin, packageDirectory ) - const isFileIgnored = npmignore.match(convertedRelativePath) - const isFileExecutable = isExecutable(originalAbsolutePath) - - const needsShebang = isFileBin || (isFileIgnored && isFileExecutable) const info = getShebangInfo(sourceCode) return { diff --git a/tests/lib/rules/shebang.js b/tests/lib/rules/shebang.js index 865e2782..bf88220f 100644 --- a/tests/lib/rules/shebang.js +++ b/tests/lib/rules/shebang.js @@ -153,7 +153,7 @@ ruleTester.run("shebang", rule, { code: "#!/usr/bin/env node\nhello();", }, - // ignored files are executable + // npm ignored files are ignored! { name: "ignored file is executable", filename: fixture("ignored/executable.js"), @@ -379,21 +379,5 @@ ruleTester.run("shebang", rule, { output: "#!/usr/bin/env node\nhello();", errors: ['This file needs shebang "#!/usr/bin/env node".'], }, - - // ignored files are executable - { - name: "ignored files is executable", - filename: fixture("ignored/executable.js"), - code: "hello();", - output: "#!/usr/bin/env node\nhello();", - errors: ['This file needs shebang "#!/usr/bin/env node".'], - }, - { - name: "ignored files is not executable", - filename: fixture("ignored/normal.js"), - code: "#!/usr/bin/env node\nhello();", - output: "hello();", - errors: ["This file needs no shebang."], - }, ], }) From a1f0d7bdf08fd781df97e359d0c021433f87fc06 Mon Sep 17 00:00:00 2001 From: scagood <2230835+scagood@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:20:36 +0000 Subject: [PATCH 4/9] feat(shebang): Add "ignoreUnpublished" option --- lib/rules/shebang.js | 10 +++-- tests/fixtures/shebang/ignored/executable.js | 2 - tests/fixtures/shebang/ignored/normal.js | 1 - .../{ignored => unpublished}/package.json | 2 +- tests/lib/rules/shebang.js | 43 ++++++++++++++++--- 5 files changed, 45 insertions(+), 13 deletions(-) delete mode 100755 tests/fixtures/shebang/ignored/executable.js delete mode 100644 tests/fixtures/shebang/ignored/normal.js rename tests/fixtures/shebang/{ignored => unpublished}/package.json (73%) diff --git a/lib/rules/shebang.js b/lib/rules/shebang.js index 5ee6da2c..41809ea0 100644 --- a/lib/rules/shebang.js +++ b/lib/rules/shebang.js @@ -81,8 +81,8 @@ module.exports = { { type: "object", properties: { - // convertPath: getConvertPath.schema, + ignoreUnpublished: { type: "boolean" }, }, additionalProperties: false, }, @@ -121,10 +121,12 @@ module.exports = { convertedRelativePath ) - const npmignore = getNpmignore(convertedAbsolutePath) + if (context.options?.[0]?.ignoreUnpublished === true) { + const npmignore = getNpmignore(convertedAbsolutePath) - if (npmignore.match(convertedRelativePath)) { - return {} + if (npmignore.match(convertedRelativePath)) { + return {} + } } const needsShebang = isBinFile( diff --git a/tests/fixtures/shebang/ignored/executable.js b/tests/fixtures/shebang/ignored/executable.js deleted file mode 100755 index 06bcbf4e..00000000 --- a/tests/fixtures/shebang/ignored/executable.js +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node -module.exports = {} diff --git a/tests/fixtures/shebang/ignored/normal.js b/tests/fixtures/shebang/ignored/normal.js deleted file mode 100644 index 4ba52ba2..00000000 --- a/tests/fixtures/shebang/ignored/normal.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {} diff --git a/tests/fixtures/shebang/ignored/package.json b/tests/fixtures/shebang/unpublished/package.json similarity index 73% rename from tests/fixtures/shebang/ignored/package.json rename to tests/fixtures/shebang/unpublished/package.json index f564f113..05fe8a95 100644 --- a/tests/fixtures/shebang/ignored/package.json +++ b/tests/fixtures/shebang/unpublished/package.json @@ -2,6 +2,6 @@ "name": "test", "version": "0.0.0", "files": [ - "./bin/test.js" + "./published.js" ] } diff --git a/tests/lib/rules/shebang.js b/tests/lib/rules/shebang.js index bf88220f..6ad7eb0e 100644 --- a/tests/lib/rules/shebang.js +++ b/tests/lib/rules/shebang.js @@ -153,16 +153,24 @@ ruleTester.run("shebang", rule, { code: "#!/usr/bin/env node\nhello();", }, - // npm ignored files are ignored! + // npm unpublished files are ignored { - name: "ignored file is executable", - filename: fixture("ignored/executable.js"), + name: "published file cant have shebang", + filename: fixture("unpublished/published.js"), + code: "hello();", + options: [{ ignoreUnpublished: true }], + }, + { + name: "unpublished file can have shebang", + filename: fixture("unpublished/unpublished.js"), code: "#!/usr/bin/env node\nhello();", + options: [{ ignoreUnpublished: true }], }, { - name: "ignored file is not executable", - filename: fixture("ignored/normal.js"), + name: "unpublished file can have noshebang", + filename: fixture("unpublished/unpublished.js"), code: "hello();", + options: [{ ignoreUnpublished: true }], }, ], invalid: [ @@ -379,5 +387,30 @@ ruleTester.run("shebang", rule, { output: "#!/usr/bin/env node\nhello();", errors: ['This file needs shebang "#!/usr/bin/env node".'], }, + + // npm unpublished files are ignored + { + name: "unpublished file should not have shebang", + filename: fixture("unpublished/unpublished.js"), + code: "#!/usr/bin/env node\nhello();", + output: "hello();", + errors: ["This file needs no shebang."], + }, + { + name: "published file should have shebang", + filename: fixture("unpublished/published.js"), + code: "#!/usr/bin/env node\nhello();", + output: "hello();", + errors: ["This file needs no shebang."], + }, + + { + name: "unpublished file shebang ignored", + filename: fixture("unpublished/published.js"), + code: "#!/usr/bin/env node\nhello();", + options: [{ ignoreUnpublished: true }], + output: "hello();", + errors: ["This file needs no shebang."], + }, ], }) From 3262702c2adfd9325d916e7aa0ae2350f89c52c7 Mon Sep 17 00:00:00 2001 From: scagood <2230835+scagood@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:21:07 +0000 Subject: [PATCH 5/9] chore: Actually ignore test fixtures --- eslint.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.js b/eslint.config.js index 3534ae88..2da338e0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -23,7 +23,7 @@ module.exports = [ "coverage/", "docs/", "lib/converted-esm/", - "test/fixtures/", + "tests/fixtures/", ], }, js.configs.recommended, From 94d4d0e512bedcf8f0ba55e438e0c6ebc14c1066 Mon Sep 17 00:00:00 2001 From: scagood <2230835+scagood@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:21:31 +0000 Subject: [PATCH 6/9] chore: Remove import-maps module disable --- eslint.config.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 2da338e0..6542c613 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -13,9 +13,6 @@ module.exports = [ { languageOptions: { globals: globals.mocha }, linterOptions: { reportUnusedDisableDirectives: true }, - settings: { - n: { allowModules: ["#eslint-rule-tester"] }, // the plugin does not support import-maps yet. - }, }, { ignores: [ From 86875714abe41dc058545abede2a25a065b14481 Mon Sep 17 00:00:00 2001 From: scagood <2230835+scagood@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:42:03 +0000 Subject: [PATCH 7/9] feat(shebang): Add "additionalExecutables" option --- lib/rules/shebang.js | 25 +++++++++++++++++++------ tests/lib/rules/shebang.js | 24 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/lib/rules/shebang.js b/lib/rules/shebang.js index 41809ea0..6bb9c6fa 100644 --- a/lib/rules/shebang.js +++ b/lib/rules/shebang.js @@ -5,6 +5,7 @@ "use strict" const path = require("path") +const matcher = require("ignore") const getConvertPath = require("../util/get-convert-path") const getPackageJson = require("../util/get-package-json") @@ -83,6 +84,10 @@ module.exports = { properties: { convertPath: getConvertPath.schema, ignoreUnpublished: { type: "boolean" }, + additionalExecutables: { + type: "array", + items: { type: "string" }, + }, }, additionalProperties: false, }, @@ -121,7 +126,17 @@ module.exports = { convertedRelativePath ) - if (context.options?.[0]?.ignoreUnpublished === true) { + const { additionalExecutables = [] } = context.options?.[0] ?? {} + + const executable = matcher() + executable.add(additionalExecutables) + const isExecutable = executable.test(convertedRelativePath) + + if ( + (additionalExecutables.length === 0 || + isExecutable.ignored === false) && + context.options?.[0]?.ignoreUnpublished === true + ) { const npmignore = getNpmignore(convertedAbsolutePath) if (npmignore.match(convertedRelativePath)) { @@ -129,11 +144,9 @@ module.exports = { } } - const needsShebang = isBinFile( - convertedAbsolutePath, - p.bin, - packageDirectory - ) + const needsShebang = + isExecutable.ignored === true || + isBinFile(convertedAbsolutePath, p.bin, packageDirectory) const info = getShebangInfo(sourceCode) return { diff --git a/tests/lib/rules/shebang.js b/tests/lib/rules/shebang.js index 6ad7eb0e..f0e0f9c1 100644 --- a/tests/lib/rules/shebang.js +++ b/tests/lib/rules/shebang.js @@ -172,6 +172,13 @@ ruleTester.run("shebang", rule, { code: "hello();", options: [{ ignoreUnpublished: true }], }, + + { + name: "file matching additionalExecutables", + filename: fixture("unpublished/something.test.js"), + code: "#!/usr/bin/env node\nhello();", + options: [{ additionalExecutables: ["*.test.js"] }], + }, ], invalid: [ { @@ -412,5 +419,22 @@ ruleTester.run("shebang", rule, { output: "hello();", errors: ["This file needs no shebang."], }, + + { + name: "executable in additionalExecutables without shebang", + filename: fixture("unpublished/something.test.js"), + code: "hello();", + options: [{ additionalExecutables: ["*.test.js"] }], + output: "#!/usr/bin/env node\nhello();", + errors: ['This file needs shebang "#!/usr/bin/env node".'], + }, + { + name: "file not in additionalExecutables with shebang", + filename: fixture("unpublished/not-a-test.js"), + code: "#!/usr/bin/env node\nhello();", + options: [{ additionalExecutables: ["*.test.js"] }], + output: "hello();", + errors: ["This file needs no shebang."], + }, ], }) From 3b74a71fe0c0ab196f772c6838ac599b700333bd Mon Sep 17 00:00:00 2001 From: scagood <2230835+scagood@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:45:43 +0000 Subject: [PATCH 8/9] docs(shebang): Add two new options to docs --- docs/rules/shebang.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/rules/shebang.md b/docs/rules/shebang.md index 205e5d11..126daea5 100644 --- a/docs/rules/shebang.md +++ b/docs/rules/shebang.md @@ -61,7 +61,11 @@ console.log("hello"); ```json { - "n/shebang": ["error", {"convertPath": null}] + "n/shebang": ["error", { + "convertPath": null, + "ignoreUnpublished": false, + "additionalExecutables": [], + }] } ``` @@ -70,6 +74,14 @@ console.log("hello"); This can be configured in the rule options or as a shared setting [`settings.convertPath`](../shared-settings.md#convertpath). Please see the shared settings documentation for more information. +#### ignoreUnpublished + +Allow for files that are not published to npm to be ignored by this rule. + +#### additionalExecutables + +Mark files as executable that are not referenced by the package.json#bin property + ## 🔎 Implementation - [Rule source](../../lib/rules/shebang.js) From 3352acd4b1c0c74e0189efc092cb9399a5515f91 Mon Sep 17 00:00:00 2001 From: scagood <2230835+scagood@users.noreply.github.com> Date: Tue, 6 Feb 2024 18:20:11 +0000 Subject: [PATCH 9/9] chore(shebang): Only report the first line #85 --- lib/rules/shebang.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/rules/shebang.js b/lib/rules/shebang.js index 6bb9c6fa..c4f9d4ef 100644 --- a/lib/rules/shebang.js +++ b/lib/rules/shebang.js @@ -69,6 +69,7 @@ function getShebangInfo(sourceCode) { } } +/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { docs: { @@ -150,7 +151,12 @@ module.exports = { const info = getShebangInfo(sourceCode) return { - Program(node) { + Program() { + const loc = { + start: { line: 1, column: 0 }, + end: { line: 1, column: sourceCode.lines.at(0).length }, + } + if ( needsShebang ? NODE_SHEBANG_PATTERN.test(info.shebang) @@ -160,7 +166,7 @@ module.exports = { // Checks BOM and \r. if (needsShebang && info.bom) { context.report({ - node, + loc, messageId: "unexpectedBOM", fix(fixer) { return fixer.removeRange([-1, 0]) @@ -169,7 +175,7 @@ module.exports = { } if (needsShebang && info.cr) { context.report({ - node, + loc, messageId: "expectedLF", fix(fixer) { const index = sourceCode.text.indexOf("\r") @@ -180,7 +186,7 @@ module.exports = { } else if (needsShebang) { // Shebang is lacking. context.report({ - node, + loc, messageId: "expectedHashbangNode", fix(fixer) { return fixer.replaceTextRange( @@ -192,7 +198,7 @@ module.exports = { } else { // Shebang is extra. context.report({ - node, + loc, messageId: "expectedHashbang", fix(fixer) { return fixer.removeRange([0, info.length])