diff --git a/packages/ember-glimmer/lib/environment.js b/packages/ember-glimmer/lib/environment.js index ebe1df401a7..19c4c7da3d2 100644 --- a/packages/ember-glimmer/lib/environment.js +++ b/packages/ember-glimmer/lib/environment.js @@ -121,7 +121,43 @@ export default class Environment extends GlimmerEnvironment { }; } + // Hello future traveler, welcome to the world of syntax refinement. + // The method below is called by Glimmer's runtime compiler to allow + // us to take generic statement syntax and refine it to more meaniful + // syntax for Ember's use case. This on the fly switch-a-roo sounds fine + // and dandy, however Ember has precedence on statement refinement that you + // need to be aware of. The presendence for language constructs is as follows: + // + // ------------------------ + // Native & Built-in Syntax + // ------------------------ + // User-land components + // ------------------------ + // User-land helpers + // ------------------------ + // + // The one caveat here is that Ember also allows for dashed references that are + // not a component or helper: + // + // export default Component.extend({ + // 'foo-bar': 'LAME' + // }); + // + // {{foo-bar}} + // + // The heuristic for the above situation is a dashed "key" in inline form + // that does not resolve to a defintion. In this case refine statement simply + // isn't going to return any syntax and the Glimmer engine knows how to handle + // this case. + refineStatement(statement) { + // 1. resolve any native syntax – if, unless, with, each, and partial + let nativeSyntax = super.refineStatement(statement); + + if (nativeSyntax) { + return nativeSyntax; + } + let { isSimple, isInline, @@ -133,37 +169,37 @@ export default class Environment extends GlimmerEnvironment { templates } = statement; - if (key !== 'partial' && isSimple && (isInline || isBlock)) { + assert(`You attempted to overwrite the built-in helper "${key}" which is not allowed. Please rename the helper.`, !(builtInHelpers[key] && this.owner.hasRegistration(`helper:${key}`))); + + if (isSimple && (isInline || isBlock)) { + // 2. built-in syntax if (key === 'component') { return new DynamicComponentSyntax({ args, templates }); } else if (key === 'outlet') { return new OutletSyntax({ args }); + } + + let internalKey = builtInComponents[key]; + let definition = null; + + if (internalKey) { + definition = this.getComponentDefinition([internalKey]); } else if (key.indexOf('-') >= 0) { - let definition = this.getComponentDefinition(path); - - if (definition) { - wrapClassBindingAttribute(args); - wrapClassAttribute(args); - return new CurlyComponentSyntax({ args, definition, templates }); - } else if (isBlock && !this.hasHelper(key)) { - assert(`A helper named '${path[0]}' could not be found`, false); - } - } else { - // Check if it's a keyword - let mappedKey = builtInComponents[key]; - if (mappedKey) { - let definition = this.getComponentDefinition([mappedKey]); - wrapClassBindingAttribute(args); - wrapClassAttribute(args); - return new CurlyComponentSyntax({ args, definition, templates }); - } + definition = this.getComponentDefinition(path); + } + + if (definition) { + wrapClassBindingAttribute(args); + wrapClassAttribute(args); + return new CurlyComponentSyntax({ args, definition, templates }); } + + assert(`Could not find component named "${key}" (no component or template with that name was found)`, !isBlock || this.hasHelper(key)); } - let nativeSyntax = super.refineStatement(statement); - assert(`Helpers may not be used in the block form, for example {{#${key}}}{{/${key}}}. Please use a component, or alternatively use the helper in combination with a built-in Ember helper, for example {{#if (${key})}}{{/if}}.`, !nativeSyntax && key && this.hasHelper(key) ? !isBlock : true); + assert(`Helpers may not be used in the block form, for example {{#${key}}}{{/${key}}}. Please use a component, or alternatively use the helper in combination with a built-in Ember helper, for example {{#if (${key})}}{{/if}}.`, !isBlock || !this.hasHelper(key)); + assert(`Helpers may not be used in the element form.`, !nativeSyntax && key && this.hasHelper(key) ? !isModifier : true); - return nativeSyntax; } hasComponentDefinition() { @@ -179,8 +215,6 @@ export default class Environment extends GlimmerEnvironment { if (ComponentClass || layout) { definition = this._components[name] = new CurlyComponentDefinition(name, ComponentClass, layout); - } else if (!this.hasHelper(name)) { - assert(`Glimmer error: Could not find component named "${name}" (no component or template with that name was found)`, !!(ComponentClass || layout)); } } diff --git a/packages/ember-glimmer/lib/syntax/dynamic-component.js b/packages/ember-glimmer/lib/syntax/dynamic-component.js index ba4527f7951..a3167772069 100644 --- a/packages/ember-glimmer/lib/syntax/dynamic-component.js +++ b/packages/ember-glimmer/lib/syntax/dynamic-component.js @@ -1,5 +1,6 @@ import { ArgsSyntax, StatementSyntax } from 'glimmer-runtime'; import { ConstReference, isConst, UNDEFINED_REFERENCE } from 'glimmer-reference'; +import { assert } from 'ember-metal/debug'; function dynamicComponentFor(vm) { let env = vm.env; @@ -10,6 +11,8 @@ function dynamicComponentFor(vm) { let name = nameRef.value(); let definition = env.getComponentDefinition([name]); + assert(`Could not find component named "${name}" (no component or template with that name was found)`, definition); + return new ConstReference(definition); } else { return new DynamicComponentReference({ nameRef, env }); @@ -44,6 +47,9 @@ class DynamicComponentReference { if (typeof name === 'string') { let definition = env.getComponentDefinition([name]); + + assert(`Could not find component named "${name}" (no component or template with that name was found)`, definition); + return definition; } else { return null; diff --git a/packages/ember-glimmer/tests/integration/components/curly-components-test.js b/packages/ember-glimmer/tests/integration/components/curly-components-test.js index 7719eea48d1..99d6f0d2b0c 100644 --- a/packages/ember-glimmer/tests/integration/components/curly-components-test.js +++ b/packages/ember-glimmer/tests/integration/components/curly-components-test.js @@ -905,7 +905,7 @@ moduleFor('Components test: curly components', class extends RenderingTest { this.assertComponentElement(this.firstChild, { content: 'true' }); } - ['@htmlbars lookup of component takes priority over property']() { + ['@test lookup of component takes priority over property']() { this.registerComponent('some-component', { template: 'some-component' }); diff --git a/packages/ember-glimmer/tests/integration/helpers/custom-helper-test.js b/packages/ember-glimmer/tests/integration/helpers/custom-helper-test.js index 5f91f45d07d..d0fdf2491f5 100644 --- a/packages/ember-glimmer/tests/integration/helpers/custom-helper-test.js +++ b/packages/ember-glimmer/tests/integration/helpers/custom-helper-test.js @@ -6,6 +6,13 @@ let assert = QUnit.assert; moduleFor('Helpers test: custom helpers', class extends RenderingTest { + ['@glimmer it cannot override built-in syntax']() { + this.registerHelper('if', () => 'Nope'); + expectAssertion(() => { + this.render(`{{if foo 'LOL'}}`, { foo: true }); + }, /You attempted to overwrite the built-in helper \"if\" which is not allowed. Please rename the helper./); + } + ['@test it can resolve custom simple helpers with or without dashes']() { this.registerHelper('hello', () => 'hello'); this.registerHelper('hello-world', () => 'hello world'); diff --git a/packages/ember/tests/component_registration_test.js b/packages/ember/tests/component_registration_test.js index f8357ad7710..99ba41a7eca 100644 --- a/packages/ember/tests/component_registration_test.js +++ b/packages/ember/tests/component_registration_test.js @@ -144,18 +144,6 @@ QUnit.test('Late-registered components can be rendered with ONLY the template re ok(!helpers['borf-snorlax'], 'Component wasn\'t saved to global helpers hash'); }); -test('Component-like invocations are treated as bound paths if neither template nor component are registered on the container', function() { - setTemplate('application', compile('