Skip to content

Commit 2047aec

Browse files
Merge pull request #859 from typed-ember/nvp/test-for-satisfies
Fix handling of `satisfies` on components.
2 parents 8822ae8 + 1cd7e08 commit 2047aec

File tree

3 files changed

+146
-32
lines changed

3 files changed

+146
-32
lines changed

packages/environment-ember-template-imports/-private/environment/transform.ts

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ export const transform: GlintExtensionTransform<PreprocessData> = (
2525
} else if (isETIDefaultTemplate(ts, node)) {
2626
// Annotate that this template is a default export
2727
setEmitMetadata(node.expression, { prepend: 'export default ' });
28-
28+
return node;
29+
} else if (isETIDefaultSatisfiesTemplate(ts, node)) {
30+
// Annotate that this template is a default export
31+
setEmitMetadata(node.expression.expression, { prepend: 'export default ' });
2932
return node;
3033
} else if (isETITemplateExpression(ts, node)) {
3134
// Convert '[__T`foo`]' as an expression to just '__T`foo`'
@@ -111,18 +114,43 @@ type ETITemplateProperty = ts.PropertyDeclaration & {
111114
name: ts.ComputedPropertyName & { expression: ETITemplateLiteral };
112115
};
113116

114-
type ETIDefaultTemplate =
115-
| (ts.ExpressionStatement & {
116-
expression: ETITemplateLiteral;
117-
})
118-
| (ts.SatisfiesExpression & {
119-
expression: ETITemplateLiteral;
120-
});
117+
type ETIDefaultTemplate = ts.ExpressionStatement & {
118+
expression: ETITemplateLiteral;
119+
};
120+
121+
type ETIDefaultSatisfiesTemplate = ts.ExpressionStatement & {
122+
expression: ts.SatisfiesExpression & { expression: ETITemplateLiteral };
123+
};
121124

125+
/**
126+
* Implicit default export:
127+
*
128+
* ( <template></template> )
129+
* ^ ExpressionStatement
130+
*
131+
* ( <template></template> satisfies ... )
132+
* ^ SatisfiesExpression
133+
*
134+
* But!
135+
*
136+
* ( const X = <template></template> satisfies ... )
137+
* ^ VariableStatement
138+
*
139+
* So when we check for a wrapping SatisfiesExpression, we need to also make sure
140+
* the parent node is not a variable Statement.
141+
*/
122142
function isETIDefaultTemplate(ts: TSLib, node: ts.Node): node is ETIDefaultTemplate {
143+
return ts.isExpressionStatement(node) && isETITemplateLiteral(ts, node.expression);
144+
}
145+
146+
function isETIDefaultSatisfiesTemplate(
147+
ts: TSLib,
148+
node: ts.Node,
149+
): node is ETIDefaultSatisfiesTemplate {
123150
return (
124-
(ts.isExpressionStatement(node) || ts.isSatisfiesExpression(node)) &&
125-
isETITemplateLiteral(ts, node.expression)
151+
ts.isExpressionStatement(node) &&
152+
ts.isSatisfiesExpression(node.expression) &&
153+
isETITemplateLiteral(ts, node.expression.expression)
126154
);
127155
}
128156

packages/environment-ember-template-imports/__tests__/transformation.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,43 @@ describe('Environment: ETI', () => {
234234
);
235235
});
236236

