Skip to content

Commit

Permalink
Add support for the modifier keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
Windvis committed Mar 6, 2022
1 parent 2a44b73 commit 259c44b
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 1 deletion.
10 changes: 10 additions & 0 deletions packages/compat/src/resolver-transform.ts
Expand Up @@ -78,6 +78,10 @@ export function makeResolverTransform(resolver: Resolver) {
handleDynamicHelper(node.params[0], resolver, filename);
return;
}
if (node.path.original === 'modifier' && node.params.length > 0) {
handleDynamicModifier(node.params[0], resolver, filename);
return;
}
resolver.resolveSubExpression(node.path.original, filename, node.path.loc);
},
MustacheStatement(node: ASTv1.MustacheStatement) {
Expand Down Expand Up @@ -341,3 +345,9 @@ function handleDynamicHelper(param: ASTv1.Node, resolver: Resolver, moduleName:
resolver.resolveDynamicHelper({ type: 'other' }, moduleName, param.loc);
}
}

function handleDynamicModifier(param: ASTv1.Expression, resolver: Resolver, moduleName: string): void {
if (param.type === 'StringLiteral') {
resolver.resolveDynamicModifier({ type: 'literal', path: param.value }, moduleName, param.loc);
}
}
37 changes: 37 additions & 0 deletions packages/compat/src/resolver.ts
Expand Up @@ -859,6 +859,43 @@ export default class CompatResolver implements Resolver {
);
}
}

resolveDynamicModifier(modifier: ComponentLocator, from: string, loc: Loc): Resolution | null {
if (!this.staticModifiersEnabled) {
return null;
}

if (modifier.type === 'literal') {
let modifierName = modifier.path;
if (builtInModifiers.includes(modifierName)) {
return null;
}

let found = this.tryModifier(modifierName, from);
if (found) {
return this.add(found, from);
}
return this.add(
{
type: 'error',
message: `Missing modifier`,
detail: modifierName,
loc,
},
from
);
} else {
return this.add(
{
type: 'error',
message: 'Unsafe dynamic modifier',
detail: `cannot statically analyze this expression`,
loc,
},
from
);
}
}
}

function humanReadableFile(root: string, file: string) {
Expand Down
97 changes: 96 additions & 1 deletion packages/compat/tests/resolver.test.ts
Expand Up @@ -531,6 +531,44 @@ describe('compat-resolver', function () {
},
]);
});
test('string literal passed to "modifier" keyword in content position', function () {
let findDependencies = configure({
staticModifiers: true,
});
givenFile('modifiers/add-listener.js');
expect(
findDependencies(
'templates/application.hbs',
`<button {{(modifier "add-listener" "click" this.handleClick)}}>Test</button>`
)
).toEqual([
{
path: '../modifiers/add-listener.js',
runtimeName: 'the-app/modifiers/add-listener',
},
]);
});
test('modifier currying using the "modifier" keyword', function () {
let findDependencies = configure({ staticModifiers: true });
givenFile('modifiers/add-listener.js');
expect(
findDependencies(
'templates/application.hbs',
`
{{#let (modifier "add-listener") as |addListener|}}
{{#let (modifier addListener "click") as |addClickListener|}}
<button {{addClickListener this.handleClick}}>Test</button>
{{/let}}
{{/let}}
`
)
).toEqual([
{
path: '../modifiers/add-listener.js',
runtimeName: 'the-app/modifiers/add-listener',
},
]);
});
test('built-in components are ignored when used with the component helper', function () {
let findDependencies = configure({
staticComponents: true,
Expand Down Expand Up @@ -561,6 +599,20 @@ describe('compat-resolver', function () {
)
).toEqual([]);
});
test('built-in modifiers are ignored when used with the "modifier" keyword', function () {
let findDependencies = configure({
staticModifiers: true,
});
expect(
findDependencies(
'templates/application.hbs',
`
<button {{(modifier "on" "click" this.handleClick)}}>Test</button>
<button {{(modifier "action" "handleClick")}}>Test</button>
`
)
).toEqual([]);
});
test('component helper with direct addon package reference', function () {
let findDependencies = configure({
staticComponents: true,
Expand Down Expand Up @@ -681,6 +733,25 @@ describe('compat-resolver', function () {
},
]);
});
test('string literal passed to "modifier" keyword in helper position', function () {
let findDependencies = configure({ staticModifiers: true });
givenFile('modifiers/add-listener.js');
expect(
findDependencies(
'templates/application.hbs',
`
{{#let (modifier "add-listener" "click") as |addClickListener|}}
<button {{addClickListener this.handleClick}}>Test</button>
{{/let}}
`
)
).toEqual([
{
path: '../modifiers/add-listener.js',
runtimeName: 'the-app/modifiers/add-listener',
},
]);
});
test('string literal passed to component helper fails to resolve', function () {
let findDependencies = configure({ staticComponents: true });
givenFile('components/my-thing.js');
Expand All @@ -694,6 +765,15 @@ describe('compat-resolver', function () {
findDependencies('templates/application.hbs', `{{helper "hello-world"}}`);
}).toThrow(new RegExp(`Missing helper: hello-world in templates/application.hbs`));
});
test('string literal passed to "modifier" keyword fails to resolve', function () {
let findDependencies = configure({ staticModifiers: true });
expect(() => {
findDependencies(
'templates/application.hbs',
`<button {{(modifier "add-listener" "click" this.handleClick)}}>Test</button>`
);
}).toThrow(new RegExp(`Missing modifier: add-listener in templates/application.hbs`));
});
test('string literal passed to component helper fails to resolve when staticComponents is off', function () {
let findDependencies = configure({ staticComponents: false });
givenFile('components/my-thing.js');
Expand All @@ -703,6 +783,16 @@ describe('compat-resolver', function () {
let findDependencies = configure({ staticHelpers: false });
expect(findDependencies('templates/application.hbs', `{{helper "hello-world"}}`)).toEqual([]);
});
test('string literal passed to "modifier" keyword fails to resolve when staticModifiers is off', function () {
let findDependencies = configure({ staticModifiers: false });
givenFile('modifiers/add-listener.js');
expect(
findDependencies(
'templates/application.hbs',
`<button {{(modifier "add-listener" "click" this.handleClick)}}>Test</button>`
)
).toEqual([]);
});
test('dynamic component helper error in content position', function () {
let findDependencies = configure({ staticComponents: true });
givenFile('components/hello-world.js');
Expand Down Expand Up @@ -925,7 +1015,7 @@ describe('compat-resolver', function () {
]);
});
test('ignores builtins', function () {
let findDependencies = configure({ staticHelpers: true, staticComponents: true });
let findDependencies = configure({ staticHelpers: true, staticComponents: true, staticModifiers: true });
expect(
findDependencies(
'templates/application.hbs',
Expand Down Expand Up @@ -1778,6 +1868,11 @@ describe('compat-resolver', function () {
);
});

test('ignores any non-string-literal in "modifier" keyword', function () {
let findDependencies = configure({ staticModifiers: true });
expect(findDependencies('templates/application.hbs', `<div {{(modifier this.which)}}></div>`)).toEqual([]);
});

test('trusts inline ensure-safe-component helper', function () {
let findDependencies = configure({ staticComponents: true });
expect(findDependencies('templates/application.hbs', `{{component (ensure-safe-component this.which) }}`)).toEqual(
Expand Down

0 comments on commit 259c44b

Please sign in to comment.