| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 | /** * @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/uconst NODE_SHEBANG_PATTERN = /#!\/usr\/bin\/env node(?: [^\r\n]+?)?\n/ufunction 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])                        },                    })                }            },        }    },}
 |