Skip to content
Open
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
100 changes: 100 additions & 0 deletions packages/@ember/helper/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import {
concat as glimmerConcat,
get as glimmerGet,
fn as glimmerFn,
gt as glimmerGt,
gte as glimmerGte,
lt as glimmerLt,
lte as glimmerLte,
} from '@glimmer/runtime';
import { element as glimmerElement, uniqueId as glimmerUniqueId } from '@ember/-internals/glimmer';
import { type Opaque } from '@ember/-internals/utility-types';
Expand Down Expand Up @@ -470,6 +474,102 @@ export interface GetHelper extends Opaque<'helper:get'> {}
export const fn = glimmerFn as FnHelper;
export interface FnHelper extends Opaque<'helper:fn'> {}

/**
* The `{{gt}}` helper returns `true` if the first argument is greater than
* the second argument.
*
* ```js
* import { gt } from '@ember/helper';
*
* <template>
* {{if (gt @score 100) "High score!" "Keep trying"}}
* </template>
* ```
*
* In strict-mode (gjs/gts) templates, `gt` is available as a keyword and
* does not need to be imported.
*
* @method gt
* @param {number} left
* @param {number} right
* @return {boolean}
* @public
*/
export const gt = glimmerGt as unknown as GtHelper;
export interface GtHelper extends Opaque<'helper:gt'> {}

/**
* The `{{gte}}` helper returns `true` if the first argument is greater than
* or equal to the second argument.
*
* ```js
* import { gte } from '@ember/helper';
*
* <template>
* {{if (gte @age 18) "Adult" "Minor"}}
* </template>
* ```
*
* In strict-mode (gjs/gts) templates, `gte` is available as a keyword and
* does not need to be imported.
*
* @method gte
* @param {number} left
* @param {number} right
* @return {boolean}
* @public
*/
export const gte = glimmerGte as unknown as GteHelper;
export interface GteHelper extends Opaque<'helper:gte'> {}

/**
* The `{{lt}}` helper returns `true` if the first argument is less than
* the second argument.
*
* ```js
* import { lt } from '@ember/helper';
*
* <template>
* {{if (lt @temperature 0) "Freezing" "Above zero"}}
* </template>
* ```
*
* In strict-mode (gjs/gts) templates, `lt` is available as a keyword and
* does not need to be imported.
*
* @method lt
* @param {number} left
* @param {number} right
* @return {boolean}
* @public
*/
export const lt = glimmerLt as unknown as LtHelper;
export interface LtHelper extends Opaque<'helper:lt'> {}

/**
* The `{{lte}}` helper returns `true` if the first argument is less than
* or equal to the second argument.
*
* ```js
* import { lte } from '@ember/helper';
*
* <template>
* {{if (lte @count 0) "Empty" "Has items"}}
* </template>
* ```
*
* In strict-mode (gjs/gts) templates, `lte` is available as a keyword and
* does not need to be imported.
*
* @method lte
* @param {number} left
* @param {number} right
* @return {boolean}
* @public
*/
export const lte = glimmerLte as unknown as LteHelper;
export interface LteHelper extends Opaque<'helper:lte'> {}

