Infer via shebang (#5149)

If no file type can be inferred, this attempts to read the first line of a file and extract a shebang, which can be checked against a known list.
master
Petter 2018-10-04 17:47:23 -07:00 committed by Ika
parent ea0954995d
commit 1244729ad7
9 changed files with 93 additions and 2 deletions

View File

@ -49,6 +49,7 @@
"mem": "1.1.0",
"minimatch": "3.0.4",
"minimist": "1.2.0",
"n-readlines": "1.0.0",
"normalize-path": "3.0.0",
"parse5-htmlparser2-tree-adapter": "5.0.0",
"postcss-less": "1.1.5",

View File

@ -11,6 +11,9 @@ const languages = [
since: "0.0.0",
parsers: ["babylon", "flow"],
vscodeLanguageIds: ["javascript"]
},
extend: {
interpreters: ["nodejs"]
}
}),
createLanguage(require("linguist-languages/data/javascript"), {

View File

@ -1,6 +1,8 @@
"use strict";
const fs = require("fs");
const normalizePath = require("normalize-path");
const readlines = require("n-readlines");
const UndefinedParserError = require("../common/errors").UndefinedParserError;
const getSupportInfo = require("../main/support").getSupportInfo;
const normalizer = require("./options-normalizer");
@ -114,10 +116,56 @@ function getPlugin(options) {
return printerPlugin;
}
function getInterpreter(filepath) {
if (typeof filepath !== "string") {
return "";
}
let fd;
try {
fd = fs.openSync(filepath, "r");
} catch (err) {
return "";
}
try {
const liner = new readlines(fd);
const firstLine = liner.next().toString("utf8");
// #!/bin/env node, #!/usr/bin/env node
const m1 = firstLine.match(/^#!\/(?:usr\/)?bin\/env\s+(\S+)/);
if (m1) {
return m1[1];
}
// #!/bin/node, #!/usr/bin/node, #!/usr/local/bin/node
const m2 = firstLine.match(/^#!\/(?:usr\/(?:local\/)?)?bin\/(\S+)/);
if (m2) {
return m2[1];
}
return "";
} catch (err) {
// There are some weird cases where paths are missing, causing Jest
// failures. It's unclear what these correspond to in the real world.
return "";
} finally {
try {
// There are some weird cases where paths are missing, causing Jest
// failures. It's unclear what these correspond to in the real world.
fs.closeSync(fd);
} catch (err) {
// nop
}
}
}
function inferParser(filepath, plugins) {
const filepathParts = normalizePath(filepath).split("/");
const filename = filepathParts[filepathParts.length - 1].toLowerCase();
// If the file has no extension, we can try to infer the language from the
// interpreter in the shebang line, if any; but since this requires FS access,
// do it last.
const language = getSupportInfo(null, {
plugins
}).languages.find(
@ -126,7 +174,10 @@ function inferParser(filepath, plugins) {
((language.extensions &&
language.extensions.some(extension => filename.endsWith(extension))) ||
(language.filenames &&
language.filenames.find(name => name.toLowerCase() === filename)))
language.filenames.find(name => name.toLowerCase() === filename)) ||
(filename.indexOf(".") === -1 &&
language.interpreters &&
language.interpreters.indexOf(getInterpreter(filepath)) !== -1))
);
return language && language.parsers[0];

View File

@ -566,7 +566,7 @@ exports[`CLI --support-info (stdout) 1`] = `
\\".xsjslib\\"
],
\\"filenames\\": [\\"Jakefile\\"],
\\"interpreters\\": [\\"node\\"],
\\"interpreters\\": [\\"node\\", \\"nodejs\\"],
\\"linguistLanguageId\\": 183,
\\"name\\": \\"JavaScript\\",
\\"parsers\\": [\\"babylon\\", \\"flow\\"],

View File

@ -194,6 +194,33 @@ test("API getFileInfo with withNodeModules", () => {
});
});
describe("extracts file-info for a JS file with no extension but a standard shebang", () => {
expect(
prettier.getFileInfo.sync("tests_integration/cli/shebang/node-shebang")
).toMatchObject({
ignored: false,
inferredParser: "babylon"
});
});
describe("extracts file-info for a JS file with no extension but an env-based shebang", () => {
expect(
prettier.getFileInfo.sync("tests_integration/cli/shebang/env-node-shebang")
).toMatchObject({
ignored: false,
inferredParser: "babylon"
});
});
describe("returns null parser for unknown shebang", () => {
expect(
prettier.getFileInfo.sync("tests_integration/cli/shebang/nonsense-shebang")
).toMatchObject({
ignored: false,
inferredParser: null
});
});
test("API getFileInfo with plugins loaded using pluginSearchDir", () => {
const file = "file.foo";
const pluginsPath = path.resolve(

View File

@ -0,0 +1,2 @@
#!/usr/bin/env node
'use strict';

View File

@ -0,0 +1,2 @@
#!/usr/bin/node
'use strict';

View File

@ -0,0 +1 @@
#!/bin/frobble

View File

@ -4728,6 +4728,10 @@ mute-stream@0.0.7:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
n-readlines@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/n-readlines/-/n-readlines-1.0.0.tgz#c353797f216c253fdfef7e91da4e8b17c29a91a6"
nan@^2.9.2:
version "2.10.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"