237+
test('single template, no export, with satisfies', () => {
238+
let source = stripIndent`
239+
import type { TOC } from '@ember/component/template-only';
240+
const hello = <template>HelloWorld!</template> satisfies TOC<{
241+
Blocks: { default: [] }
242+
}>;
243+
`;
244+
245+
let { meta, sourceFile } = applyTransform(source);
246+
247+
let declaration = sourceFile.statements[2] as ts.VariableStatement;
248+
let satisfiesExpression = declaration.declarationList.declarations[0]
249+
.initializer! as ts.SatisfiesExpression;
250+
let templateNode = satisfiesExpression.expression;
251+
252+
let start = source.indexOf('<template>');
253+
let contentStart = start + '<template>'.length;
254+
let contentEnd = source.indexOf('</template>');
255+
let end = contentEnd + '</template>'.length;
256+
257+
expect(meta).toEqual(
258+
new Map([
259+
[
260+
templateNode,
261+
{
262+
templateLocation: {
263+
start,
264+
contentStart,
265+
contentEnd,
266+
end,
267+
},
268+
},
269+
],
270+
]),
271+
);
272+
});
273+
237274
test('multiple templates', () => {
238275
let source = stripIndent`
239276
<template>

test-packages/package-test-core/__tests__/transform/rewrite.test.ts

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,7 @@ describe('Transform: rewriteModule', () => {
500500
}"
501501
`);
502502
});
503+
503504
test('embedded gts templates', () => {
504505
let customEnv = GlintEnvironment.load(['ember-loose', 'ember-template-imports']);
505506
let script = {
@@ -890,31 +891,79 @@ describe('Transform: rewriteModule', () => {
890891
`);
891892
});
892893

893-
test('with implicit export default and satisfies', () => {
894-
let customEnv = GlintEnvironment.load(['ember-loose', 'ember-template-imports']);
895-
let script = {
896-
filename: 'test.gts',
897-
contents: stripIndent`
894+
describe('satisfies', () => {
895+
test('with implicit export default', () => {
896+
let customEnv = GlintEnvironment.load(['ember-loose', 'ember-template-imports']);
897+
let script = {
898+
filename: 'test.gts',
899+
contents: stripIndent`
900+
import type { TOC } from '@ember/component/template-only';
901+
<template>HelloWorld!</template> satisfies TOC<{
902+
Blocks: { default: [] }
903+
}>;
904+
`,
905+
};
906+
907+
let transformedModule = rewriteModule(ts, { script }, customEnv);
908+
909+
expect(transformedModule?.errors).toEqual([]);
910+
expect(transformedModule?.transformedContents).toMatchInlineSnapshot(`
911+
"import __GLINT_GTS_EXTENSION_HACK__ from './__glint-non-existent.gts';
912+
import __GLINT_GJS_EXTENSION_HACK__ from './__glint-non-existent.gjs';
898913
import type { TOC } from '@ember/component/template-only';
899-
<template>HelloWorld!</template> satisfies TOC<{
914+
export default ({} as typeof import("@glint/environment-ember-template-imports/-private/dsl")).templateExpression(function(__glintRef__, __glintDSL__: typeof import("@glint/environment-ember-template-imports/-private/dsl")) {
915+
__glintRef__; __glintDSL__;
916+
}) satisfies TOC<{
900917
Blocks: { default: [] }
901-
}>;
902-
`,
903-
};
904-
905-
let transformedModule = rewriteModule(ts, { script }, customEnv);
918+
}>;"
919+
`);
920+
});
921+
922+
test('with two template-only components', () => {
923+
const emberTemplateImportsEnvironment = GlintEnvironment.load(['ember-template-imports']);
924+
925+
let script = {
926+
filename: 'test.gts',
927+
contents: [
928+
`import type { TOC } from '@ember/component/template-only';`,
929+
``,
930+
`const SmolComp = `,
931+
` <template>`,
932+
` Hello there, {{@name}}`,
933+
` </template> satisfies TOC<{ Args: { name: string }}>;`,
934+
``,
935+
`<template>`,
936+
` <SmolComp @name="Ember" />`,
937+
`</template> satisfies TOC<{ Args: {}, Blocks: {}, Element: null }>`,
938+
``,
939+
].join('\n'),
940+
};
941+
942+
let transformedModule = rewriteModule(ts, { script }, emberTemplateImportsEnvironment);
943+
944+
expect(transformedModule?.errors?.length).toBe(0);
945+
expect(transformedModule?.transformedContents).toMatchInlineSnapshot(`
946+
"import __GLINT_GTS_EXTENSION_HACK__ from './__glint-non-existent.gts';
947+
import __GLINT_GJS_EXTENSION_HACK__ from './__glint-non-existent.gjs';
948+
import type { TOC } from '@ember/component/template-only';
906949
907-
expect(transformedModule?.errors).toEqual([]);
908-
expect(transformedModule?.transformedContents).toMatchInlineSnapshot(`
909-
"import __GLINT_GTS_EXTENSION_HACK__ from './__glint-non-existent.gts';
910-
import __GLINT_GJS_EXTENSION_HACK__ from './__glint-non-existent.gjs';
911-
import type { TOC } from '@ember/component/template-only';
912-
export default ({} as typeof import("@glint/environment-ember-template-imports/-private/dsl")).templateExpression(function(__glintRef__, __glintDSL__: typeof import("@glint/environment-ember-template-imports/-private/dsl")) {
913-
__glintRef__; __glintDSL__;
914-
}) satisfies TOC<{
915-
Blocks: { default: [] }
916-
}>;"
917-
`);
950+
const SmolComp =
951+
({} as typeof import("@glint/environment-ember-template-imports/-private/dsl")).templateExpression(function(__glintRef__, __glintDSL__: typeof import("@glint/environment-ember-template-imports/-private/dsl")) {
952+
__glintDSL__.emitContent(__glintDSL__.resolveOrReturn(__glintRef__.args.name)());
953+
__glintRef__; __glintDSL__;
954+
}) satisfies TOC<{ Args: { name: string }}>;
955+
956+
export default ({} as typeof import("@glint/environment-ember-template-imports/-private/dsl")).templateExpression(function(__glintRef__, __glintDSL__: typeof import("@glint/environment-ember-template-imports/-private/dsl")) {
957+
{
958+
const __glintY__ = __glintDSL__.emitComponent(__glintDSL__.resolve(SmolComp)({
959+
name: "Ember", ...__glintDSL__.NamedArgsMarker }));
960+
__glintY__;
961+
}
962+
__glintRef__; __glintDSL__;
963+
}) satisfies TOC<{ Args: {}, Blocks: {}, Element: null }>
964+
"
965+
`);
966+
});
918967
});
919968
});
920969
});

0 commit comments

Comments
 (0)