Skip to content

Commit

Permalink
Merge pull request #510 from bwbuchanan/logical-operators
Browse files Browse the repository at this point in the history
Resolves #221
  • Loading branch information
dfreeman committed Jan 4, 2023
2 parents 3339718 + 742c2d2 commit 2cb0894
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 1 deletion.
145 changes: 145 additions & 0 deletions packages/core/__tests__/transform/template-to-typescript.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,67 @@ describe('Transform: rewriteTemplate', () => {
`);
});
});

describe('{{and}}', () => {
test('with two arguments', () => {
let template = stripIndent`
{{log (testAnd 1 2)}}
`;

expect(templateBody(template, { globals: ['testAnd'], specialForms: { testAnd: '&&' } }))
.toMatchInlineSnapshot(`
"χ.emitContent(χ.resolve(log)((1 && 2)));"
`);
});

test('with three arguments', () => {
let template = stripIndent`
{{log (testAnd 1 2 3)}}
`;

expect(templateBody(template, { globals: ['testAnd'], specialForms: { testAnd: '&&' } }))
.toMatchInlineSnapshot(`
"χ.emitContent(χ.resolve(log)((1 && 2 && 3)));"
`);
});
});

describe('{{or}}', () => {
test('with two arguments', () => {
let template = stripIndent`
{{log (testOr 1 2)}}
`;

expect(templateBody(template, { globals: ['testOr'], specialForms: { testOr: '||' } }))
.toMatchInlineSnapshot(`
"χ.emitContent(χ.resolve(log)((1 || 2)));"
`);
});

test('with three arguments', () => {
let template = stripIndent`
{{log (testOr 1 2 3)}}
`;

expect(templateBody(template, { globals: ['testOr'], specialForms: { testOr: '||' } }))
.toMatchInlineSnapshot(`
"χ.emitContent(χ.resolve(log)((1 || 2 || 3)));"
`);
});
});

describe('{{not}}', () => {
test('with one argument', () => {
let template = stripIndent`
{{log (testNot 1)}}
`;

expect(templateBody(template, { globals: ['testNot'], specialForms: { testNot: '!' } }))
.toMatchInlineSnapshot(`
"χ.emitContent(χ.resolve(log)(!1));"
`);
});
});
});

describe('inline curlies', () => {
Expand Down Expand Up @@ -1365,6 +1426,90 @@ describe('Transform: rewriteTemplate', () => {
]);
});

test('{{and}} with named parameters', () => {
let { errors } = templateToTypescript('<Foo @attr={{testAnd 123 456 foo="bar"}} />', {
typesModule: '@glint/template',
specialForms: { testAnd: '&&' },
});

expect(errors).toEqual([
{
message: '{{testAnd}} only accepts positional parameters',
location: { start: 11, end: 40 },
},
]);
});

test('{{and}} with wrong number of parameters', () => {
let { errors } = templateToTypescript('<Foo @attr={{testAnd 123}} />', {
typesModule: '@glint/template',
specialForms: { testAnd: '&&' },
});

expect(errors).toEqual([
{
message: '{{testAnd}} requires at least two parameters',
location: { start: 11, end: 26 },
},
]);
});

test('{{or}} with named parameters', () => {
let { errors } = templateToTypescript('<Foo @attr={{testOr 123 456 foo="bar"}} />', {
typesModule: '@glint/template',
specialForms: { testOr: '||' },
});

expect(errors).toEqual([
{
message: '{{testOr}} only accepts positional parameters',
location: { start: 11, end: 39 },
},
]);
});

test('{{or}} with wrong number of parameters', () => {
let { errors } = templateToTypescript('<Foo @attr={{testOr 123}} />', {
typesModule: '@glint/template',
specialForms: { testOr: '||' },
});

expect(errors).toEqual([
{
message: '{{testOr}} requires at least two parameters',
location: { start: 11, end: 25 },
},
]);
});

test('{{not}} with named parameters', () => {
let { errors } = templateToTypescript('<Foo @attr={{testNot 123 foo="bar"}} />', {
typesModule: '@glint/template',
specialForms: { testNot: '!' },
});

expect(errors).toEqual([
{
message: '{{testNot}} only accepts positional parameters',
location: { start: 11, end: 36 },
},
]);
});

test('{{not}} with wrong number of parameters', () => {
let { errors } = templateToTypescript('<Foo @attr={{testNot 123 456}} />', {
typesModule: '@glint/template',
specialForms: { testNot: '!' },
});

expect(errors).toEqual([
{
message: '{{testNot}} requires exactly one parameter',
location: { start: 11, end: 30 },
},
]);
});

test('inline {{if}} with no consequent', () => {
let { errors } = templateToTypescript('<Foo @attr={{testIf true}} />', {
typesModule: '@glint/template',
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/config/types.cts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ export type GlintSpecialForm =
| 'object-literal'
| 'array-literal'
| '==='
| '!==';
| '!=='
| '&&'
| '||'
| '!';
export type GlintSpecialFormConfig = {
globals?: { [global: string]: GlintSpecialForm };
imports?: {
Expand Down
56 changes: 56 additions & 0 deletions packages/core/src/transform/template/template-to-typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,15 @@ export function templateToTypescript(
emitBinaryOperatorExpression(formInfo, node);
break;

case '&&':
case '||':
emitLogicalExpression(formInfo, node);
break;

case '!':
emitUnaryOperatorExpression(formInfo, node);
break;

default:
record.error(`${formInfo.name} is not valid in inline form`, rangeForNode(node));
emit.text('undefined');
Expand Down Expand Up @@ -346,6 +355,53 @@ export function templateToTypescript(
});
}

function emitLogicalExpression(
formInfo: SpecialFormInfo,
node: AST.MustacheStatement | AST.SubExpression
): void {
emit.forNode(node, () => {
assert(
node.hash.pairs.length === 0,
() => `{{${formInfo.name}}} only accepts positional parameters`
);
assert(
node.params.length >= 2,
() => `{{${formInfo.name}}} requires at least two parameters`
);

emit.text('(');
for (const [index, param] of node.params.entries()) {
emitExpression(param);

if (index < node.params.length - 1) {
emit.text(` ${formInfo.form} `);
}
}
emit.text(')');
});
}

function emitUnaryOperatorExpression(
formInfo: SpecialFormInfo,
node: AST.MustacheStatement | AST.SubExpression
): void {
emit.forNode(node, () => {
assert(
node.hash.pairs.length === 0,
() => `{{${formInfo.name}}} only accepts positional parameters`
);
assert(
node.params.length === 1,
() => `{{${formInfo.name}}} requires exactly one parameter`
);

const [param] = node.params;

emit.text(formInfo.form);
emitExpression(param);
});
}

type SpecialFormInfo = {
form: GlintSpecialForm;
name: string;
Expand Down

0 comments on commit 2cb0894

Please sign in to comment.