Skip to content

Commit

Permalink
Added support for non-block components definitions with `%my-comp/ @v…
Browse files Browse the repository at this point in the history
…alue=foo`.

Also glimmer component will be non-blocked if it's not contain nested nodes.
resolved machty#338
  • Loading branch information
evgenibir committed Mar 27, 2021
1 parent 4161770 commit 660a21a
Show file tree
Hide file tree
Showing 20 changed files with 91 additions and 56 deletions.
4 changes: 4 additions & 0 deletions lib/ast-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ var builder = {
return node;
},

setVoid: function(){
this.currentNode.isVoid = true;
},

enter: function(node){
this.previousNodes.push(this.currentNode);
this.currentNode = node;
Expand Down
3 changes: 2 additions & 1 deletion lib/parser.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,13 @@ colonContent = ': ' _ c:contentStatement { return c; }
// and any nested content inside of it.
htmlElement = h:inHtmlTag nested:htmlTerminator
{
const isDoubleExit = parseInHtml(...h);
const [isHtmlComponent, isDoubleExit] = parseInHtml(...h);
if (nested && nested.length > 0) {
nested = castStringsToTextNodes(nested);
builder.add('childNodes', nested);
}
if (isDoubleExit) builder.add('childNodes', [builder.exit()])
if (isHtmlComponent && !isDoubleExit && !nested) builder.setVoid()
return [builder.exit()];
}

Expand Down
10 changes: 6 additions & 4 deletions lib/pegjs/html/in-tag.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
@import "./tag-component.pegjs" as tagComponent

{
function parseInHtml(h, inTagMustaches, fullAttributes, blockParams) {
function parseInHtml(h, inTagMustaches, fullAttributes, blockParams, isHtmlComponent) {
var tagName = h[0] || 'div',
shorthandAttributes = h[1] || [],
isVoid = h[2],
id = shorthandAttributes[0],
classes = shorthandAttributes[1] || [];
var i, l;

var elementNode = builder.generateElement(tagName);
const elementNode = builder.generateElement(tagName);
if (isVoid) elementNode.isVoid = isVoid
builder.enter(elementNode);

for (i=0, l=classes.length;i<l;i++) {
Expand Down Expand Up @@ -65,10 +67,10 @@

if (destructuredParams.length) {
createDestructuringBlock(destructuredParams)
return true;
return [isHtmlComponent, true];
}
}
return false;
return [isHtmlComponent, false];
}

function isKnownTag(tag) {
Expand Down
8 changes: 5 additions & 3 deletions lib/pegjs/html/tag-component.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ inHtmlTag
? [].concat(blockParamsStart || []).concat(blockParamsEnd || [])
: null
const inTagMustaches = startingInTagMustaches.concat(modifiers)
return [h, inTagMustaches, fullAttributes, blockParams]
return [h, inTagMustaches, fullAttributes, blockParams, true]
}
/ h:htmlStart inTagMustaches:inTagMustache* shorthands:inlineShorthands? blockParamsStart:blockParams? fullAttributes:attribute* blockParamsEnd:blockParams?
{
Expand All @@ -65,13 +65,15 @@ inHtmlTag
const blockParams = (blockParamsStart || blockParamsEnd)
? [].concat(blockParamsStart || []).concat(blockParamsEnd || [])
: null
return [h, inTagMustaches, fullAttributes, blockParams]
return [h, inTagMustaches, fullAttributes, blockParams, true]
}

htmlStart
= h:componentTag? s:shorthandAttributes? '/'?
= h:componentTag? s:shorthandAttributes? isVoid:'/'?
&{
return h || s;
} {
return [h, s, isVoid === '/']
}

inlineShorthands
Expand Down
8 changes: 5 additions & 3 deletions lib/pegjs/html/tag-html.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ tagHtml
}

const inTagMustaches = startingInTagMustaches.concat(modifiers)
return [h, inTagMustaches, fullAttributes]
return [h, inTagMustaches, fullAttributes, false]
}
/ h:htmlStart inTagMustaches:inTagMustache* shorthands:inlineShorthands? fullAttributes:attribute*
{
Expand All @@ -65,7 +65,7 @@ tagHtml
if (classes && classes.length) firstShorthandAttrs[1] = mainClasses.concat(classes)
h[1] = firstShorthandAttrs
}
return [h, inTagMustaches, fullAttributes]
return [h, inTagMustaches, fullAttributes, false]
}


