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
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
@@ -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) {
@@ -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 () {
@@ -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 = [
[
@@ -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 = [
[
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@
]
},
"dependencies": {
"@glimmer/syntax": ">= 0.84.3",
"@glimmer/syntax": "^0.93.1",
"babel-import-util": "^3.0.0"
},
"devDependencies": {
@@ -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",
@@ -90,5 +90,10 @@
"release": true,
"tokenRef": "GITHUB_AUTH"
}
},
"pnpm": {
"patchedDependencies": {
"ember-source@6.1.0": "patches/ember-source@6.1.0.patch"
}
}
}
24 changes: 24 additions & 0 deletions patches/ember-source@6.1.0.patch
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
Oops, something went wrong.
Loading
Oops, something went wrong.