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])