UNPKG

prettier-plugin-sh

Version:

An opinionated `shellscript` formatter plugin for Prettier, also support simple format of `Dockerfile`, `properties`, `gitignore`, `dotenv`, `hosts`, `jvmoptions`...

861 lines (856 loc) 21.3 kB
'use strict'; var shSyntax = require('sh-syntax'); var path = require('node:path'); const languages = [ { "name": "Dockerfile", "aceMode": "dockerfile", "parsers": [ "dockerfile" ], "linguistLanguageId": 89, "vscodeLanguageIds": [ "dockerfile" ], "extensions": [ ".dockerfile", ".containerfile" ], "tmScope": "source.dockerfile", "aliases": [ "Containerfile" ], "codemirrorMode": "dockerfile", "codemirrorMimeType": "text/x-dockerfile", "filenames": [ "Containerfile", "Dockerfile" ] }, { "name": "Alpine Abuild", "aceMode": "sh", "parsers": [ "sh" ], "linguistLanguageId": 14, "vscodeLanguageIds": [ "shellscript" ], "tmScope": "source.shell", "aliases": [ "abuild", "apkbuild" ], "codemirrorMode": "shell", "codemirrorMimeType": "text/x-sh", "group": "Shell", "filenames": [ "APKBUILD" ] }, { "name": "CODEOWNERS", "aceMode": "gitignore", "parsers": [ "sh" ], "linguistLanguageId": 321684729, "vscodeLanguageIds": [ "gitignore" ], "tmScope": "text.codeowners", "filenames": [ "CODEOWNERS" ] }, { "name": "Gentoo Ebuild", "aceMode": "sh", "parsers": [ "sh" ], "linguistLanguageId": 127, "vscodeLanguageIds": [ "shellscript" ], "extensions": [ ".ebuild" ], "tmScope": "source.shell", "codemirrorMode": "shell", "codemirrorMimeType": "text/x-sh", "group": "Shell" }, { "name": "Gentoo Eclass", "aceMode": "sh", "parsers": [ "sh" ], "linguistLanguageId": 128, "vscodeLanguageIds": [ "shellscript" ], "extensions": [ ".eclass" ], "tmScope": "source.shell", "codemirrorMode": "shell", "codemirrorMimeType": "text/x-sh", "group": "Shell" }, { "name": "Git Attributes", "aceMode": "gitignore", "parsers": [ "sh" ], "linguistLanguageId": 956324166, "vscodeLanguageIds": [ "gitignore" ], "tmScope": "source.gitattributes", "aliases": [ "gitattributes" ], "codemirrorMode": "shell", "codemirrorMimeType": "text/x-sh", "filenames": [ ".gitattributes" ] }, { "name": "Ignore List", "aceMode": "gitignore", "parsers": [ "sh" ], "linguistLanguageId": 74444240, "vscodeLanguageIds": [ "gitignore" ], "extensions": [ ".gitignore" ], "tmScope": "source.gitignore", "aliases": [ "ignore", "gitignore", "git-ignore" ], "codemirrorMode": "shell", "codemirrorMimeType": "text/x-sh", "filenames": [ ".atomignore", ".babelignore", ".bzrignore", ".coffeelintignore", ".cvsignore", ".dockerignore", ".easignore", ".eleventyignore", ".eslintignore", ".gitignore", ".ignore", ".markdownlintignore", ".nodemonignore", ".npmignore", ".prettierignore", ".stylelintignore", ".vercelignore", ".vscodeignore", "gitignore-global", "gitignore_global" ] }, { "name": "Java Properties", "aceMode": "properties", "parsers": [ "sh" ], "linguistLanguageId": 519377561, "vscodeLanguageIds": [ "properties" ], "extensions": [ ".properties" ], "tmScope": "source.java-properties", "codemirrorMode": "properties", "codemirrorMimeType": "text/x-properties" }, { "name": "Nushell", "aceMode": "sh", "parsers": [ "sh" ], "linguistLanguageId": 446573572, "vscodeLanguageIds": [ "shellscript" ], "extensions": [ ".nu" ], "tmScope": "source.nushell", "aliases": [ "nu-script", "nushell-script" ], "codemirrorMode": "shell", "codemirrorMimeType": "text/x-sh", "interpreters": [ "nu" ] }, { "name": "OpenRC runscript", "aceMode": "sh", "parsers": [ "sh" ], "linguistLanguageId": 265, "vscodeLanguageIds": [ "shellscript" ], "tmScope": "source.shell", "aliases": [ "openrc" ], "codemirrorMode": "shell", "codemirrorMimeType": "text/x-sh", "interpreters": [ "openrc-run" ], "group": "Shell" }, { "name": "Option List", "aceMode": "sh", "parsers": [ "sh" ], "linguistLanguageId": 723589315, "vscodeLanguageIds": [ "shellscript" ], "tmScope": "source.opts", "aliases": [ "opts", "ackrc" ], "codemirrorMode": "shell", "codemirrorMimeType": "text/x-sh", "filenames": [ ".ackrc", ".rspec", ".yardopts", "ackrc", "mocha.opts" ] }, { "name": "Shell", "aceMode": "sh", "parsers": [ "sh" ], "linguistLanguageId": 346, "vscodeLanguageIds": [ "shellscript" ], "extensions": [ ".sh", ".bash", ".bats", ".cgi", ".command", ".fcgi", ".ksh", ".sh.in", ".tmux", ".tool", ".trigger", ".zsh", ".zsh-theme" ], "tmScope": "source.shell", "aliases": [ "sh", "shell-script", "bash", "zsh", "envrc" ], "codemirrorMode": "shell", "codemirrorMimeType": "text/x-sh", "interpreters": [ "ash", "bash", "dash", "ksh", "mksh", "pdksh", "rc", "sh", "zsh" ], "filenames": [ ".bash_aliases", ".bash_functions", ".bash_history", ".bash_logout", ".bash_profile", ".bashrc", ".cshrc", ".envrc", ".flaskenv", ".kshrc", ".login", ".profile", ".tmux.conf", ".zlogin", ".zlogout", ".zprofile", ".zshenv", ".zshrc", "9fs", "PKGBUILD", "bash_aliases", "bash_logout", "bash_profile", "bashrc", "cshrc", "gradlew", "kshrc", "login", "man", "profile", "tmux.conf", "zlogin", "zlogout", "zprofile", "zshenv", "zshrc" ] }, { "name": "Tcsh", "aceMode": "sh", "parsers": [ "sh" ], "linguistLanguageId": 368, "vscodeLanguageIds": [ "shellscript" ], "extensions": [ ".tcsh", ".csh" ], "tmScope": "source.shell", "codemirrorMode": "shell", "codemirrorMimeType": "text/x-sh", "interpreters": [ "tcsh", "csh" ], "group": "Shell" }, { "name": "TextMate Properties", "aceMode": "properties", "parsers": [ "sh" ], "linguistLanguageId": 981795023, "vscodeLanguageIds": [ "properties" ], "tmScope": "source.tm-properties", "aliases": [ "tm-properties" ], "codemirrorMode": "properties", "codemirrorMimeType": "text/x-properties", "filenames": [ ".tm_properties" ] }, { "name": "iCalendar", "aceMode": "properties", "parsers": [ "sh" ], "linguistLanguageId": 98384424, "vscodeLanguageIds": [ "properties" ], "extensions": [ ".ics", ".ical" ], "tmScope": "source.iCalendar", "aliases": [ "iCal" ], "codemirrorMode": "properties", "codemirrorMimeType": "text/x-properties" }, { "name": "vCard", "aceMode": "properties", "parsers": [ "sh" ], "linguistLanguageId": 851476558, "vscodeLanguageIds": [ "properties" ], "extensions": [ ".vcf" ], "tmScope": "source.vcard", "aliases": [ "virtual contact file", "electronic business card" ], "codemirrorMode": "properties", "codemirrorMimeType": "text/x-properties" }, { "name": "JvmOptions", "parsers": [ "sh" ], "extensions": [ ".vmoptions" ], "filenames": [ "jvm.options" ], "vscodeLanguageIds": [ "jvmoptions" ] }, { "name": "hosts", "parsers": [ "sh" ], "filenames": [ "hosts" ], "vscodeLanguageIds": [ "hosts" ] }, { "name": "dotenv", "parsers": [ "sh" ], "extensions": [ ".env" ], "vscodeLanguageIds": [ "dotenv" ], "isSupported": function({ filepath }) { const basename = path.basename(filepath); return basename === ".env" || basename.startsWith(".env."); } }, { "name": "husky", "parsers": [ "sh" ], "isSupported": function({ filepath }) { const dirname = path.dirname(filepath); return path.basename(dirname) === ".husky"; } }, { "name": "nvmrc", "parsers": [ "sh" ], "extensions": [ ".node-version", ".nvmrc" ], "filenames": [ ".node-version", ".nvmrc" ] } ]; var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; class ShSyntaxParseError extends SyntaxError { constructor(err) { const error = err; super("Text" in error && error.Text || error.message); this.cause = err; if ("Pos" in error && error.Pos != null && typeof error.Pos === "object") { this.loc = { start: { column: error.Pos.Col, line: error.Pos.Line } }; } } } function hasPragma(text) { var _a, _b; const commentLineRegex = new RegExp("^\\s*(#(?<comment>.*))?$", "gm"); let lastIndex = -1; for (; ; ) { const match = commentLineRegex.exec(text); if (match == null || match.index !== lastIndex + 1) { return false; } lastIndex = commentLineRegex.lastIndex; const comment = (_b = (_a = match.groups) == null ? void 0 : _a.comment) == null ? void 0 : _b.trim(); if (comment == null) { continue; } if (comment.startsWith("@prettier") || comment.startsWith("@format")) { return true; } } } const dockerfileParser = { astFormat: "dockerfile", hasPragma, parse: (text) => text, locStart: () => 0, locEnd: (node) => node.length }; let formatDockerfileContents_; const getFormatDockerfileContents = () => __async(null, null, function* () { if (!formatDockerfileContents_) { const dockerfmt = yield import('@reteps/dockerfmt'); formatDockerfileContents_ = dockerfmt.formatDockerfileContents; } return formatDockerfileContents_; }); const dockerPrinter = { // @ts-expect-error -- https://github.com/prettier/prettier/issues/15080#issuecomment-1630987744 print(_0, _1) { return __async(this, arguments, function* (path, { filepath, // parser options keepComments = true, variant, stopAt, recoverErrors, // printer options useTabs, tabWidth, indent = useTabs ? 0 : tabWidth != null ? tabWidth : 2, binaryNextLine = true, switchCaseIndent = true, spaceRedirects, // eslint-disable-next-line sonarjs/deprecation keepPadding, minify, singleLine, functionNextLine }) { const formatDockerfileContents = yield getFormatDockerfileContents(); try { return yield formatDockerfileContents(path.node, { indent, spaceRedirects: spaceRedirects != null ? spaceRedirects : false, trailingNewline: true }); } catch (e) { return shSyntax.processor(path.node, { print: true, filepath, keepComments, variant, stopAt, recoverErrors, useTabs, tabWidth, indent, binaryNextLine, switchCaseIndent, spaceRedirects: spaceRedirects != null ? spaceRedirects : true, keepPadding, minify, singleLine, functionNextLine }); } }); } }; const shParser = { astFormat: "sh", hasPragma, locStart: (node) => node.Pos.Offset, locEnd: (node) => node.End.Offset, parse(_0, _1) { return __async(this, arguments, function* (text, { filepath, keepComments = true, /** * The following `@link` doesn't work as expected, see * {@link https://github.com/microsoft/tsdoc/issues/9} */ /** TODO: support {@link LangVariant.LangAuto} */ // eslint-disable-line sonarjs/todo-tag variant, stopAt, recoverErrors }) { return shSyntax.processor(text, { filepath, keepComments, variant, stopAt, recoverErrors }); }); } }; const shPrinter = { // @ts-expect-error -- https://github.com/prettier/prettier/issues/15080#issuecomment-1630987744 print(_0, _1) { return __async(this, arguments, function* (path, { originalText, filepath, // parser options keepComments = true, variant, stopAt, recoverErrors, // printer options useTabs, tabWidth, indent = useTabs ? 0 : tabWidth, binaryNextLine = true, switchCaseIndent = true, spaceRedirects = true, // eslint-disable-next-line sonarjs/deprecation keepPadding, minify, singleLine, functionNextLine }) { return shSyntax.processor(path.node, { originalText, filepath, keepComments, variant, stopAt, recoverErrors, useTabs, tabWidth, indent, binaryNextLine, switchCaseIndent, spaceRedirects, keepPadding, minify, singleLine, functionNextLine }); }); } }; const parsers = { dockerfile: dockerfileParser, sh: shParser }; const printers = { dockerfile: dockerPrinter, sh: shPrinter }; const options = { keepComments: { // since: '0.1.0', category: "Output", type: "boolean", default: true, description: "KeepComments makes the parser parse comments and attach them to nodes, as opposed to discarding them." }, variant: { // since: '0.1.0', category: "Config", type: "choice", choices: [ { value: shSyntax.LangVariant.LangBash, description: [ "LangBash corresponds to the GNU Bash language, as described in its manual at https://www.gnu.org/software/bash/manual/bash.html.", "", "We currently follow Bash version 5.2.", "", 'Its string representation is "bash".' ].join("\n") }, { value: shSyntax.LangVariant.LangPOSIX, description: [ "LangPOSIX corresponds to the POSIX Shell language, as described at https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html.", "", 'Its string representation is "posix" or "sh".' ].join("\n") }, { value: shSyntax.LangVariant.LangMirBSDKorn, description: [ "LangMirBSDKorn corresponds to the MirBSD Korn Shell, also known as mksh, as described at http://www.mirbsd.org/htman/i386/man1/mksh.htm.", "Note that it shares some features with Bash, due to the shared ancestry that is ksh.", "", "We currently follow mksh version 59.", "", 'Its string representation is "mksh".' ].join("\n") }, { value: shSyntax.LangVariant.LangBats, description: [ "LangBats corresponds to the Bash Automated Testing System language, as described at https://github.com/bats-core/bats-core.", "Note that it's just a small extension of the Bash language.", "", 'Its string representation is "bats".' ].join("\n") }, { value: shSyntax.LangVariant.LangAuto, description: [ "LangAuto corresponds to automatic language detection, commonly used by end-user applications like shfmt, which can guess a file's language variant given its filename or shebang.", "", "At this time, [Variant] does not support LangAuto." ].join("\n") } ], description: "Variant changes the shell language variant that the parser will accept." }, stopAt: { // since: '0.1.0', category: "Config", type: "path", description: [ "StopAt configures the lexer to stop at an arbitrary word, treating it as if it were the end of the input. It can contain any characters except whitespace, and cannot be over four bytes in size.", "This can be useful to embed shell code within another language, as one can use a special word to mark the delimiters between the two.", `As a word, it will only apply when following whitespace or a separating token. For example, StopAt("$$") will act on the inputs "foo $$" and "foo;$$", but not on "foo '$$'".`, 'The match is done by prefix, so the example above will also act on "foo $$bar".' ].join("\n") }, recoverErrors: { // since: '0.17.0', category: "Config", type: "path", description: [ "RecoverErrors allows the parser to skip up to a maximum number of errors in the given input on a best-effort basis.", "This can be useful to tab-complete an interactive shell prompt, or when providing diagnostics on slightly incomplete shell source.", "", "Currently, this only helps with mandatory tokens from the shell grammar which are not present in the input. They result in position fields or nodes whose position report [Pos.IsRecovered] as true.", "", "For example, given the input `(foo |`, the result will contain two recovered positions; first, the pipe requires a statement to follow, and as [Stmt.Pos] reports, the entire node is recovered.", "Second, the subshell needs to be closed, so [Subshell.Rparen] is recovered." ].join("\n") }, indent: { // since: '0.1.0', category: "Format", type: "int", description: "Indent sets the number of spaces used for indentation. If set to 0, tabs will be used instead." }, binaryNextLine: { // since: '0.1.0', category: "Output", type: "boolean", default: true, description: "BinaryNextLine will make binary operators appear on the next line when a binary command, such as a pipe, spans multiple lines. A backslash will be used." }, switchCaseIndent: { // since: '0.1.0', category: "Format", type: "boolean", default: true, description: "SwitchCaseIndent will make switch cases be indented. As such, switch case bodies will be two levels deeper than the switch itself." }, spaceRedirects: { // since: '0.1.0', category: "Format", type: "boolean", default: true, description: "SpaceRedirects will put a space after most redirection operators. The exceptions are '>&', '<&', '>(', and '<('." }, keepPadding: { // since: '0.1.0', category: "Format", type: "boolean", default: false, description: [ "KeepPadding will keep most nodes and tokens in the same column that they were in the original source.", "This allows the user to decide how to align and pad their code with spaces.", "", "Note that this feature is best-effort and will only keep the alignment stable, so it may need some human help the first time it is run." ].join("\n"), deprecated: [ "This formatting option is flawed and buggy, and often does not result in what the user wants when the code gets complex enough.", "The next major version, v4, will remove this feature entirely.", "See: https://github.com/mvdan/sh/issues/658" ].join("\n") }, minify: { // since: '0.1.0', category: "Output", type: "boolean", default: false, description: [ "Minify will print programs in a way to save the most bytes possible.", "For example, indentation and comments are skipped, and extra whitespace is avoided when possible." ].join("\n") }, singleLine: { // since: '0.17.0', category: "Format", type: "boolean", default: false, description: [ "SingleLine will attempt to print programs in one line. For example, lists of commands or nested blocks do not use newlines in this mode.", "Note that some newlines must still appear, such as those following comments or around here-documents.", "", "Print's trailing newline when given a [*File] is not affected by this option." ].join("\n") }, functionNextLine: { // since: '0.1.0', category: "Format", type: "boolean", default: false, description: "FunctionNextLine will place a function's opening braces on the next line." } }; exports.ShSyntaxParseError = ShSyntaxParseError; exports.languages = languages; exports.options = options; exports.parsers = parsers; exports.printers = printers;