You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
5.5 KiB
170 lines
5.5 KiB
/** |
|
* @author Toru Nagashima |
|
* See LICENSE file in root directory for full license. |
|
*/ |
|
"use strict" |
|
|
|
const path = require("path") |
|
const getConvertPath = require("../util/get-convert-path") |
|
const getPackageJson = require("../util/get-package-json") |
|
|
|
const NODE_SHEBANG = "#!/usr/bin/env node\n" |
|
const SHEBANG_PATTERN = /^(#!.+?)?(\r)?\n/u |
|
const NODE_SHEBANG_PATTERN = /#!\/usr\/bin\/env node(?: [^\r\n]+?)?\n/u |
|
|
|
function simulateNodeResolutionAlgorithm(filePath, binField) { |
|
const possibilities = [filePath] |
|
let newFilePath = filePath.replace(/\.js$/u, "") |
|
possibilities.push(newFilePath) |
|
newFilePath = newFilePath.replace(/[/\\]index$/u, "") |
|
possibilities.push(newFilePath) |
|
return possibilities.includes(binField) |
|
} |
|
|
|
/** |
|
* Checks whether or not a given path is a `bin` file. |
|
* |
|
* @param {string} filePath - A file path to check. |
|
* @param {string|object|undefined} binField - A value of the `bin` field of `package.json`. |
|
* @param {string} basedir - A directory path that `package.json` exists. |
|
* @returns {boolean} `true` if the file is a `bin` file. |
|
*/ |
|
function isBinFile(filePath, binField, basedir) { |
|
if (!binField) { |
|
return false |
|
} |
|
if (typeof binField === "string") { |
|
return simulateNodeResolutionAlgorithm( |
|
filePath, |
|
path.resolve(basedir, binField) |
|
) |
|
} |
|
return Object.keys(binField).some(key => |
|
simulateNodeResolutionAlgorithm( |
|
filePath, |
|
path.resolve(basedir, binField[key]) |
|
) |
|
) |
|
} |
|
|
|
/** |
|
* Gets the shebang line (includes a line ending) from a given code. |
|
* |
|
* @param {SourceCode} sourceCode - A source code object to check. |
|
* @returns {{length: number, bom: boolean, shebang: string, cr: boolean}} |
|
* shebang's information. |
|
* `retv.shebang` is an empty string if shebang doesn't exist. |
|
*/ |
|
function getShebangInfo(sourceCode) { |
|
const m = SHEBANG_PATTERN.exec(sourceCode.text) |
|
|
|
return { |
|
bom: sourceCode.hasBOM, |
|
cr: Boolean(m && m[2]), |
|
length: (m && m[0].length) || 0, |
|
shebang: (m && m[1] && `${m[1]}\n`) || "", |
|
} |
|
} |
|
|
|
module.exports = { |
|
meta: { |
|
docs: { |
|
description: "suggest correct usage of shebang", |
|
category: "Possible Errors", |
|
recommended: true, |
|
url: |
|
"https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/shebang.md", |
|
}, |
|
type: "problem", |
|
fixable: "code", |
|
schema: [ |
|
{ |
|
type: "object", |
|
properties: { |
|
// |
|
convertPath: getConvertPath.schema, |
|
}, |
|
additionalProperties: false, |
|
}, |
|
], |
|
}, |
|
create(context) { |
|
const sourceCode = context.getSourceCode() |
|
let filePath = context.getFilename() |
|
if (filePath === "<input>") { |
|
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 needsShebang = isBinFile(filePath, p.bin, basedir) |
|
const info = getShebangInfo(sourceCode) |
|
|
|
return { |
|
Program(node) { |
|
if ( |
|
needsShebang |
|
? NODE_SHEBANG_PATTERN.test(info.shebang) |
|
: !info.shebang |
|
) { |
|
// Good the shebang target. |
|
// Checks BOM and \r. |
|
if (needsShebang && info.bom) { |
|
context.report({ |
|
node, |
|
message: "This file must not have Unicode BOM.", |
|
fix(fixer) { |
|
return fixer.removeRange([-1, 0]) |
|
}, |
|
}) |
|
} |
|
if (needsShebang && info.cr) { |
|
context.report({ |
|
node, |
|
message: |
|
"This file must have Unix linebreaks (LF).", |
|
fix(fixer) { |
|
const index = sourceCode.text.indexOf("\r") |
|
return fixer.removeRange([index, index + 1]) |
|
}, |
|
}) |
|
} |
|
} else if (needsShebang) { |
|
// Shebang is lacking. |
|
context.report({ |
|
node, |
|
message: |
|
'This file needs shebang "#!/usr/bin/env node".', |
|
fix(fixer) { |
|
return fixer.replaceTextRange( |
|
[-1, info.length], |
|
NODE_SHEBANG |
|
) |
|
}, |
|
}) |
|
} else { |
|
// Shebang is extra. |
|
context.report({ |
|
node, |
|
message: "This file needs no shebang.", |
|
fix(fixer) { |
|
return fixer.removeRange([0, info.length]) |
|
}, |
|
}) |
|
} |
|
}, |
|
} |
|
}, |
|
}
|
|
|