UNPKG

@glint/core

Version:

A CLI for performing typechecking on Glimmer templates

128 lines 5.13 kB
import * as path from 'node:path'; import { isEmbeddedInClass } from './index.js'; import MappingTree, { ParseError } from '../mapping-tree.js'; import { templateToTypescript } from '../template-to-typescript.js'; export function calculateCompanionTemplateSpans(ts, ast, script, template, environment) { let errors = []; let directives = []; let partialSpans = []; let templateConfig = environment.getStandaloneTemplateConfig(); if (!templateConfig) { errors.push({ source: template, location: { start: 0, end: template.contents.length }, message: `No active Glint environment (${environment.names.join(', ')}) supports standalone template files`, }); return { errors, directives, partialSpans }; } let { typesModule, specialForms } = templateConfig; let useJsDoc = environment.isUntypedScript(script.filename); let targetNode = findCompanionTemplateTarget(ts, ast); if (targetNode && ts.isClassLike(targetNode)) { let rewriteResult = templateToTypescript(template.contents, { typesModule, specialForms, useJsDoc, backingValue: isEmbeddedInClass(ts, targetNode) ? 'this' : undefined, }); pushTransformedTemplate(rewriteResult, { insertionPoint: targetNode.getEnd() - 1, prefix: `static {\n`, suffix: '}\n', }); } else { let backingValue; if (targetNode) { let moduleName = path.basename(script.filename, path.extname(script.filename)); backingValue = useJsDoc ? `(/** @type {typeof import('./${moduleName}').default} */ ({}))` : `({} as unknown as typeof import('./${moduleName}').default)`; } let rewriteResult = templateToTypescript(template.contents, { typesModule, backingValue, specialForms, useJsDoc, }); pushTransformedTemplate(rewriteResult, { insertionPoint: script.contents.length, prefix: '\n', suffix: ';\n', }); } return { errors, directives, partialSpans }; function pushTransformedTemplate(transformedTemplate, options) { errors.push(...transformedTemplate.errors.map(({ message, location }) => ({ message, location: location ?? { start: 0, end: template.contents.length }, source: template, }))); if (transformedTemplate.result) { directives.push(...transformedTemplate.result.directives.map(({ kind, location, areaOfEffect }) => ({ kind, location, areaOfEffect, source: template, }))); partialSpans.push({ originalFile: template, originalStart: 0, originalLength: 0, insertionPoint: options.insertionPoint, transformedSource: options.prefix, }, { originalFile: template, originalStart: 0, originalLength: template.contents.length, insertionPoint: options.insertionPoint, transformedSource: transformedTemplate.result.code, mapping: transformedTemplate.result.mapping, }, { originalFile: template, originalStart: template.contents.length - 1, originalLength: 0, insertionPoint: options.insertionPoint, transformedSource: options.suffix, }); } else { let mapping = new MappingTree({ start: 0, end: 0 }, { start: 0, end: template.contents.length }, [], new ParseError()); partialSpans.push({ originalFile: template, originalStart: 0, originalLength: template.contents.length, insertionPoint: options.insertionPoint, transformedSource: '', mapping, }); } } } function findCompanionTemplateTarget(ts, sourceFile) { let classes = Object.create(null); for (let statement of sourceFile.statements) { if (ts.isClassLike(statement)) { let mods = statement.modifiers; if (mods?.some((mod) => mod.kind === ts.SyntaxKind.DefaultKeyword) && mods.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword)) { return statement; } if (statement.name) { classes[statement.name.text] = statement; } } } for (let statement of sourceFile.statements) { if (ts.isExportAssignment(statement) && !statement.isExportEquals) { if (ts.isIdentifier(statement.expression) && statement.expression.text in classes) { return classes[statement.expression.text]; } else { return statement.expression; } } } return null; } //# sourceMappingURL=companion-file.js.map