Skip to content

Commit 22b3879

Browse files
authored
Merge pull request #20373 from emberjs/just-SafeString
[BUGFIX BETA] Make the type for `SafeString` public
2 parents 7b11de3 + 00c69d2 commit 22b3879

File tree

2 files changed

+31
-13
lines changed

2 files changed

+31
-13
lines changed

packages/@ember/-internals/glimmer/lib/utils/string.ts

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
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
}

packages/@ember/template/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
export { htmlSafe, isHTMLSafe } from '@ember/-internals/glimmer';
1+
// NOTE: this intentionally *only* exports the *type* `SafeString`, not its
2+
// value, since it should not be constructed by users.
3+
export { htmlSafe, isHTMLSafe, type SafeString } from '@ember/-internals/glimmer';

0 commit comments

Comments
 (0)