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
parent
ea0954995d
commit
1244729ad7
|
@ -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",
|
||||
|
|
|
@ -11,6 +11,9 @@ const languages = [
|
|||
since: "0.0.0",
|
||||
parsers: ["babylon", "flow"],
|
||||
vscodeLanguageIds: ["javascript"]
|
||||
},
|
||||
extend: {
|
||||
interpreters: ["nodejs"]
|
||||
}
|
||||
}),
|
||||
createLanguage(require("linguist-languages/data/javascript"), {
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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\\"],
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env node
|
||||
'use strict';
|
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/node
|
||||
'use strict';
|
|
@ -0,0 +1 @@
|
|||
#!/bin/frobble
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue