UNPKG

prettier-plugin-ember-template-tag

Version:
140 lines (124 loc) 4.03 kB
import { traverse } from '@babel/core'; import type { BlockStatement, File, Node, ObjectExpression, StaticBlock, } from '@babel/types'; import { Preprocessor } from 'content-tag'; import type { Parser } from 'prettier'; import { parsers as babelParsers } from 'prettier/plugins/babel.js'; import { PRINTER_NAME } from '../config.js'; import type { Options } from '../options.js'; import { assert } from '../utils/index.js'; import { byteToCharIndex, preprocessTemplateRange, type Template, } from './preprocess.js'; const typescript = babelParsers['babel-ts'] as Parser<Node | undefined>; const p = new Preprocessor(); /** Converts a node into a GlimmerTemplate node */ function convertNode( node: BlockStatement | ObjectExpression | StaticBlock, rawTemplate: Template, ): void { node.innerComments = []; node.extra = Object.assign(node.extra ?? {}, { isGlimmerTemplate: true as const, template: rawTemplate, }); } /** Traverses the AST and replaces the transformed template parts with other AST */ function convertAst(ast: File, templates: Template[]): void { const unprocessedTemplates = [...templates]; traverse(ast, { enter(path) { const { node } = path; if ( node.type === 'BlockStatement' || node.type === 'ObjectExpression' || node.type === 'StaticBlock' ) { const { range } = node; assert('expected range', range); const [start, end] = range; const templateIndex = unprocessedTemplates.findIndex( (t) => (t.utf16Range.start === start && t.utf16Range.end === end) || (node.type === 'ObjectExpression' && t.utf16Range.start === start - 1 && t.utf16Range.end === end + 1), ); if (templateIndex > -1) { const rawTemplate = unprocessedTemplates.splice(templateIndex, 1)[0]; if (!rawTemplate) { throw new Error( 'expected raw template because splice index came from findIndex', ); } const index = node.innerComments?.[0] && ast.comments?.indexOf(node.innerComments[0]); if (ast.comments && index !== undefined && index >= 0) { ast.comments.splice(index, 1); } convertNode(node, rawTemplate); } else { return null; } } return null; }, }); if (unprocessedTemplates.length > 0) { throw new Error( `failed to process all templates, ${unprocessedTemplates.length} remaining`, ); } } /** * Pre-processes the template info, parsing the template content to Glimmer AST, * fixing the offsets and locations of all nodes also calculates the block * params locations & ranges and adding it to the info */ export function preprocess( code: string, fileName: string, ): { code: string; templates: Template[]; } { const templates = codeToGlimmerAst(code, fileName); for (const template of templates) { code = preprocessTemplateRange(template, code); } return { templates, code }; } export const parser: Parser<Node | undefined> = { ...typescript, astFormat: PRINTER_NAME, async parse(code: string, options: Options): Promise<Node> { const preprocessed = preprocess(code, options.filepath); const ast = await typescript.parse(preprocessed.code, options); assert('expected ast', ast); convertAst(ast as File, preprocessed.templates); return ast; }, }; /** Pre-processes the template info, parsing the template content to Glimmer AST. */ export function codeToGlimmerAst(code: string, filename: string): Template[] { const rawTemplates = p.parse(code, { filename }); const templates: Template[] = rawTemplates.map((r) => ({ type: r.type, range: r.range, contentRange: r.contentRange, contents: r.contents, utf16Range: { start: byteToCharIndex(code, r.range.start), end: byteToCharIndex(code, r.range.end), }, })); return templates; }