/**
* The `element` helper lets you dynamically set the tag name of an element.
*
Expand Down
6 changes: 5 additions & 1 deletion packages/@ember/template-compiler/lib/compile-options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fn } from '@ember/helper';
import { fn, gt, gte, lt, lte } from '@ember/helper';
import { on } from '@ember/modifier';
import { assert } from '@ember/debug';
import {
Expand Down Expand Up @@ -26,6 +26,10 @@ export const RUNTIME_KEYWORDS_NAME = '__ember_keywords__';

export const keywords: Record<string, unknown> = {
fn,
gt,
gte,
lt,
lte,
on,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,35 @@ export default function autoImportBuiltins(env: EmberASTPluginEnvironment): ASTP
if (isFn(node, hasLocal)) {
rewriteKeyword(env, node, 'fn', '@ember/helper');
}
if (isGt(node, hasLocal)) {
rewriteKeyword(env, node, 'gt', '@ember/helper');
}
if (isGte(node, hasLocal)) {
rewriteKeyword(env, node, 'gte', '@ember/helper');
}
if (isLt(node, hasLocal)) {
rewriteKeyword(env, node, 'lt', '@ember/helper');
}
if (isLte(node, hasLocal)) {
rewriteKeyword(env, node, 'lte', '@ember/helper');
}
},
MustacheStatement(node: AST.MustacheStatement) {
if (isFn(node, hasLocal)) {
rewriteKeyword(env, node, 'fn', '@ember/helper');
}
if (isGt(node, hasLocal)) {
rewriteKeyword(env, node, 'gt', '@ember/helper');
}
if (isGte(node, hasLocal)) {
rewriteKeyword(env, node, 'gte', '@ember/helper');
}
if (isLt(node, hasLocal)) {
rewriteKeyword(env, node, 'lt', '@ember/helper');
}
if (isLte(node, hasLocal)) {
rewriteKeyword(env, node, 'lte', '@ember/helper');
}
},
},
};
Expand Down Expand Up @@ -68,3 +92,31 @@ function isFn(
): node is (AST.MustacheStatement | AST.SubExpression) & { path: AST.PathExpression } {
return isPath(node.path) && node.path.original === 'fn' && !hasLocal('fn');
}

function isGt(
node: AST.MustacheStatement | AST.SubExpression,
hasLocal: (k: string) => boolean
): node is (AST.MustacheStatement | AST.SubExpression) & { path: AST.PathExpression } {
return isPath(node.path) && node.path.original === 'gt' && !hasLocal('gt');
}

function isGte(
node: AST.MustacheStatement | AST.SubExpression,
hasLocal: (k: string) => boolean
): node is (AST.MustacheStatement | AST.SubExpression) & { path: AST.PathExpression } {
return isPath(node.path) && node.path.original === 'gte' && !hasLocal('gte');
}

function isLt(
node: AST.MustacheStatement | AST.SubExpression,
hasLocal: (k: string) => boolean
): node is (AST.MustacheStatement | AST.SubExpression) & { path: AST.PathExpression } {
return isPath(node.path) && node.path.original === 'lt' && !hasLocal('lt');
}

function isLte(
node: AST.MustacheStatement | AST.SubExpression,
hasLocal: (k: string) => boolean
): node is (AST.MustacheStatement | AST.SubExpression) & { path: AST.PathExpression } {
return isPath(node.path) && node.path.original === 'lte' && !hasLocal('lte');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
GlimmerishComponent,
jitSuite,
RenderTest,
test,
} from '@glimmer-workspace/integration-tests';

import { template } from '@ember/template-compiler/runtime';

class KeywordGtRuntime extends RenderTest {
static suiteName = 'keyword helper: gt (runtime)';

@test
'explicit scope'() {
const compiled = template('{{if (gt a b) "yes" "no"}}', {
strictMode: true,
scope: () => ({ a: 3, b: 2 }),
});
this.renderComponent(compiled);
this.assertHTML('yes');
}

@test
'implicit scope (eval)'() {
let a = 3;
let b = 2;
hide(a);
hide(b);
const compiled = template('{{if (gt a b) "yes" "no"}}', {
strictMode: true,
eval() {
return eval(arguments[0]);
},
});
this.renderComponent(compiled);
this.assertHTML('yes');
}

@test
'no eval and no scope'() {
class Foo extends GlimmerishComponent {
a = 3;
b = 2;
static {
template('{{if (gt this.a this.b) "yes" "no"}}', {
strictMode: true,
component: this,
});
}
}
this.renderComponent(Foo);
this.assertHTML('yes');
}
}

jitSuite(KeywordGtRuntime);

const hide = (variable: unknown) => {
new Function(`return (${JSON.stringify(variable)});`);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { DEBUG } from '@glimmer/env';
import { jitSuite, RenderTest, test } from '@glimmer-workspace/integration-tests';

import { template } from '@ember/template-compiler';
import { gt } from '@ember/helper';

class KeywordGt extends RenderTest {
static suiteName = 'keyword helper: gt';

@test
'returns true when first arg is greater'() {
let a = 3;
let b = 2;
const compiled = template('{{if (gt a b) "yes" "no"}}', {
strictMode: true,
scope: () => ({ gt, a, b }),
});
this.renderComponent(compiled);
this.assertHTML('yes');
}

@test
'returns false when first arg is equal'() {
let a = 2;
let b = 2;
const compiled = template('{{if (gt a b) "yes" "no"}}', {
strictMode: true,
scope: () => ({ gt, a, b }),
});
this.renderComponent(compiled);
this.assertHTML('no');
}

@test
'returns false when first arg is less'() {
let a = 1;
let b = 2;
const compiled = template('{{if (gt a b) "yes" "no"}}', {
strictMode: true,
scope: () => ({ gt, a, b }),
});
this.renderComponent(compiled);
this.assertHTML('no');
}

@test({ skip: !DEBUG })
'throws if not called with exactly two arguments'(assert: Assert) {
let a = 1;
const compiled = template('{{gt a}}', {
strictMode: true,
scope: () => ({ gt, a }),
});

assert.throws(() => {
this.renderComponent(compiled);
}, /`gt` expects exactly two arguments/);
}
}

jitSuite(KeywordGt);
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
GlimmerishComponent,
jitSuite,
RenderTest,
test,
} from '@glimmer-workspace/integration-tests';

import { template } from '@ember/template-compiler/runtime';

class KeywordGteRuntime extends RenderTest {
static suiteName = 'keyword helper: gte (runtime)';

@test
'explicit scope'() {
const compiled = template('{{if (gte a b) "yes" "no"}}', {
strictMode: true,
scope: () => ({ a: 2, b: 2 }),
});
this.renderComponent(compiled);
this.assertHTML('yes');
}

@test
'no eval and no scope'() {
class Foo extends GlimmerishComponent {
a = 2;
b = 2;
static {
template('{{if (gte this.a this.b) "yes" "no"}}', {
strictMode: true,
component: this,
});
}
}
this.renderComponent(Foo);
this.assertHTML('yes');
}
}

jitSuite(KeywordGteRuntime);
Loading
Loading