/* eslint-disable @typescript-eslint/consistent-type-assertions */ /* eslint-disable max-lines-per-function */ /* eslint-disable function-call-argument-newline */ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ /* eslint-disable @typescript-eslint/strict-boolean-expressions */ import { emptyArray, toArray, ILogger, camelCase, noop, getResourceKeyFor, allResources, IPlatform, pascalCase, createImplementationRegister, registrableMetadataKey, isString, } from '@aurelia/kernel'; import { IExpressionParser, createPrimitiveLiteralExpression, } from '@aurelia/expression-parser'; import { IAttrMapper } from './attribute-mapper'; import { ITemplateElementFactory } from './template-element-factory'; import { itHydrateAttribute, itHydrateElement, itHydrateLetElement, itHydrateTemplateController, itInterpolation, itLetBinding, itSetAttribute, itSetClassAttribute, itSetProperty, itSetStyleAttribute, itTextBinding, itPropertyBinding, itSpreadElementProp, itSpreadTransferedBinding, itSpreadValueBinding, IInstruction, type HydrateAttributeInstruction, type HydrateElementInstruction, type HydrateLetElementInstruction, type HydrateTemplateController, type InterpolationInstruction, type LetBindingInstruction, type SetAttributeInstruction, type SetClassAttributeInstruction, type SetPropertyInstruction, type SetStyleAttributeInstruction, type TextBindingInstruction, type PropertyBindingInstruction, type SpreadElementPropBindingInstruction, type SpreadTransferedBindingInstruction, type SpreadValueBindingInstruction, } from './instructions'; import { AttrSyntax, IAttributeParser } from './attribute-pattern'; import { BindingCommand, BindingCommandInstance } from './binding-command'; import { etInterpolation, etIsProperty, tcObjectFreeze, tcCreateInterface, singletonRegistration, definitionTypeElement } from './utilities'; import { auLocationStart, auLocationEnd, appendManyToTemplate, appendToTemplate, insertBefore, insertManyBefore, isElement, isTextNode } from './utilities-dom'; import type { IContainer, Constructable, Key, IRegistry, } from '@aurelia/kernel'; import type { AnyBindingExpression, IsBindingBehavior, } from '@aurelia/expression-parser'; import type { IAttributeComponentDefinition, ICompiledElementComponentDefinition, IComponentBindablePropDefinition, IDomPlatform, IElementComponentDefinition, StringBindingMode, } from './interfaces-template-compiler'; import { ErrorNames, createMappedError } from './errors'; import { ITemplateCompiler } from './interfaces-template-compiler'; const auslotAttr = 'au-slot'; const defaultSlotName = 'default'; export const generateElementName = ((id) => () => `anonymous-${++id}`)(0); /** * Result of classifying all attributes on an element. * Groups attributes into their semantic categories for instruction generation. */ interface IAttrClassificationResult { /** Instructions for template controllers (if, repeat, etc.) */ tcInstructions: HydrateTemplateController[] | undefined; /** Instructions for custom attributes */ attrInstructions: HydrateAttributeInstruction[] | undefined; /** Instructions for custom element bindable properties */ elBindableInstructions: IInstruction[] | undefined; /** Instructions for plain attribute bindings/interpolations */ plainAttrInstructions: IInstruction[] | undefined; /** Whether the element has the containerless attribute */ hasContainerless: boolean; } export class TemplateCompiler implements ITemplateCompiler { public static register = /*@__PURE__*/ createImplementationRegister(ITemplateCompiler); public debug: boolean = false; public resolveResources: boolean = true; public compile( definition: IElementComponentDefinition, container: IContainer, ): ICompiledElementComponentDefinition { if (definition.template == null || definition.needsCompile === false) { return definition as ICompiledElementComponentDefinition; } const context = new CompilationContext(definition, container, null, null, void 0); const template = isString(definition.template) || !definition.enhance ? context._templateFactory.createTemplate(definition.template) : definition.template as HTMLElement; const isTemplateElement = template.nodeName === TEMPLATE_NODE_NAME && (template as HTMLTemplateElement).content != null; const content = isTemplateElement ? (template as HTMLTemplateElement).content : template; const hooks = TemplateCompilerHooks.findAll(container); const ii = hooks.length; let i = 0; if (ii > 0) { while (ii > i) { hooks[i].compiling?.(template); ++i; } } if (template.hasAttribute(localTemplateIdentifier)) { throw createMappedError(ErrorNames.compiler_root_is_local, definition); } this._compileLocalElement(content, context); this._compileNode(content, context); const compiledDef = { ...definition, name: definition.name || generateElementName(), dependencies: (definition.dependencies ?? emptyArray).concat(context.deps ?? emptyArray), instructions: context.rows, surrogates: isTemplateElement ? this._compileSurrogate(template, context) : emptyArray, template, hasSlots: context.hasSlot, needsCompile: false, } satisfies ICompiledElementComponentDefinition; if (__DEV__) { if (compiledDef.instructions.some(row => row.some(instr => { if (instr.type === itHydrateElement) { return (instr as HydrateElementInstruction).res === compiledDef.name || ((instr as HydrateElementInstruction).res as ICompiledElementComponentDefinition)?.name === compiledDef.name; } return false; }))) { // eslint-disable-next-line no-console console.warn(`[DEV:aurelia] Detected unguarded self-referencing component name "${compiledDef.name}" in compiled instructions. This may lead to infinite recursion at runtime.`); } } return compiledDef; } public compileSpread( requestor: IElementComponentDefinition, attrSyntaxs: AttrSyntax[], container: IContainer, target: Element, targetDef?: IElementComponentDefinition, ): IInstruction[] { const context = new CompilationContext(requestor, container, null, null, void 0); const instructions: IInstruction[] = []; const elDef = targetDef ?? context._findElement(target.nodeName.toLowerCase()); const isCustomElement = elDef !== null; const exprParser = context._exprParser; const ii = attrSyntaxs.length; let i = 0; let attrSyntax: AttrSyntax; let attrDef: IAttributeComponentDefinition | null = null; let attrInstructions: (HydrateAttributeInstruction | HydrateTemplateController)[] | undefined; let attrBindableInstructions: IInstruction[]; let bindablesInfo: IAttributeBindablesInfo | IElementBindablesInfo; let bindable: IComponentBindablePropDefinition; let bindingCommand: BindingCommandInstance | null = null; let expr: AnyBindingExpression; let attrTarget: string; let attrValue: string; for (; ii > i; ++i) { attrSyntax = attrSyntaxs[i]; attrTarget = attrSyntax.target; attrValue = attrSyntax.rawValue; if (attrTarget === '...$attrs') { instructions.push({ type: itSpreadTransferedBinding } as SpreadTransferedBindingInstruction); continue; } bindingCommand = context._getCommand(attrSyntax); if (bindingCommand !== null && bindingCommand.ignoreAttr) { // when the binding command overrides everything // just pass the target as is to the binding command, and treat it as a normal attribute: // active.class="..." // background.style="..." // my-attr.attr="..." instructions.push(bindingCommand.build( { node: target, attr: attrSyntax, bindable: null, def: null }, context._exprParser, context._attrMapper )); // to next attribute continue; } if (isCustomElement) { // if the element is a custom element // - prioritize bindables on a custom element before plain attributes bindablesInfo = context._getBindables(elDef); bindable = bindablesInfo.attrs[attrTarget]; if (bindable !== void 0) { if (bindingCommand == null) { expr = exprParser.parse(attrValue, etInterpolation); instructions.push({ type: itSpreadElementProp, instruction: expr == null ? { type: itSetProperty, value: attrValue, to: bindable.name } as SetPropertyInstruction : { type: itInterpolation, from: expr, to: bindable.name } as InterpolationInstruction } as SpreadElementPropBindingInstruction); } else { instructions.push({ type: itSpreadElementProp, instruction: bindingCommand.build( { node: target, attr: attrSyntax, bindable, def: elDef }, context._exprParser, context._attrMapper ) } as SpreadElementPropBindingInstruction); } continue; } } attrDef = context._findAttr(attrTarget); if (attrDef !== null) { if (attrDef.isTemplateController) { throw createMappedError(ErrorNames.no_spread_template_controller, attrTarget); } attrBindableInstructions = this._compileCustomAttributeBindables( target, attrDef, attrSyntax, attrValue, bindingCommand, context, /* treatEmptyAsNoBinding */ false ); (attrInstructions ??= []).push({ type: itHydrateAttribute, // todo: def/ def.Type or def.name should be configurable // example: AOT/runtime can use def.Type, but there are situation // where instructions need to be serialized, def.name should be used res: this.resolveResources ? attrDef : attrDef.name, alias: attrDef.aliases != null && attrDef.aliases.includes(attrTarget) ? attrTarget : void 0, props: attrBindableInstructions } as HydrateAttributeInstruction); continue; } if (bindingCommand == null) { expr = exprParser.parse(attrValue, etInterpolation); // reaching here means: // + maybe a plain attribute with interpolation // + maybe a plain attribute if (expr != null) { instructions.push({ type: itInterpolation, from: expr, // if not a bindable, then ensure plain attribute are mapped correctly: // e.g: colspan -> colSpan // innerhtml -> innerHTML // minlength -> minLength etc... to: context._attrMapper.map(target, attrTarget) ?? camelCase(attrTarget) } as InterpolationInstruction); } else { switch (attrTarget) { case 'class': instructions.push({ type: itSetClassAttribute, value: attrValue } as SetClassAttributeInstruction); break; case 'style': instructions.push({ type: itSetStyleAttribute, value: attrValue } as SetStyleAttributeInstruction); break; default: // if not a custom attribute + no binding command + not a bindable + not an interpolation // then it's just a plain attribute instructions.push({ type: itSetAttribute, value: attrValue, to: attrTarget } as SetAttributeInstruction); } } } else { // reaching here means: // + a plain attribute with binding command instructions.push(bindingCommand.build( { node: target, attr: attrSyntax, bindable: null, def: null }, context._exprParser, context._attrMapper )); } } if (attrInstructions != null) { return (attrInstructions as IInstruction[]).concat(instructions); } return instructions; } /** * Compile attributes on a surrogate element (the root `