Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { template } from '@ember/template-compiler/runtime';
import { ALLOWED_GLOBALS } from '@ember/template-compiler';
import { RenderingTestCase, defineSimpleModifier, moduleFor } from 'internal-test-helpers';
import GlimmerishComponent from '../../utils/glimmerish-component';
import { on } from '@ember/modifier/on';
Expand Down Expand Up @@ -336,3 +337,32 @@ moduleFor(
}
}
);

// These tests are more to ensure that we don't accidentally break anything from
// RFC#1070 -- but explicit scope does not _implicitly_ get access to anything
// so the globals here are still just passed in to the scope bag, as if they were
// normal variables.
moduleFor(
'Strict Mode - Runtime Template Compiler (explicit) - allowed globals from RFC#1070',
class AllowedGlobalsTest extends RenderingTestCase {
static {
for (let globalName of ALLOWED_GLOBALS) {
if (!(globalName in globalThis)) continue;

// @ts-expect-error - this *is* generally unsafe
this.prototype[`@test Can use ${globalName}`] = async function () {
await this.renderComponentModule(() => {
return template(`{{if ${globalName} "exists"}}`, {
scope: () => ({
// @ts-expect-error - dynamic test gets minimally type safety
[globalName]: globalThis[globalName],
}),
});
});

this.assertStableRerender();
};
}
}
}
);
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { template } from '@ember/template-compiler/runtime';
import { ALLOWED_GLOBALS } from '@ember/template-compiler';
import { RenderingTestCase, defineSimpleModifier, moduleFor } from 'internal-test-helpers';
import GlimmerishComponent from '../../utils/glimmerish-component';
import { on } from '@ember/modifier/on';
Expand Down Expand Up @@ -466,6 +467,30 @@ moduleFor(
}
);

moduleFor(
'Strict Mode - Runtime Template Compiler (implicit) - allowed globals from RFC#1070',
class AllowedGlobalsTest extends RenderingTestCase {
static {
for (let globalName of ALLOWED_GLOBALS) {
if (!(globalName in globalThis)) continue;

// @ts-expect-error - this *is* generally unsafe
AllowedGlobalsTest.prototype[`@test Can use ${globalName}`] = async function () {
await this.renderComponentModule(() => {
return template(`{{if ${globalName} "exists"}}`, {
eval() {
return eval(arguments[0]);
},
});
});

this.assertStableRerender();
};
}
}
}
);

/**
* This function is used to hide a variable from the transpiler, so that it
* doesn't get removed as "unused". It does not actually do anything with the
Expand Down
2 changes: 2 additions & 0 deletions packages/@ember/template-compiler/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './lib/public-api';

export { ALLOWED_GLOBALS } from './lib/plugins/allowed-globals';
9 changes: 8 additions & 1 deletion packages/@ember/template-compiler/lib/compile-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
STRICT_MODE_KEYWORDS,
STRICT_MODE_TRANSFORMS,
} from './plugins/index';
import { ALLOWED_GLOBALS } from './plugins/allowed-globals';
import type { EmberPrecompileOptions, PluginFunc } from './types';
import COMPONENT_NAME_SIMPLE_DASHERIZE_CACHE from './dasherize-component-name';

Expand Down Expand Up @@ -39,6 +40,10 @@ function buildCompileOptions(_options: EmberPrecompileOptions): EmberPrecompileO
const globalScopeEvaluator = (value: string) => new Function(`return ${value};`)();

options.lexicalScope = (variable: string) => {
if (ALLOWED_GLOBALS.has(variable)) {
return variable in globalThis;
}

if (inScope(variable, localScopeEvaluator)) {
return !inScope(variable, globalScopeEvaluator);
}
Expand All @@ -52,7 +57,9 @@ function buildCompileOptions(_options: EmberPrecompileOptions): EmberPrecompileO
if ('scope' in options) {
const scope = (options.scope as () => Record<string, unknown>)();

options.lexicalScope = (variable: string) => variable in scope;
options.lexicalScope = (variable: string) => {
return variable in scope;
};

delete options.scope;
}
Expand Down
72 changes: 72 additions & 0 deletions packages/@ember/template-compiler/lib/plugins/allowed-globals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* @private
*
* RFC: https://github.com/emberjs/rfcs/pull/1070
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of this copied from babel-plugin-ember-template-compilation

perhaps in a future release babel-plugin-ember-template-compilation could import this file (or remove the list entirely if we do this right)

*
* Criteria for inclusion in this list:
*
* Any of:
* - begins with an uppercase letter
* - guaranteed to never be added to glimmer as a keyword (e.g.: globalThis)
*
* And:
* - must not need new to invoke
* - must not require lifetime management (e.g.: setTimeout)
* - must not be a single-word lower-case API, because of potential collision with future new HTML elements
* - if the API is a function, the return value should not be a promise
* - must be one one of these lists:
* - https://tc39.es/ecma262/#sec-global-object
* - https://tc39.es/ecma262/#sec-function-properties-of-the-global-object
* - https://html.spec.whatwg.org/multipage/nav-history-apis.html#window
* - https://html.spec.whatwg.org/multipage/indices.html#all-interfaces
* - https://html.spec.whatwg.org/multipage/webappapis.html
*/
export const ALLOWED_GLOBALS = new Set([
// ////////////////
// namespaces
// ////////////////
// TC39
'globalThis',
'Atomics',
'JSON',
'Math',
'Reflect',
// WHATWG
'localStorage',
'sessionStorage',
'URL',
// ////////////////
// functions / utilities
// ////////////////
// TC39
'isNaN',
'isFinite',
'parseInt',
'parseFloat',
'decodeURI',
'decodeURIComponent',
'encodeURI',
'encodeURIComponent',
// WHATWG
'postMessage',
'structuredClone',
// ////////////////
// new-less Constructors (still functions)
// ////////////////
// TC39
'Array', // different behavior from (array)
'BigInt',
'Boolean',
'Date',
'Number',
'Object', // different behavior from (hash)
'String',
// ////////////////
// Values
// ////////////////
// TC39
'Infinity',
'NaN',
// WHATWG
'isSecureContext',
]);