Expand All @@ -77,7 +77,9 @@ tagHtml
// span.combo#of.stuff
// NOTE: this returns a 2 element array of [h,s].
// The return is used to reject a when both h an s are falsy.
htmlStart = h:knownTagName? s:shorthandAttributes? '/'? &{ return h || s; }
htmlStart = h:knownTagName? s:shorthandAttributes? '/'? &{ return h || s; } {
return [h, s]
}

inlineShorthands
= _ shorthands:shorthandAttributes { return shorthands }
Expand Down
8 changes: 6 additions & 2 deletions lib/template-compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,12 @@ var compiler = {
pushContent(this, '<'+tagName);
},

openElementEnd: function(){
pushContent(this, '>');
openElementEnd: function(isVoid = false){
if (isVoid) {
pushContent(this, '/>');
} else {
pushContent(this, '>');
}
this._insideElement = false;
},

Expand Down
4 changes: 2 additions & 2 deletions lib/template-visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ var visitor = {

visitArray(node.inTagText, opcodes);

opcodes.push(['openElementEnd']);

if (node.isVoid) {
if (node.childNodes.length) {
throw new Error('Cannot nest under void element ' + node.tagName);
}
opcodes.push(['openElementEnd', [node.isVoid]]);
} else {
opcodes.push(['openElementEnd']);
visitArray(node.childNodes, opcodes);
opcodes.push(['closeElement', [node.tagName]]);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/basic-syntax/html-attributes-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module('basic syntax: html-attributes', function (hooks) {
});

test('numbers in shorthand', function (assert) {
assert.compilesTo('%4', '<4></4>');
assert.compilesTo('%4', '<4/>');
assert.compilesTo('%4 ermagerd', '<4>ermagerd</4>');
assert.compilesTo('%4#4.4 ermagerd', '<4 id="4" class="4">ermagerd</4>');
});
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/basic-syntax/plain-text-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@ module('basic syntax: plain-text', function (hooks) {
test("self closing tag with forward slash", function (assert) {
const emblem = 'hr/';

assert.compilesTo(emblem, '<hr>');
assert.compilesTo(emblem, '<hr/>');
});

test("non-void elements are still closed correctly", function (assert) {
Expand Down
14 changes: 7 additions & 7 deletions tests/integration/basic-syntax/tags-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,25 @@ module('basic syntax: tags', function (hooks) {
test("br", function (assert) {
const emblem = "br";

assert.compilesTo(emblem, '<br>');
assert.compilesTo(emblem, '<br/>');
});

test("hr", function (assert) {
const emblem = "hr";

assert.compilesTo(emblem, '<hr>');
assert.compilesTo(emblem, '<hr/>');
});

test("br paragraph example", function (assert) {
const emblem = "p\n | LOL!\n br\n | BORF!";

assert.compilesTo(emblem, '<p>LOL!<br>BORF!</p>');
assert.compilesTo(emblem, '<p>LOL!<br/>BORF!</p>');
});

test("input", function (assert) {
const emblem = "input type=\"text\"";

assert.compilesTo(emblem, '<input type="text">');
assert.compilesTo(emblem, '<input type="text"/>');
});

test("nested content under self-closing tag should fail", function (assert) {
Expand All @@ -114,7 +114,7 @@ module('basic syntax: tags', function (hooks) {
test("basic", function (assert) {
const emblem = "%borf";

assert.compilesTo(emblem, '<borf></borf>');
assert.compilesTo(emblem, '<borf/>');
});

test("nested", function (assert) {
Expand All @@ -126,14 +126,14 @@ module('basic syntax: tags', function (hooks) {
test("capitalized", function (assert) {
const emblem = "%Alex alex\n%Alex\n %Woot";

assert.compilesTo(emblem, '<Alex>alex</Alex><Alex><Woot></Woot></Alex>');
assert.compilesTo(emblem, '<Alex>alex</Alex><Alex><Woot/></Alex>');
});

test("funky chars", function (assert) {
const emblem = "%borf:narf\n%borf:narf Hello, {{foo}}.\n%alex = foo";

assert.compilesTo(emblem,
'<borf:narf></borf:narf><borf:narf>Hello, {{foo}}.</borf:narf><alex>{{foo}}</alex>');
'<borf:narf/><borf:narf>Hello, {{foo}}.</borf:narf><alex>{{foo}}</alex>');
});
});
});
2 changes: 1 addition & 1 deletion tests/integration/ember/attribute-bindings-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ module('ember: attribute bindings', function (hooks) {
test('mustache in attribute', function (assert) {
const emblem = 'img src="{{unbound post.showLogoUrl}}" onerror="this.src=\'{{unbound orgSettings.onErrorBlankLogoImage}}\'"';

assert.compilesTo(emblem, '<img src="{{unbound post.showLogoUrl}}" onerror="this.src=\'{{unbound orgSettings.onErrorBlankLogoImage}}\'">');
assert.compilesTo(emblem, '<img src="{{unbound post.showLogoUrl}}" onerror="this.src=\'{{unbound orgSettings.onErrorBlankLogoImage}}\'"/>');
});

test('mustache in attribute with exclamation point', function (assert) {
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/ember/ember-input-helper-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module('ember: input helper test', function (hooks) {
test('example 1', function (assert) {
const emblem = "input value=name oninput={ action (mut myValue) value='target.value' }";

assert.compilesTo(emblem, "<input value={{name}} oninput={{action (mut myValue) value='target.value'}}>");
assert.compilesTo(emblem, "<input value={{name}} oninput={{action (mut myValue) value='target.value'}}/>");
});

test('example 2', function (assert) {
Expand Down
24 changes: 12 additions & 12 deletions tests/integration/glimmer/basic-syntax-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ module('glimmer: basic syntax', function (hooks) {
});

test('basic syntax for shorthands 1', function (assert) {
assert.compilesTo("%my-comp#hello .woot @value=foo data-hint=\"not-my-component%%::\"", '<my-comp id="hello" @value={{foo}} data-hint=\"not-my-component%%::\" class="woot"></my-comp>');
assert.compilesTo("%MyComp.woot #hello @value=foo", '<MyComp id="hello" @value={{foo}} class="woot"></MyComp>');
assert.compilesTo("%my-comp#id.woot #hello @value=foo", '<my-comp id="hello" @value={{foo}} class="woot"></my-comp>');
assert.compilesTo("%my-comp .woot#hello @value=foo", '<my-comp id="hello" @value={{foo}} class="woot"></my-comp>');
assert.compilesTo("%my-comp .woot.loot#hello @value=foo", '<my-comp id="hello" @value={{foo}} class="woot loot"></my-comp>');
assert.compilesTo("%my-comp#hello .woot @value=foo data-hint=\"not-my-component%%::\"", '<my-comp id="hello" @value={{foo}} data-hint=\"not-my-component%%::\" class="woot"/>');
assert.compilesTo("%MyComp.woot #hello @value=foo", '<MyComp id="hello" @value={{foo}} class="woot"/>');
assert.compilesTo("%my-comp#id.woot #hello @value=foo", '<my-comp id="hello" @value={{foo}} class="woot"/>');
assert.compilesTo("%my-comp .woot#hello @value=foo", '<my-comp id="hello" @value={{foo}} class="woot"/>');
assert.compilesTo("%my-comp .woot.loot#hello @value=foo", '<my-comp id="hello" @value={{foo}} class="woot loot"/>');
});

test('basic syntax 1', function (assert) {
Expand All @@ -28,19 +28,19 @@ module('glimmer: basic syntax', function (hooks) {
);

assert.compilesTo(emblem,
'<MyComponent @value={{foo}} data-hint=\"not-my-component%%::\"></MyComponent>');
'<MyComponent @value={{foo}} data-hint=\"not-my-component%%::\"/>');
});

test('basic syntax 2', function (assert) {
const emblem = "% my-component @value=fooValue data-hint='My special component'";

assert.compilesTo(emblem, '<my-component @value={{fooValue}} data-hint="My special component"></my-component>');
assert.compilesTo(emblem, '<my-component @value={{fooValue}} data-hint="My special component"/>');
});

test('basic syntax 3', function (assert) {
const emblem = "% modal-popup @onClose={ action 'modalClosed' }";

assert.compilesTo(emblem, "<modal-popup @onClose={{action 'modalClosed'}}></modal-popup>");
assert.compilesTo(emblem, "<modal-popup @onClose={{action 'modalClosed'}}/>");
});

test("basic syntax with legacy quoting", function (assert) {
Expand All @@ -49,7 +49,7 @@ module('glimmer: basic syntax', function (hooks) {
);

assert.compilesTo(emblem,
'<MyComponent value=\"{{foo}}\" data-hint=\"not-my-component%%::\"></MyComponent>', null, {
'<MyComponent value=\"{{foo}}\" data-hint=\"not-my-component%%::\"/>', null, {
legacyAttributeQuoting: true
});
});
Expand All @@ -60,7 +60,7 @@ module('glimmer: basic syntax', function (hooks) {
);

assert.compilesTo(emblem,
'<MyComponent @multiselect={{false}}></MyComponent>');
'<MyComponent @multiselect={{false}}/>');
});

test("...attributes", function (assert) {
Expand All @@ -69,7 +69,7 @@ module('glimmer: basic syntax', function (hooks) {
);

assert.compilesTo(emblem,
'<MyComponent ...attributes type={{@post.type}}></MyComponent>');
'<MyComponent ...attributes type={{@post.type}}/>');
});

test("Sub-expressions", function (assert) {
Expand All @@ -78,7 +78,7 @@ module('glimmer: basic syntax', function (hooks) {
);

assert.compilesTo(emblem,
'<MyComponent @value={{(or (eq foo \'bar\') (eq foo \'baz\'))}}></MyComponent>');
'<MyComponent @value={{(or (eq foo \'bar\') (eq foo \'baz\'))}}/>');
});

test("nested glimmer components with colon", function (assert) {
Expand Down
20 changes: 19 additions & 1 deletion tests/integration/glimmer/blocks-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ module('glimmer: blocks', function (hooks) {
'<MyComponent @value={{foo}}>Hi!</MyComponent>');
});

test("basic without block", function (assert) {
const emblem = w(
"%MyComponent/ @value=foo"
);

assert.compilesTo(emblem,
'<MyComponent @value={{foo}}/>');
});

test("basic without block - case 2", function (assert) {
const emblem = w(
"%MyComponent @value=foo"
);

assert.compilesTo(emblem,
'<MyComponent @value={{foo}}/>');
});

test("block params", function (assert) {
const emblem = w(
"%MyComponent @value=foo as |comp1 @comp2|",
Expand All @@ -29,7 +47,7 @@ module('glimmer: blocks', function (hooks) {
);

assert.compilesTo(emblem,
'<MyComponent @value={{foo}} as |comp|><comp.subcomp @value={{foo}}></comp.subcomp></MyComponent>');
'<MyComponent @value={{foo}} as |comp|><comp.subcomp @value={{foo}}/></MyComponent>');
});

test('recursive nesting part 2', function (assert) {
Expand Down
8 changes: 4 additions & 4 deletions tests/integration/glimmer/brackets-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module('glimmer: brackets', function (hooks) {
']'
);

assert.compilesTo(emblem, '<MyComponent @foo={{bar}} @baz=\"food\"></MyComponent>');
assert.compilesTo(emblem, '<MyComponent @foo={{bar}} @baz=\"food\"/>');
});

test('brackets with block params in the start', function (assert) {
Expand Down Expand Up @@ -49,7 +49,7 @@ module('glimmer: brackets', function (hooks) {
']'
);

assert.compilesTo(emblem, '<MyComponent @foo={{bar}} @baz=\"food\"></MyComponent>');
assert.compilesTo(emblem, '<MyComponent @foo={{bar}} @baz=\"food\"/>');
});

test('bracketed nested block 1', function (assert) {
Expand Down Expand Up @@ -186,7 +186,7 @@ module('glimmer: brackets', function (hooks) {
);

assert.compilesTo(emblem,
'<MyComponent @onClose={{action (queue (action this.closeWizard) (transition-to "home"))}}></MyComponent>');
'<MyComponent @onClose={{action (queue (action this.closeWizard) (transition-to "home"))}}/>');
});

test("bracketed from first with Sub-expressions", function (assert) {
Expand All @@ -200,6 +200,6 @@ module('glimmer: brackets', function (hooks) {
);

assert.compilesTo(emblem,
'<MyComponent @onClose={{coop (action this.closeWizard) (transition-to "home")}}></MyComponent>');
'<MyComponent @onClose={{coop (action this.closeWizard) (transition-to "home")}}/>');
});
});
4 changes: 2 additions & 2 deletions tests/integration/glimmer/namespacing-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ module('glimmer: namespacing', function (hooks) {
);

assert.compilesTo(emblem,
'<inputs:MyComponent @value={{foo}}></inputs:MyComponent>');
'<inputs:MyComponent @value={{foo}}/>');
});

test('module namespaces', function (assert) {
const emblem = w(
'% my-addon::foo'
);

assert.compilesTo(emblem, '<my-addon::foo></my-addon::foo>');
assert.compilesTo(emblem, '<my-addon::foo/>');
});
});
Loading

0 comments on commit 660a21a

Please sign in to comment.