diff --git a/src/common/get-file-info.js b/src/common/get-file-info.js index f136d0db2..a099c7697 100644 --- a/src/common/get-file-info.js +++ b/src/common/get-file-info.js @@ -1,4 +1,4 @@ -import inferParser from "../main/infer-parser.js"; +import inferParser from "../utils/infer-parser.js"; import { resolveConfig } from "../config/resolve-config.js"; import { isIgnored } from "../utils/ignore.js"; @@ -42,7 +42,7 @@ async function getParser(filePath, options) { config = await resolveConfig(filePath); } - return config?.parser ?? inferParser(filePath, options.plugins); + return config?.parser ?? inferParser(options, { physicalFile: filePath }); } export default getFileInfo; diff --git a/src/language-html/parser-html.js b/src/language-html/parser-html.js index 32c8e00bd..f73105ace 100644 --- a/src/language-html/parser-html.js +++ b/src/language-html/parser-html.js @@ -9,7 +9,7 @@ import { TagContentType, } from "angular-html-parser"; import parseFrontMatter from "../utils/front-matter/parse.js"; -import inferParserByLanguage from "../utils/infer-parser-by-language.js"; +import inferParser from "../utils/infer-parser.js"; import createError from "../common/parser-create-error.js"; import HTML_TAGS from "./utils/html-tag-names.evaluate.js"; import HTML_ELEMENT_ATTRIBUTES from "./utils/html-elements-attributes.evaluate.js"; @@ -87,7 +87,7 @@ function ngHtmlParser( return false; } const language = node.attrs.find((attr) => attr.name === "lang")?.value; - return !language || inferParserByLanguage(options, language) === "html"; + return !language || inferParser(options, { language }) === "html"; }; if (rootNodes.some(shouldParseAsHTML)) { /** @type {ParserTreeResult | undefined} */ diff --git a/src/language-html/utils/index.js b/src/language-html/utils/index.js index 3bdee04c8..8d256ec6b 100644 --- a/src/language-html/utils/index.js +++ b/src/language-html/utils/index.js @@ -3,7 +3,7 @@ */ import isFrontMatter from "../../utils/front-matter/is-front-matter.js"; -import inferParserByLanguage from "../../utils/infer-parser-by-language.js"; +import inferParser from "../../utils/infer-parser.js"; import { line, hardline, join } from "../../document/builders.js"; import { replaceEndOfLine } from "../../document/utils.js"; import { @@ -386,7 +386,7 @@ function inferStyleParser(node, options) { // But, we need to handle `"stylus"` here for printing a style block in Vue SFC as stylus code by external plugin. // https://github.com/prettier/prettier/pull/12707 if (lang === "stylus") { - return inferParserByLanguage(options, "stylus"); + return inferParser(options, { language: "stylus" }); } } @@ -406,7 +406,7 @@ function inferScriptParser(node, options) { return ( _inferScriptParser(node) || (!("src" in node.attrMap) && - inferParserByLanguage(options, node.attrMap.lang)) + inferParser(options, { language: node.attrMap.lang })) ); } } diff --git a/src/language-markdown/embed.js b/src/language-markdown/embed.js index 4c80d772a..763fa629f 100644 --- a/src/language-markdown/embed.js +++ b/src/language-markdown/embed.js @@ -2,14 +2,14 @@ import getMaxContinuousCount from "../utils/get-max-continuous-count.js"; import { hardline, markAsRoot } from "../document/builders.js"; import { replaceEndOfLine } from "../document/utils.js"; import printFrontMatter from "../utils/front-matter/print.js"; -import inferParserByLanguage from "../utils/infer-parser-by-language.js"; +import inferParser from "../utils/infer-parser.js"; import { getFencedCodeBlockValue } from "./utils.js"; function embed(path, options) { const { node } = path; if (node.type === "code" && node.lang !== null) { - const parser = inferParserByLanguage(options, node.lang); + const parser = inferParser(options, { language: node.lang }); if (parser) { return async (textToDoc) => { const styleUnit = options.__inJsTemplate ? "~" : "`"; diff --git a/src/main/infer-parser.js b/src/main/infer-parser.js deleted file mode 100644 index 1c84ecb82..000000000 --- a/src/main/infer-parser.js +++ /dev/null @@ -1,33 +0,0 @@ -import path from "node:path"; -import getInterpreter from "../utils/get-interpreter.js"; - -function inferParser(filepath, plugins) { - const filename = path.basename(filepath).toLowerCase(); - const languages = plugins.flatMap((plugin) => plugin.languages ?? []); - - // 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. - let language = languages.find( - (language) => - language.extensions?.some((extension) => filename.endsWith(extension)) || - language.filenames?.some((name) => name.toLowerCase() === filename) - ); - - if ( - process.env.PRETTIER_TARGET !== "universal" && - !language && - !filename.includes(".") - ) { - const interpreter = getInterpreter(filepath); - if (interpreter) { - language = languages.find((language) => - language.interpreters?.includes(interpreter) - ); - } - } - - return language?.parsers[0]; -} - -export default inferParser; diff --git a/src/main/normalize-format-options.js b/src/main/normalize-format-options.js index 642ec942e..3a7c70044 100644 --- a/src/main/normalize-format-options.js +++ b/src/main/normalize-format-options.js @@ -1,8 +1,8 @@ import { UndefinedParserError } from "../common/errors.js"; import { getSupportInfo } from "../main/support.js"; +import inferParser from "../utils/infer-parser.js"; import normalizeOptions from "./normalize-options.js"; import { resolveParser } from "./parser.js"; -import inferParser from "./infer-parser.js"; const formatOptionsHiddenDefaults = { astFormat: "estree", @@ -39,7 +39,9 @@ async function normalizeFormatOptions(options, opts = {}) { ); rawOptions.parser = "babel"; } else { - rawOptions.parser = inferParser(rawOptions.filepath, rawOptions.plugins); + rawOptions.parser = inferParser(rawOptions, { + physicalFile: rawOptions.filepath, + }); if (!rawOptions.parser) { throw new UndefinedParserError( `No parser could be inferred for file: ${rawOptions.filepath}` diff --git a/src/utils/infer-parser-by-language.js b/src/utils/infer-parser-by-language.js deleted file mode 100644 index 36e147553..000000000 --- a/src/utils/infer-parser-by-language.js +++ /dev/null @@ -1,15 +0,0 @@ -function inferParserByLanguage(options, language) { - if (!language) { - return; - } - - const languages = options.plugins.flatMap((plugin) => plugin.languages ?? []); - - const matched = - languages.find(({ name }) => name.toLowerCase() === language) ?? - languages.find(({ aliases }) => aliases?.includes(language)) ?? - languages.find(({ extensions }) => extensions?.includes(`.${language}`)); - return matched?.parsers[0]; -} - -export default inferParserByLanguage; diff --git a/src/utils/infer-parser.js b/src/utils/infer-parser.js new file mode 100644 index 000000000..e018766ab --- /dev/null +++ b/src/utils/infer-parser.js @@ -0,0 +1,76 @@ +import getInterpreter from "./get-interpreter.js"; + +// Didn't use `path.basename` since this module need work in browsers too +const getFileBasename = (file) => file.split(/[/\\]/).pop(); + +function getLanguageByFilename(languages, filename) { + if (!filename) { + return; + } + + const basename = getFileBasename(filename).toLowerCase(); + + return languages.find( + (language) => + language.extensions?.some((extension) => basename.endsWith(extension)) || + language.filenames?.some((name) => name.toLowerCase() === basename) + ); +} + +function getLanguageByName(languages, languageName) { + if (!languageName) { + return; + } + + return ( + languages.find(({ name }) => name.toLowerCase() === languageName) ?? + languages.find(({ aliases }) => aliases?.includes(languageName)) ?? + languages.find(({ extensions }) => extensions?.includes(`.${languageName}`)) + ); +} + +function getLanguageByInterpreter(languages, file) { + if ( + process.env.PRETTIER_TARGET === "universal" || + !file || + getFileBasename(file).includes(".") + ) { + return; + } + + const interpreter = getInterpreter(file); + + if (!interpreter) { + return; + } + + return languages.find((language) => + language.interpreters?.includes(interpreter) + ); +} + +/** + * @param {import("../index.js").Options} options + * @param {{physicalFile?: string, file?: string, language?: string}} fileInfo + * @returns {string | void} matched parser name if found + */ +function inferParser(options, fileInfo) { + const languages = options.plugins.flatMap( + (plugin) => + // @ts-expect-error -- Safe + plugin.languages ?? [] + ); + + // 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 = + getLanguageByName(languages, fileInfo.language) ?? + getLanguageByFilename(languages, fileInfo.physicalFile) ?? + getLanguageByFilename(languages, fileInfo.file) ?? + getLanguageByInterpreter(languages, fileInfo.physicalFile); + + return language?.parsers[0]; +} + +export default inferParser;