UNPKG

prettier-plugin-ember-template-tag

Version:
174 lines (147 loc) 4.76 kB
import type { Node } from '@babel/types'; import type { AstPath, doc, Options as PrettierOptions, Printer, } from 'prettier'; import { printers as estreePrinters } from 'prettier/plugins/estree.js'; import { TEMPLATE_TAG_CLOSE, TEMPLATE_TAG_OPEN } from '../config.js'; import type { Options } from '../options.js'; import { isGlimmerTemplate, isGlimmerTemplateParent, } from '../types/glimmer.js'; import { assert } from '../utils/index.js'; import { fixPreviousPrint, saveCurrentPrintOnSiblingNode, } from './ambiguity.js'; import { printTemplateContent, printTemplateTag } from './template.js'; const estreePrinter = estreePrinters['estree'] as Printer<Node | undefined>; export const printer: Printer<Node | undefined> = { ...estreePrinter, getVisitorKeys(node, nonTraversableKeys) { if (node && isGlimmerTemplate(node)) { return []; } return estreePrinter.getVisitorKeys?.(node, nonTraversableKeys) || []; }, print( path: AstPath<Node | undefined>, options: Options, print: (path: AstPath<Node | undefined>) => doc.builders.Doc, args: unknown, ) { const { node } = path; if (isGlimmerTemplateParent(node)) { if (checkPrettierIgnore(path)) { return printRawText(path, options); } else { let printed = estreePrinter.print(path, options, print, args); assert('Expected Glimmer doc to be an array', Array.isArray(printed)); trimPrinted(printed); // Remove semicolons so we can manage them ourselves if (docMatchesString(printed[0], ';')) { printed.shift(); } if (docMatchesString(printed.at(-1), ';')) { printed.pop(); } trimPrinted(printed); // Always remove export default so we start with a blank slate if ( docMatchesString(printed[0], 'export') && docMatchesString(printed[1], 'default') ) { printed = printed.slice(2); trimPrinted(printed); } if (options.templateExportDefault) { printed.unshift('export ', 'default '); } saveCurrentPrintOnSiblingNode(path, printed); return printed; } } if (options.semi && node?.extra?.['prevTemplatePrinted']) { fixPreviousPrint( node.extra['prevTemplatePrinted'] as doc.builders.Doc[], path, options, print, args, ); } return estreePrinter.print(path, options, print, args); }, /** Prints embedded GlimmerExpressions/GlimmerTemplates. */ embed(path: AstPath<Node | undefined>, embedOptions: PrettierOptions) { const { node } = path; return async (textToDoc) => { if (node && isGlimmerTemplate(node)) { if (checkPrettierIgnore(path)) { return printRawText(path, embedOptions as Options); } try { const content = await printTemplateContent( node.extra.template.contents, textToDoc, embedOptions as Options, ); const printed = printTemplateTag(content); saveCurrentPrintOnSiblingNode(path, printed); return printed; } catch (error) { console.error(error); const printed = [printRawText(path, embedOptions as Options)]; saveCurrentPrintOnSiblingNode(path, printed); return printed; } } // Nothing to embed, so move on to the regular printer. return; }; }, }; /** Remove the empty strings that Prettier added so we can manage them. */ function trimPrinted(printed: doc.builders.Doc[]): void { while (docMatchesString(printed[0], '')) { printed.shift(); } while (docMatchesString(printed.at(-1), '')) { printed.pop(); } } function printRawText( { node }: AstPath<Node | undefined>, options: Options, ): string { if (!node) { return ''; } if (isGlimmerTemplate(node)) { return ( TEMPLATE_TAG_OPEN + node.extra.template.contents + TEMPLATE_TAG_CLOSE ); } assert('expected start', typeof node.start == 'number'); assert('expected end', typeof node.end == 'number'); return options.originalText.slice(node.start, node.end); } function hasPrettierIgnore(path: AstPath<Node | undefined>): boolean { return path.node?.leadingComments?.at(-1)?.value.trim() === 'prettier-ignore'; } function checkPrettierIgnore(path: AstPath<Node | undefined>): boolean { return ( hasPrettierIgnore(path) || (!!path.getParentNode() && path.callParent((parent) => checkPrettierIgnore(parent))) ); } function docMatchesString( doc: doc.builders.Doc | undefined, string: string, ): doc is string { return typeof doc === 'string' && doc.trim() === string; }