{"version":3,"file":"plugin.js","sources":["src/lib/elements.ts","src/lib/extractAttributes.ts","src/lib/snipTagContent.ts","src/lib/getText.ts","src/options.ts","src/print/helpers.ts","src/print/doc-helpers.ts","src/print/node-helpers.ts","src/print/index.ts","src/embed.ts","src/index.ts"],"sourcesContent":["export type TagName = keyof HTMLElementTagNameMap | 'svg';\r\n\r\n// @see http://xahlee.info/js/html5_non-closing_tag.html\r\nexport const selfClosingTags = [\r\n 'area',\r\n 'base',\r\n 'br',\r\n 'col',\r\n 'embed',\r\n 'hr',\r\n 'img',\r\n 'input',\r\n 'link',\r\n 'meta',\r\n 'param',\r\n 'source',\r\n 'track',\r\n 'wbr',\r\n];\r\n\r\n// https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements#Elements\r\nexport const blockElements: TagName[] = [\r\n 'address',\r\n 'article',\r\n 'aside',\r\n 'blockquote',\r\n 'details',\r\n 'dialog',\r\n 'dd',\r\n 'div',\r\n 'dl',\r\n 'dt',\r\n 'fieldset',\r\n 'figcaption',\r\n 'figure',\r\n 'footer',\r\n 'form',\r\n 'h1',\r\n 'h2',\r\n 'h3',\r\n 'h4',\r\n 'h5',\r\n 'h6',\r\n 'header',\r\n 'hgroup',\r\n 'hr',\r\n 'li',\r\n 'main',\r\n 'nav',\r\n 'ol',\r\n 'p',\r\n 'pre',\r\n 'section',\r\n 'table',\r\n 'ul',\r\n];\r\n\r\n/**\r\n * HTML attributes that we may safely reformat (trim whitespace, add or remove newlines)\r\n */\r\nexport const formattableAttributes: string[] = [\r\n // None at the moment\r\n // Prettier HTML does not format attributes at all\r\n // and to be consistent we leave this array empty for now\r\n];\r\n","import { AttributeNode, TextNode } from '../print/nodes';\r\n\r\nexport function extractAttributes(html: string): AttributeNode[] {\r\n const extractAttributesRegex = /<[a-z]+[\\s\\n]*([\\s\\S]*?)>/im;\r\n const attributeRegex = /([^\\s=]+)(?:=(?:(?:(\"|')([\\s\\S]*?)\\2)|(?:(\\S+?)(?:\\s|>|$))))?/gim;\r\n\r\n const [, attributesString] = html.match(extractAttributesRegex)!;\r\n\r\n const attrs: AttributeNode[] = [];\r\n\r\n let match: RegExpMatchArray | null;\r\n while ((match = attributeRegex.exec(attributesString))) {\r\n const [all, name, quotes, valueQuoted, valueUnquoted] = match;\r\n const value = valueQuoted || valueUnquoted;\r\n const attrStart = match.index!;\r\n\r\n let valueNode: AttributeNode['value'];\r\n if (!value) {\r\n valueNode = true;\r\n } else {\r\n let valueStart = attrStart + name.length;\r\n if (quotes) {\r\n valueStart += 2;\r\n }\r\n\r\n valueNode = [\r\n {\r\n type: 'Text',\r\n data: value,\r\n start: valueStart,\r\n end: valueStart + value.length,\r\n } as TextNode,\r\n ];\r\n }\r\n\r\n attrs.push({\r\n type: 'Attribute',\r\n name,\r\n value: valueNode,\r\n start: attrStart,\r\n end: attrStart + all.length,\r\n });\r\n }\r\n\r\n return attrs;\r\n}\r\n","export const snippedTagContentAttribute = '✂prettier:content✂';\r\n\r\nexport function snipScriptAndStyleTagContent(source: string): string {\r\n let scriptMatchSpans = getMatchIndexes('script');\r\n let styleMatchSpans = getMatchIndexes('style');\r\n\r\n return snipTagContent(\r\n snipTagContent(source, 'script', '{}', styleMatchSpans),\r\n 'style',\r\n '',\r\n scriptMatchSpans,\r\n );\r\n\r\n function getMatchIndexes(tagName: string) {\r\n const regex = getRegexp(tagName);\r\n const indexes: [number, number][] = [];\r\n let match = null;\r\n while ((match = regex.exec(source)) != null) {\r\n if (source.slice(match.index, match.index + 4) !== '|<${tagName}([^]*?)>([^]*?)<\\/${tagName}>`, 'g');\r\n }\r\n}\r\n\r\nexport function hasSnippedContent(text: string) {\r\n return text.includes(snippedTagContentAttribute);\r\n}\r\n\r\nexport function unsnipContent(text: string): string {\r\n const regex = /(<\\w+.*?)\\s*✂prettier:content✂=\"(.*?)\">.*?(?=<\\/)/gi;\r\n\r\n return text.replace(regex, (_, start, encodedContent) => {\r\n const content = Buffer.from(encodedContent, 'base64').toString('utf8');\r\n return `${start}>${content}`;\r\n });\r\n}\r\n","import { ParserOptions } from 'prettier';\r\nimport { Node } from '../print/nodes';\r\nimport { hasSnippedContent, unsnipContent } from './snipTagContent';\r\n\r\nexport function getText(node: Node, options: ParserOptions, unsnip = false) {\r\n const leadingComments: Node[] = (node as any).leadingComments;\r\n const text = options.originalText.slice(\r\n options.locStart(\r\n // if there are comments before the node they are not included\r\n // in the `start` of the node itself\r\n (leadingComments && leadingComments[0]) || node,\r\n ),\r\n options.locEnd(node),\r\n );\r\n\r\n if (!unsnip || !hasSnippedContent(text)) {\r\n return text;\r\n }\r\n\r\n return unsnipContent(text);\r\n}\r\n","import { ParserOptions, SupportOption } from 'prettier';\r\n\r\ndeclare module 'prettier' {\r\n interface RequiredOptions extends PluginOptions {}\r\n}\r\n\r\nexport interface PluginOptions {\r\n svelteSortOrder: SortOrder;\r\n svelteStrictMode: boolean;\r\n svelteBracketNewLine: boolean;\r\n svelteAllowShorthand: boolean;\r\n svelteIndentScriptAndStyle: boolean;\r\n}\r\n\r\nfunction makeChoice(choice: string) {\r\n return { value: choice, description: choice };\r\n}\r\n\r\nexport const options: Record = {\r\n svelteSortOrder: {\r\n since: '0.6.0',\r\n category: 'Svelte',\r\n type: 'choice',\r\n default: 'options-scripts-markup-styles',\r\n description: 'Sort order for scripts, markup, and styles',\r\n choices: [\r\n makeChoice('options-scripts-markup-styles'),\r\n makeChoice('options-scripts-styles-markup'),\r\n makeChoice('options-markup-styles-scripts'),\r\n makeChoice('options-markup-scripts-styles'),\r\n makeChoice('options-styles-markup-scripts'),\r\n makeChoice('options-styles-scripts-markup'),\r\n makeChoice('scripts-options-markup-styles'),\r\n makeChoice('scripts-options-styles-markup'),\r\n makeChoice('markup-options-styles-scripts'),\r\n makeChoice('markup-options-scripts-styles'),\r\n makeChoice('styles-options-markup-scripts'),\r\n makeChoice('styles-options-scripts-markup'),\r\n makeChoice('scripts-markup-options-styles'),\r\n makeChoice('scripts-styles-options-markup'),\r\n makeChoice('markup-styles-options-scripts'),\r\n makeChoice('markup-scripts-options-styles'),\r\n makeChoice('styles-markup-options-scripts'),\r\n makeChoice('styles-scripts-options-markup'),\r\n makeChoice('scripts-markup-styles-options'),\r\n makeChoice('scripts-styles-markup-options'),\r\n makeChoice('markup-styles-scripts-options'),\r\n makeChoice('markup-scripts-styles-options'),\r\n makeChoice('styles-markup-scripts-options'),\r\n makeChoice('styles-scripts-markup-options'),\r\n makeChoice('none'),\r\n // Deprecated, keep in 2.x for backwards-compatibility. svelte:options will be moved to the top\r\n makeChoice('scripts-markup-styles'),\r\n makeChoice('scripts-styles-markup'),\r\n makeChoice('markup-styles-scripts'),\r\n makeChoice('markup-scripts-styles'),\r\n makeChoice('styles-markup-scripts'),\r\n makeChoice('styles-scripts-markup'),\r\n ],\r\n },\r\n svelteStrictMode: {\r\n since: '0.7.0',\r\n category: 'Svelte',\r\n type: 'boolean',\r\n default: false,\r\n description: 'More strict HTML syntax: self-closed tags, quotes in attributes',\r\n },\r\n svelteBracketNewLine: {\r\n since: '0.6.0',\r\n category: 'Svelte',\r\n type: 'boolean',\r\n description: 'Put the `>` of a multiline element on a new line',\r\n deprecated: '2.5.0',\r\n },\r\n svelteAllowShorthand: {\r\n since: '1.0.0',\r\n category: 'Svelte',\r\n type: 'boolean',\r\n default: true,\r\n description:\r\n 'Option to enable/disable component attribute shorthand if attribute name and expressions are same',\r\n },\r\n svelteIndentScriptAndStyle: {\r\n since: '1.2.0',\r\n category: 'Svelte',\r\n type: 'boolean',\r\n default: true,\r\n description:\r\n 'Whether or not to indent the code inside `}\r\n getText(node, options, true),\r\n ),\r\n embeddedOptions,\r\n );\r\n if (node.forceSingleLine) {\r\n docs = removeLines(docs);\r\n }\r\n if (node.removeParentheses) {\r\n docs = removeParentheses(docs);\r\n }\r\n return docs;\r\n } catch (e) {\r\n return getText(node, options, true);\r\n }\r\n }\r\n\r\n const embedType = (\r\n tag: 'script' | 'style' | 'template',\r\n parser: 'typescript' | 'babel-ts' | 'css' | 'pug',\r\n isTopLevel: boolean,\r\n ) =>\r\n embedTag(\r\n tag,\r\n options.originalText,\r\n path,\r\n (content) => formatBodyContent(content, parser, textToDoc, options),\r\n print,\r\n isTopLevel,\r\n options,\r\n );\r\n\r\n const embedScript = (isTopLevel: boolean) =>\r\n embedType(\r\n 'script',\r\n // Use babel-ts as fallback because the absence does not mean the content is not TS,\r\n // the user could have set the default language. babel-ts will format things a little\r\n // bit different though, especially preserving parentheses around dot notation which\r\n // fixes https://github.com/sveltejs/prettier-plugin-svelte/issues/218\r\n isTypeScript(node) ? 'typescript' : 'babel-ts',\r\n isTopLevel,\r\n );\r\n const embedStyle = (isTopLevel: boolean) => embedType('style', 'css', isTopLevel);\r\n const embedPug = () => embedType('template', 'pug', false);\r\n\r\n switch (node.type) {\r\n case 'Script':\r\n return embedScript(true);\r\n case 'Style':\r\n return embedStyle(true);\r\n case 'Element': {\r\n if (node.name === 'script') {\r\n return embedScript(false);\r\n } else if (node.name === 'style') {\r\n return embedStyle(false);\r\n } else if (isPugTemplate(node)) {\r\n return embedPug();\r\n }\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\nfunction forceIntoExpression(statement: string) {\r\n // note the trailing newline: if the statement ends in a // comment,\r\n // we can't add the closing bracket right afterwards\r\n return `(${statement}\\n)`;\r\n}\r\n\r\nfunction expressionParser(text: string, parsers: any, options: any) {\r\n const ast = parsers.babel(text, parsers, options);\r\n\r\n return { ...ast, program: ast.program.body[0].expression };\r\n}\r\n\r\nfunction preformattedBody(str: string): Doc {\r\n const firstNewline = /^[\\t\\f\\r ]*\\n/;\r\n const lastNewline = /\\n[\\t\\f\\r ]*$/;\r\n\r\n // If we do not start with a new line prettier might try to break the opening tag\r\n // to keep it together with the string. Use a literal line to skip indentation.\r\n return concat([literalline, str.replace(firstNewline, '').replace(lastNewline, ''), hardline]);\r\n}\r\n\r\nfunction getSnippedContent(node: Node) {\r\n const encodedContent = getAttributeTextValue(snippedTagContentAttribute, node);\r\n\r\n if (encodedContent) {\r\n return Buffer.from(encodedContent, 'base64').toString('utf-8');\r\n } else {\r\n return '';\r\n }\r\n}\r\n\r\nfunction formatBodyContent(\r\n content: string,\r\n parser: 'typescript' | 'babel-ts' | 'css' | 'pug',\r\n textToDoc: (text: string, options: object) => Doc,\r\n options: ParserOptions & { pugTabWidth?: number },\r\n) {\r\n try {\r\n const body = textToDoc(content, { parser });\r\n\r\n if (parser === 'pug' && typeof body === 'string') {\r\n // Pug returns no docs but a final string.\r\n // Therefore prepend the line offsets\r\n const whitespace = options.useTabs\r\n ? '\\t'\r\n : ' '.repeat(\r\n options.pugTabWidth && options.pugTabWidth > 0\r\n ? options.pugTabWidth\r\n : options.tabWidth,\r\n );\r\n const pugBody = body\r\n .split('\\n')\r\n .map((line) => (line ? whitespace + line : line))\r\n .join('\\n');\r\n return concat([hardline, pugBody]);\r\n }\r\n\r\n const indentIfDesired = (doc: Doc) =>\r\n options.svelteIndentScriptAndStyle ? indent(doc) : doc;\r\n trimRight([body], isLine);\r\n return concat([indentIfDesired(concat([hardline, body])), hardline]);\r\n } catch (error) {\r\n if (process.env.PRETTIER_DEBUG) {\r\n throw error;\r\n }\r\n\r\n // We will wind up here if there is a syntax error in the embedded code. If we throw an error,\r\n // prettier will try to print the node with the printer. That will fail with a hard-to-interpret\r\n // error message (e.g. \"Unsupported node type\", referring to `