22@module @ember /template
33*/
44
5- export class SafeString {
6- public string : string ;
5+ import type { SafeString as GlimmerSafeString } from '@glimmer/runtime' ;
6+
7+ export class SafeString implements GlimmerSafeString {
8+ private __string : string ;
79
810 constructor ( string : string ) {
9- this . string = string ;
11+ this . __string = string ;
1012 }
1113
1214 toString ( ) : string {
13- return `${ this . string } ` ;
15+ return `${ this . __string } ` ;
1416 }
1517
1618 toHTML ( ) : string {
@@ -35,10 +37,11 @@ function escapeChar(chr: keyof typeof escape) {
3537 return escape [ chr ] ;
3638}
3739
38- export function escapeExpression ( string : any ) : string {
40+ export function escapeExpression ( string : unknown ) : string {
41+ let s : string ;
3942 if ( typeof string !== 'string' ) {
4043 // don't escape SafeStrings, since they're already safe
41- if ( string && string . toHTML ) {
44+ if ( isHTMLSafe ( string ) ) {
4245 return string . toHTML ( ) ;
4346 } else if ( string === null || string === undefined ) {
4447 return '' ;
@@ -49,13 +52,23 @@ export function escapeExpression(string: any): string {
4952 // Force a string conversion as this will be done by the append regardless and
5053 // the regex test will do this transparently behind the scenes, causing issues if
5154 // an object's to string has escaped characters in it.
52- string = String ( string ) ;
55+ s = String ( string ) ;
56+ } else {
57+ s = string ;
5358 }
5459
55- if ( ! possible . test ( string ) ) {
56- return string ;
60+ if ( ! possible . test ( s ) ) {
61+ return s ;
5762 }
58- return string . replace ( badChars , escapeChar ) ;
63+
64+ // SAFETY: this is technically a lie, but it's a true lie as long as the
65+ // invariant it depends on is upheld: `escapeChar` will always return a string
66+ // as long as its input is one of the characters in `escape`, and it will only
67+ // be called if it matches one of the characters in the `badChar` regex, which
68+ // is hand-maintained to match the set escaped. (It would be nice if TS could
69+ // "see" into the regex to see how this works, but that'd be quite a lot of
70+ // extra fanciness.)
71+ return s . replace ( badChars , escapeChar as ( s : string ) => string ) ;
5972}
6073
6174/**
@@ -82,6 +95,7 @@ export function escapeExpression(string: any): string {
8295
8396 @method htmlSafe
8497 @for @ember /template
98+ @param str {String} The string to treat as trusted.
8599 @static
86100 @return {SafeString } A string that will not be HTML escaped by Handlebars.
87101 @public
@@ -114,6 +128,8 @@ export function htmlSafe(str: string): SafeString {
114128 @return {Boolean } `true` if the string was decorated with `htmlSafe`, `false` otherwise.
115129 @public
116130*/
117- export function isHTMLSafe ( str : any | null | undefined ) : str is SafeString {
118- return str !== null && typeof str === 'object' && typeof str . toHTML === 'function' ;
131+ export function isHTMLSafe ( str : unknown ) : str is SafeString {
132+ return (
133+ str !== null && typeof str === 'object' && 'toHTML' in str && typeof str . toHTML === 'function'
134+ ) ;
119135}
0 commit comments