import { composeParslet } from './Parslet.js' import type { RootResult } from '../result/RootResult.js' import { Precedence } from '../Precedence.js' import { getTemplateLiteralLiteral } from '../lexer/LexerRules.js' import { Parser } from '../Parser.js' import { Lexer } from '../lexer/Lexer.js' export const templateLiteralParslet = composeParslet({ name: 'templateLiteralParslet', accept: type => type === 'TemplateLiteral', parsePrefix: parser => { const text = parser.lexer.current.text parser.consume('TemplateLiteral') const literals: string[] = [] const interpolations = [] as RootResult[] let currentText = text.slice(1, -1) const advanceLiteral = (): void => { const literal = getTemplateLiteralLiteral(currentText) ?? '' // We collect backslashes for total length, but need to replace literals.push(literal.replace(/\\`/gv, '`')) currentText = currentText.slice(literal.length) } // The first can be the empty string (at least one literal // should be populated) advanceLiteral() while (true) { if (currentText.startsWith('${')) { currentText = currentText.slice(2) let templateParser let interpolationType let snipped = currentText let remnant = '' while (true) { // Some tokens (like hyphen) may not be recognized by the parser, // so we avoid processing them (may be part of a literal) try { templateParser = new Parser( parser.grammar, Lexer.create(parser.lexer.lexerRules, snipped) ) interpolationType = templateParser.parseType(Precedence.ALL) break } catch (err) { remnant = snipped.slice(-1) + remnant snipped = snipped.slice(0, -1) } } interpolations.push(interpolationType) if (templateParser.lexer.current.text !== '}') { throw new Error('unterminated interpolation') } currentText = templateParser.lexer.remaining() + remnant } else { // currentText.startsWith('`') break; } // May also be empty string if seeing `}${` or just a final `}` advanceLiteral() } return { type: 'JsdocTypeTemplateLiteral', literals, interpolations } } })