2
2
@module @ember /template
3
3
*/
4
4
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 ;
7
9
8
10
constructor ( string : string ) {
9
- this . string = string ;
11
+ this . __string = string ;
10
12
}
11
13
12
14
toString ( ) : string {
13
- return `${ this . string } ` ;
15
+ return `${ this . __string } ` ;
14
16
}
15
17
16
18
toHTML ( ) : string {
@@ -35,10 +37,11 @@ function escapeChar(chr: keyof typeof escape) {
35
37
return escape [ chr ] ;
36
38
}
37
39
38
- export function escapeExpression ( string : any ) : string {
40
+ export function escapeExpression ( string : unknown ) : string {
41
+ let s : string ;
39
42
if ( typeof string !== 'string' ) {
40
43
// don't escape SafeStrings, since they're already safe
41
- if ( string && string . toHTML ) {
44
+ if ( isHTMLSafe ( string ) ) {
42
45
return string . toHTML ( ) ;
43
46
} else if ( string === null || string === undefined ) {
44
47
return '' ;
@@ -49,13 +52,23 @@ export function escapeExpression(string: any): string {
49
52
// Force a string conversion as this will be done by the append regardless and
50
53
// the regex test will do this transparently behind the scenes, causing issues if
51
54
// an object's to string has escaped characters in it.
52
- string = String ( string ) ;
55
+ s = String ( string ) ;
56
+ } else {
57
+ s = string ;
53
58
}
54
59
55
- if ( ! possible . test ( string ) ) {
56
- return string ;
60
+ if ( ! possible . test ( s ) ) {
61
+ return s ;
57
62
}
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 ) ;
59
72
}
60
73
61
74
/**
@@ -82,6 +95,7 @@ export function escapeExpression(string: any): string {
82
95
83
96
@method htmlSafe
84
97
@for @ember /template
98
+ @param str {String} The string to treat as trusted.
85
99
@static
86
100
@return {SafeString } A string that will not be HTML escaped by Handlebars.
87
101
@public
@@ -114,6 +128,8 @@ export function htmlSafe(str: string): SafeString {
114
128
@return {Boolean } `true` if the string was decorated with `htmlSafe`, `false` otherwise.
115
129
@public
116
130
*/
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
+ ) ;
119
135
}
0 commit comments