Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lexical this #67

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
195 changes: 194 additions & 1 deletion __tests__/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import { Preprocessor } from 'content-tag';

describe('htmlbars-inline-precompile', function () {
// eslint-disable-next-line @typescript-eslint/no-var-requires
let compiler: EmberTemplateCompiler = { ...require('ember-source/dist/ember-template-compiler') };
let compiler: EmberTemplateCompiler = {
...require('ember-source/dist/ember-template-compiler.js'),
};
let plugins: ([typeof HTMLBarsInlinePrecompile, Options] | [unknown])[];

function transform(code: string) {
Expand Down Expand Up @@ -1871,6 +1873,34 @@ describe('htmlbars-inline-precompile', function () {
`);
expect(spy.firstCall.lastArg).toHaveProperty('locals', ['bar']);
});

it('can pass lexically scoped "this"', function () {
let spy = sinon.spy(compiler, 'precompile');
let transformed = transform(`
import { precompileTemplate } from '@ember/template-compilation';
export function example() {
return precompileTemplate('{{this.message}}', { scope: () => ({ "this": this }) });
}
`);
expect(spy.firstCall.lastArg).toHaveProperty('locals', ['this']);
expect(normalizeWireFormat(transformed)).toEqualCode(`
import { createTemplateFactory } from "@ember/template-factory";
export function example() {
return createTemplateFactory(
/*
{{this.message}}
*/
{
id: "<id>",
block: '[[[1,[32,0,["message"]]]],[],false,[]]',
moduleName: "<moduleName>",
scope: () => [this],
isStrictMode: false,
}
);
}
`);
});
});

describe('implicit-scope-form', function () {
Expand Down Expand Up @@ -1959,6 +1989,136 @@ describe('htmlbars-inline-precompile', function () {
`);
});

it('captures lexical "this" in mustache when template is used as an expression', function () {
plugins = [
[
HTMLBarsInlinePrecompile,
{
compiler,
targetFormat: 'hbs',
},
],
];

let transformed = transform(
`import { template } from '@ember/template-compiler';
function upper(s) { return s.toUpperCase() }
export function exampleTest() {
this.message = "hello";
render(template('{{upper this.message}}', { eval: function() { return eval(arguments[0]) } }))
}
`
);

expect(transformed).toEqualCode(`
import { precompileTemplate } from "@ember/template-compilation";
import { setComponentTemplate } from "@ember/component";
import templateOnly from "@ember/component/template-only";
function upper(s) {
return s.toUpperCase();
}
export function exampleTest() {
this.message = "hello";
render(
setComponentTemplate(
precompileTemplate("{{upper this.message}}", {
strictMode: true,
scope: () => ({
upper,
this: this,
}),
}),
templateOnly()
)
);
}
`);
});

it('captures lexical "this" in Element when template is used as an expression', function () {
plugins = [
[
HTMLBarsInlinePrecompile,
{
compiler,
targetFormat: 'hbs',
},
],
];

let transformed = transform(
`import { template } from '@ember/template-compiler';
import SomeComponent from './elsewhere.js';
export function exampleTest() {
this.message = SomeComponent;
render(template('<this.message />', { eval: function() { return eval(arguments[0]) } }))
}
`
);

expect(transformed).toEqualCode(`
import SomeComponent from './elsewhere.js';
import { precompileTemplate } from "@ember/template-compilation";
import { setComponentTemplate } from "@ember/component";
import templateOnly from "@ember/component/template-only";
export function exampleTest() {
this.message = SomeComponent;
render(
setComponentTemplate(
precompileTemplate("<this.message />", {
strictMode: true,
scope: () => ({
this: this,
}),
}),
templateOnly()
)
);
}
`);
});

it('does not captures lexical "this" when template is used in class body', function () {
plugins = [
[
HTMLBarsInlinePrecompile,
{
compiler,
targetFormat: 'hbs',
},
],
];

let transformed = transform(
`import { template } from '@ember/template-compiler';
import Component from '@glimmer/component';
export class Example extends Component {
upper(s) { return s.toUpperCase() }
message = "hi";
static {
template('{{this.upper this.message}}', { component: this, eval: function() { return eval(arguments[0]) } })
}
}
`
);

expect(transformed).toEqualCode(`
import Component from '@glimmer/component';
import { precompileTemplate } from "@ember/template-compilation";
import { setComponentTemplate } from "@ember/component";
export class Example extends Component {
upper(s) { return s.toUpperCase() }
message = "hi";
static {
setComponentTemplate(
precompileTemplate("{{this.upper this.message}}", {
strictMode: true,
}), this)
}
}
`);
});

it('leaves ember keywords alone when no local is defined', function () {
plugins = [
[
Expand Down Expand Up @@ -2170,6 +2330,39 @@ describe('htmlbars-inline-precompile', function () {
`);
});

it('expression form can capture lexical "this"', function () {
plugins = [
[
HTMLBarsInlinePrecompile,
{
compiler,
targetFormat: 'hbs',
},
],
];

let p = new Preprocessor();

let transformed = transform(
p.process(
`
export function example() {
return <template>{{this.message}}</template>;
}
`
)
);

expect(transformed).toEqualCode(`
import { precompileTemplate } from "@ember/template-compilation";
import { setComponentTemplate } from "@ember/component";
import templateOnly from "@ember/component/template-only";
export function example() {
return setComponentTemplate(precompileTemplate('{{this.message}}', { strictMode: true, scope: () => ({ this: this }) }), templateOnly());
}
`);
});

it('works for class member form', function () {
plugins = [
[
Expand Down
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
]
},
"dependencies": {
"@glimmer/syntax": ">= 0.84.3",
"@glimmer/syntax": "^0.93.1",
"babel-import-util": "^3.0.0"
},
"devDependencies": {
Expand All @@ -52,10 +52,10 @@
"@types/sinon": "^10.0.13",
"@typescript-eslint/eslint-plugin": "^7.14.1",
"@typescript-eslint/parser": "^7.14.1",
"code-equality-assertions": "^0.7.0",
"code-equality-assertions": "^1.0.1",
"common-tags": "^1.8.0",
"content-tag": "^0.1.0",
"ember-source": "^3.28.9",
"ember-source": "^6.1.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-node": "^11.1.0",
Expand Down Expand Up @@ -90,5 +90,10 @@
"release": true,
"tokenRef": "GITHUB_AUTH"
}
},
"pnpm": {
"patchedDependencies": {
"[email protected]": "patches/[email protected]"
}
}
}
24 changes: 24 additions & 0 deletions patches/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
diff --git a/dist/ember-template-compiler.js b/dist/ember-template-compiler.js
index 9e2c91f05543927b94a563df642e3cd34c7300b2..d30d3ca0cffa74b7391ffa70406cc5b4f7108d8a 100644
--- a/dist/ember-template-compiler.js
+++ b/dist/ember-template-compiler.js
@@ -11676,7 +11676,7 @@ var define, require;
});
}
localVar(name, symbol, isTemplateLocal, loc) {
- return debugAssert("this" !== name, "You called builders.var() with 'this'. Call builders.this instead"), debugAssert("@" !== name[0], `You called builders.var() with '${name}'. Call builders.at('${name}') instead`), new LocalVarReference({
+ return debugAssert("@" !== name[0], `You called builders.var() with '${name}'. Call builders.at('${name}') instead`), new LocalVarReference({
loc: loc,
name: name,
isTemplateLocal: isTemplateLocal,
@@ -12054,6 +12054,10 @@ var define, require;
offsets = block.loc(head.loc);
switch (head.type) {
case "ThisHead":
+ if (block.hasBinding('this')) {
+ let [symbol, isRoot] = table.get('this');
+ return block.builder.localVar('this', symbol, isRoot, offsets);
+ }
return builder.self(offsets);
case "AtHead":
{
Loading
Loading