diff --git a/packages/@glimmer/integration-tests/test/compiler/compile-options-test.ts b/packages/@glimmer/integration-tests/test/compiler/compile-options-test.ts index aab0c13d7..b8096882a 100644 --- a/packages/@glimmer/integration-tests/test/compiler/compile-options-test.ts +++ b/packages/@glimmer/integration-tests/test/compiler/compile-options-test.ts @@ -72,6 +72,52 @@ module('[glimmer-compiler] precompile', ({ test }) => { assert.equal(componentName, 'rental', 'customized component name was used'); }); + test('customizeComponentName is not invoked on curly components', function (assert) { + let wire = JSON.parse( + precompile('{{#my-component}}hello{{/my-component}}', { + customizeComponentName(input: string) { + return input.toUpperCase(); + }, + }) + ); + + let block: WireFormat.SerializedTemplateBlock = JSON.parse(wire.block); + + let [[, componentNameExpr]] = block[0] as [WireFormat.Statements.Block]; + + glimmerAssert( + Array.isArray(componentNameExpr) && + componentNameExpr[0] === SexpOpcodes.GetFreeAsComponentHead, + `component name is a free variable lookup` + ); + + let componentName = block[3][componentNameExpr[1]]; + assert.equal(componentName, 'my-component', 'original component name was used'); + }); + + test('customizeComponentName is not invoked on angle-bracket-like name invoked with curlies', function (assert) { + let wire = JSON.parse( + precompile('{{#MyComponent}}hello{{/MyComponent}}', { + customizeComponentName(input: string) { + return input.toUpperCase(); + }, + }) + ); + + let block: WireFormat.SerializedTemplateBlock = JSON.parse(wire.block); + + let [[, componentNameExpr]] = block[0] as [WireFormat.Statements.Block]; + + glimmerAssert( + Array.isArray(componentNameExpr) && + componentNameExpr[0] === SexpOpcodes.GetFreeAsComponentHead, + `component name is a free variable lookup` + ); + + let componentName = block[3][componentNameExpr[1]]; + assert.equal(componentName, 'MyComponent', 'original component name was used'); + }); + test('lowercased names are not resolved or customized in resolution mode', (assert) => { let wire = JSON.parse( precompile('', { diff --git a/packages/@glimmer/syntax/lib/symbol-table.ts b/packages/@glimmer/syntax/lib/symbol-table.ts index 7c5a11248..a5fce6039 100644 --- a/packages/@glimmer/syntax/lib/symbol-table.ts +++ b/packages/@glimmer/syntax/lib/symbol-table.ts @@ -2,6 +2,7 @@ import { Core, Dict, SexpOpcodes } from '@glimmer/interfaces'; import { dict } from '@glimmer/util'; import { ASTv2 } from '..'; +import { isUpperCase } from './utils'; export abstract class SymbolTable { static top( @@ -86,7 +87,13 @@ export class ProgramSymbolTable extends SymbolTable { } allocateFree(name: string, resolution: ASTv2.FreeVarResolution): number { - if (resolution.resolution() === SexpOpcodes.GetFreeAsComponentHead) { + // If the name in question is an uppercase (i.e. angle-bracket) component invocation, run + // the optional `customizeComponentName` function provided to the precompiler. + if ( + resolution.resolution() === SexpOpcodes.GetFreeAsComponentHead && + resolution.isAngleBracket && + isUpperCase(name) + ) { name = this.customizeComponentName(name); } diff --git a/packages/@glimmer/syntax/lib/utils.ts b/packages/@glimmer/syntax/lib/utils.ts index bd717f251..05fe8483f 100644 --- a/packages/@glimmer/syntax/lib/utils.ts +++ b/packages/@glimmer/syntax/lib/utils.ts @@ -114,3 +114,11 @@ export function printLiteral(literal: ASTv1.Literal): string { return JSON.stringify(literal.value); } } + +export function isUpperCase(tag: string): boolean { + return tag[0] === tag[0].toUpperCase() && tag[0] !== tag[0].toLowerCase(); +} + +export function isLowerCase(tag: string): boolean { + return tag[0] === tag[0].toLowerCase() && tag[0] !== tag[0].toUpperCase(); +} diff --git a/packages/@glimmer/syntax/lib/v2-a/loose-resolution.ts b/packages/@glimmer/syntax/lib/v2-a/loose-resolution.ts index c4b950a98..2de376faa 100644 --- a/packages/@glimmer/syntax/lib/v2-a/loose-resolution.ts +++ b/packages/@glimmer/syntax/lib/v2-a/loose-resolution.ts @@ -39,7 +39,7 @@ export function BlockSyntaxContext(node: ASTv1.BlockStatement): ASTv2.FreeVarRes export function ComponentSyntaxContext(node: ASTv1.PathExpression): ASTv2.FreeVarResolution | null { if (isSimplePath(node)) { - return ASTv2.LooseModeResolution.namespaced(ASTv2.FreeVarNamespace.Component); + return ASTv2.LooseModeResolution.namespaced(ASTv2.FreeVarNamespace.Component, true); } else { return null; } diff --git a/packages/@glimmer/syntax/lib/v2-a/normalize.ts b/packages/@glimmer/syntax/lib/v2-a/normalize.ts index 2cd78a414..fd189104d 100644 --- a/packages/@glimmer/syntax/lib/v2-a/normalize.ts +++ b/packages/@glimmer/syntax/lib/v2-a/normalize.ts @@ -10,6 +10,7 @@ import { SourceSpan } from '../source/span'; import { SpanList } from '../source/span-list'; import { BlockSymbolTable, ProgramSymbolTable, SymbolTable } from '../symbol-table'; import { generateSyntaxError } from '../syntax-error'; +import { isLowerCase, isUpperCase } from '../utils'; import * as ASTv1 from '../v1/api'; import b from '../v1/parser-builders'; import * as ASTv2 from './api'; @@ -866,14 +867,6 @@ class ElementChildren extends Children { } } -function isUpperCase(tag: string): boolean { - return tag[0] === tag[0].toUpperCase() && tag[0] !== tag[0].toLowerCase(); -} - -function isLowerCase(tag: string): boolean { - return tag[0] === tag[0].toLowerCase() && tag[0] !== tag[0].toUpperCase(); -} - function printPath(node: ASTv1.PathExpression | ASTv1.CallNode): string { if (node.type !== 'PathExpression' && node.path.type === 'PathExpression') { return printPath(node.path); diff --git a/packages/@glimmer/syntax/lib/v2-a/objects/resolution.ts b/packages/@glimmer/syntax/lib/v2-a/objects/resolution.ts index 055eef495..9a651e450 100644 --- a/packages/@glimmer/syntax/lib/v2-a/objects/resolution.ts +++ b/packages/@glimmer/syntax/lib/v2-a/objects/resolution.ts @@ -22,6 +22,8 @@ export class StrictResolution { serialize(): SerializedResolution { return 'Strict'; } + + readonly isAngleBracket = false; } export const STRICT_RESOLUTION = new StrictResolution(); @@ -46,11 +48,14 @@ export class LooseModeResolution { * * @see {NamespacedAmbiguity} */ - static namespaced(namespace: FreeVarNamespace): LooseModeResolution { - return new LooseModeResolution({ - namespaces: [namespace], - fallback: false, - }); + static namespaced(namespace: FreeVarNamespace, isAngleBracket = false): LooseModeResolution { + return new LooseModeResolution( + { + namespaces: [namespace], + fallback: false, + }, + isAngleBracket + ); } /** @@ -136,7 +141,7 @@ export class LooseModeResolution { return new LooseModeResolution({ namespaces: [FreeVarNamespace.Helper], fallback: true }); } - constructor(readonly ambiguity: Ambiguity) {} + constructor(readonly ambiguity: Ambiguity, readonly isAngleBracket = false) {} resolution(): GetContextualFreeOp { if (this.ambiguity.namespaces.length === 0) {