diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/runtime-template-compiler-implicit-test.ts b/packages/@ember/-internals/glimmer/tests/integration/components/runtime-template-compiler-implicit-test.ts index 5d4e975acc1..544fe30f710 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/runtime-template-compiler-implicit-test.ts +++ b/packages/@ember/-internals/glimmer/tests/integration/components/runtime-template-compiler-implicit-test.ts @@ -463,6 +463,56 @@ moduleFor( this.assertText('[before]after'); this.assertStableRerender(); } + + async '@test Can access private fields in templates'() { + await this.renderComponentModule(() => { + return class extends GlimmerishComponent { + #count = 0; + + #increment = () => { + this.#count++; + }; + + static { + template('
Count: {{this.#count}}
', { + component: this, + eval() { + return eval(arguments[0]); + }, + }); + } + }; + }); + + this.assertHTML('Count: 0
'); + this.assertStableRerender(); + } + + async '@test Private field methods work with on modifier'() { + await this.renderComponentModule(() => { + hide(on); + + return class extends GlimmerishComponent { + #message = 'Hello'; + + #updateMessage = () => { + this.#message = 'Updated!'; + }; + + static { + template('', { + component: this, + eval() { + return eval(arguments[0]); + }, + }); + } + }; + }); + + this.assertHTML(''); + this.assertStableRerender(); + } } ); diff --git a/packages/@ember/template-compiler/lib/compile-options.ts b/packages/@ember/template-compiler/lib/compile-options.ts index a5459e412c4..48a2433cbb3 100644 --- a/packages/@ember/template-compiler/lib/compile-options.ts +++ b/packages/@ember/template-compiler/lib/compile-options.ts @@ -110,7 +110,19 @@ type Evaluator = (value: string) => unknown; // https://tc39.es/ecma262/2020/#prod-IdentifierName const IDENT = /^[\p{ID_Start}$_][\p{ID_Continue}$_\u200C\u200D]*$/u; +// https://tc39.es/ecma262/#prod-PrivateIdentifier +const PRIVATE_IDENT = /^#[\p{ID_Start}$_][\p{ID_Continue}$_\u200C\u200D]*$/u; + function inScope(variable: string, evaluator: Evaluator): boolean { + // Check if it's a private field syntax + if (PRIVATE_IDENT.exec(variable)) { + // Private fields are always considered "in scope" when referenced in a template + // since they are class members, not lexical variables. The actual access check + // will happen at runtime when the template accesses `this.#fieldName`. + // We just need to ensure they're treated as valid identifiers and passed through. + return true; + } + // If the identifier is not a valid JS identifier, it's definitely not in scope if (!IDENT.exec(variable)) { return false; diff --git a/packages/@ember/template-compiler/lib/template.ts b/packages/@ember/template-compiler/lib/template.ts index 51f77b71f8f..6becf8affe0 100644 --- a/packages/@ember/template-compiler/lib/template.ts +++ b/packages/@ember/template-compiler/lib/template.ts @@ -128,7 +128,7 @@ export interface ExplicitClassOptions