Fixes #560 - Error for unrecognized tags

This commit is contained in:
Patrick Steele-Idem 2017-02-08 19:55:53 -08:00
parent 6e17698a5c
commit c43a998335
45 changed files with 2638 additions and 52 deletions

View File

@ -16,6 +16,7 @@ var extend = require('raptor-util/extend');
var Walker = require('./Walker');
var EventEmitter = require('events').EventEmitter;
var utilFingerprint = require('./util/fingerprint');
var htmlElements = require('./util/html-elements');
const FLAG_PRESERVE_WHITESPACE = 'PRESERVE_WHITESPACE';
@ -118,6 +119,8 @@ class CompileContext extends EventEmitter {
this.inline = this.options.inline === true;
this.useMeta = this.options.meta;
this._moduleRuntimeTarget = this.outputType === 'vdom' ? 'marko/vdom' : 'marko/html';
this.unrecognizedTags = [];
this._parsingFinished = false;
this._helpersIdentifier = null;
@ -345,6 +348,13 @@ class CompileContext extends EventEmitter {
}
}
addErrorUnrecognizedTag(tagName, elNode) {
this.addError({
node: elNode,
message: 'Unrecognized tag: ' + tagName + ' - More details: https://github.com/marko-js/marko/wiki/Error:-Unrecognized-Tag'
});
}
createNodeForEl(tagName, attributes, argument, openTagOnly, selfClosed) {
var elDef;
var builder = this.builder;
@ -402,7 +412,27 @@ class CompileContext extends EventEmitter {
node = builder.customTag(elNode);
node.body = node.makeContainer(node.body.items);
} else {
tagDef = typeof tagName === 'string' ? taglibLookup.getTag(tagName) : null;
if (typeof tagName === 'string') {
tagDef = taglibLookup.getTag(tagName);
if (!tagDef &&
!this.isMacro(tagName) &&
tagName.indexOf(':') === -1 &&
!htmlElements.isRegisteredElement(tagName, this.dirname)) {
if (this._parsingFinished) {
this.addErrorUnrecognizedTag(tagName, elNode);
} else {
// We don't throw an error right away since the tag
// may be a macro that gets registered later
this.unrecognizedTags.push({
node: elNode,
tagName: tagName
});
}
}
}
if (tagDef) {
var nodeFactoryFunc = tagDef.getNodeFactory();
if (nodeFactoryFunc) {

View File

@ -136,6 +136,19 @@ class Compiler {
// STAGE 1: Parse the template to produce the initial AST
var ast = this.parser.parse(src, context);
context._parsingFinished = true;
if (context.unrecognizedTags) {
for(let i=0; i<context.unrecognizedTags.length; i++) {
let unrecognizedTag = context.unrecognizedTags[i];
// See if the tag is a macro
if (!context.isMacro(unrecognizedTag.tagName)) {
context.addErrorUnrecognizedTag(unrecognizedTag.tagName, unrecognizedTag.node);
}
}
}
handleErrors(context);
context.root = ast;
// console.log('ROOT', JSON.stringify(ast, null, 2));

View File

@ -229,6 +229,7 @@ exports.buildTaglibLookup = buildTaglibLookup;
taglibLookup.registerTaglib(require.resolve('../taglibs/core/marko.json'));
taglibLookup.registerTaglib(require.resolve('../taglibs/layout/marko.json'));
taglibLookup.registerTaglib(require.resolve('../taglibs/html/marko.json'));
taglibLookup.registerTaglib(require.resolve('../taglibs/svg/marko.json'));
taglibLookup.registerTaglib(require.resolve('../taglibs/async/marko.json'));
taglibLookup.registerTaglib(require.resolve('../taglibs/cache/marko.json'));
taglibLookup.registerTaglib(require.resolve('../widgets/taglib/marko.json'));

View File

@ -44,7 +44,11 @@ class AttrLoader {
*/
type(value) {
var attr = this.attr;
attr.type = value;
if (value.charAt(0) === '#') {
attr.ref = value.substring(1);
} else {
attr.type = value;
}
}
/**

View File

@ -616,6 +616,15 @@ class TagLoader {
parseAttributes(value) {
this.tag.parseAttributes = value;
}
attributeGroups(value) {
if (!value) {
return;
}
var attributeGroups = this.tag.attributeGroups || (this.tag.attributeGroups = []);
this.tag.attributeGroups = attributeGroups.concat(value);
}
}
function isSupportedProperty(name) {

View File

@ -2,7 +2,6 @@
var ok = require('assert').ok;
var nodePath = require('path');
var handleAttributes = require('./handleAttributes');
var scanTagsDir = require('./scanTagsDir');
var resolve = require('../util/resolve'); // NOTE: different implementation for browser
var propertyHandlers = require('property-handlers');
@ -151,9 +150,16 @@ class TaglibLoader {
// }
// }
var taglib = this.taglib;
var path = this.filePath;
handleAttributes(value, taglib, path);
Object.keys(value).forEach((attrName) => {
var attrDef = value[attrName];
var attr = attributeLoader.loadAttribute(
attrName,
attrDef,
this.dependencyChain.append('@' + attrName));
taglib.addAttribute(attr);
});
}
tags(tags) {
// The value of the "tags" property will be an object
@ -319,6 +325,30 @@ class TaglibLoader {
taglib.addTransformer(transformer);
}
attributeGroups(value) {
let taglib = this.taglib;
let attributeGroups = taglib.attributeGroups || (taglib.attributeGroups = {});
let dependencyChain = this.dependencyChain.append('attribute-groups');
Object.keys(value).forEach((attrGroupName) => {
let attrGroup = attributeGroups[attrGroupName] = {};
let attrGroupDependencyChain = dependencyChain.append(attrGroupName);
let rawAttrGroup = value[attrGroupName];
Object.keys(rawAttrGroup).forEach((attrName) => {
var rawAttrDef = rawAttrGroup[attrName];
let attr = attributeLoader.loadAttribute(
attrName,
rawAttrDef,
attrGroupDependencyChain.append('@' + attrName));
attrGroup[attrName] = attr;
});
});
}
}
exports.loadTaglib = function(filePath, taglib, dependencyChain) {

View File

@ -75,7 +75,9 @@ function merge(target, source) {
*/
class TaglibLookup {
constructor() {
this.merged = {};
this.merged = {
attributeGroups: {}
};
this.taglibsById = {};
this._inputFiles = null;
@ -122,6 +124,8 @@ class TaglibLookup {
return;
}
// console.log("TAGLIB:", taglib);
this._sortedTags = undefined;
this.taglibsById[taglib.id] = taglib;
@ -131,7 +135,8 @@ class TaglibLookup {
transformers: taglib.transformers,
textTransformers: taglib.textTransformers,
attributes: taglib.attributes,
patternAttributes: taglib.patternAttributes
patternAttributes: taglib.patternAttributes,
attributeGroups: taglib.attributeGroups || {}
});
this._mergeNestedTags(taglib);
@ -172,12 +177,24 @@ class TaglibLookup {
return;
}
var globalAttributes = this.merged.attributes;
var taglibAttributeGroups = this.merged.attributeGroups;
function findAttributesForTagName(tagName) {
var tag = tags[tagName];
if (!tag) {
return;
}
function handleAttr(attrDef) {
if (attrDef.ref) {
attrDef = globalAttributes[attrDef.ref];
}
callback(attrDef, tag);
}
var attributes = tag.attributes;
if (!attributes) {
return;
@ -185,12 +202,24 @@ class TaglibLookup {
for (var attrName in attributes) {
if (attributes.hasOwnProperty(attrName)) {
callback(attributes[attrName], tag);
handleAttr(attributes[attrName], tag);
}
}
if (tag.attributeGroups) {
for (let i=0; i<tag.attributeGroups.length; i++) {
let attributeGroupName = tag.attributeGroups[i];
let attributeGroup = taglibAttributeGroups[attributeGroupName];
if (attributeGroup) {
for (let attrName in attributeGroup) {
handleAttr(attributeGroup[attrName]);
}
}
}
}
if (tag.patternAttributes) {
tag.patternAttributes.forEach(callback);
tag.patternAttributes.forEach(handleAttr);
}
}
@ -231,12 +260,29 @@ class TaglibLookup {
return;
}
var taglibAttributeGroups = this.merged.attributeGroups;
var tagName = element.tagName;
var attrName = attr.name;
function findAttributeForTag(tag, attributes, attrName) {
// try by exact match first
var attribute = attributes[attrName];
if (attribute === undefined) {
if (tag.attributeGroups) {
for (let i=0; i<tag.attributeGroups.length; i++) {
let attributeGroupName = tag.attributeGroups[i];
let attributeGroup = taglibAttributeGroups[attributeGroupName];
if (attributeGroup) {
attribute = attributeGroup[attrName];
if (attribute !== undefined) {
break;
}
}
}
}
}
if (attribute === undefined && attrName !== '*') {
if (tag.patternAttributes) {
// try searching by pattern
@ -261,14 +307,17 @@ class TaglibLookup {
return undefined;
}
return findAttributeForTag(tag, tag.attributes, attrName) ||
findAttributeForTag(tag, globalAttributes, attrName);
return findAttributeForTag(tag, tag.attributes, attrName);
}
var attrDef = tryAttribute(tagName, attrName) || // Look for an exact match at the tag level
tryAttribute('*', attrName) || // If not there, see if there is a exact match on the attribute name for attributes that apply to all tags
tryAttribute(tagName, '*'); // Otherwise, see if there is a splat attribute for the tag
if (attrDef && attrDef.ref) {
attrDef = globalAttributes[attrDef.ref];
}
return attrDef;
}

View File

@ -0,0 +1,78 @@
var lassoPackageRoot = require('lasso-package-root');
var path = require('path');
var lassoCachingFS = require('lasso-caching-fs');
var fs = require('fs');
var stripJsonComments = require('strip-json-comments');
var fsReadOptions = { encoding: 'utf8' };
function parseJSONFile(path) {
var json = fs.readFileSync(path, fsReadOptions);
try {
var taglibProps = JSON.parse(stripJsonComments(json));
return taglibProps;
} catch(e) {
throw new Error('Unable to parse JSON file at path "' + path + '". Error: ' + e);
}
}
function loadTags(file) {
var raw = parseJSONFile(file);
var tags = {};
for (var k in raw) {
if (raw.hasOwnProperty(k)) {
if (k.charAt(0) === '<' && k.charAt(k.length - 1) === '>') {
var tagName = k.substring(1, k.length - 1);
tags[tagName] = true;
}
}
}
return tags;
}
var cache = {};
function getPackageRootDir(dirname) {
try {
return lassoPackageRoot.getRootDir(dirname);
} catch(e) {
return undefined;
}
}
function isRegisteredElement(tagName, dir) {
var packageRootDir = getPackageRootDir(dir);
var currentDir = dir;
while (true) {
var filePath = path.join(currentDir, 'html-elements.json');
if (lassoCachingFS.existsSync(filePath)) {
var tags = cache[filePath];
if (!tags) {
tags = cache[filePath] = loadTags(filePath);
}
if (tags[tagName]) {
return true;
}
}
var parentDir = path.dirname(currentDir);
if (!parentDir || parentDir === currentDir || parentDir === packageRootDir) {
break;
}
currentDir = parentDir;
}
return false;
}
exports.isRegisteredElement = isRegisteredElement;

View File

@ -171,32 +171,9 @@
}
]
},
"<pre>": {
"preserve-whitespace": true
},
"<script>": {
"preserve-whitespace": true,
"@marko-init": "boolean",
"@template-helpers": "boolean",
"@*": {
"ignore": true
},
"autocomplete": [
{
"snippet": "script template-helpers",
"descriptionMoreURL": "http://markojs.com/docs/marko/language-guide/#helpers"
}
]
},
"<static>": {
"code-generator": "./static-tag"
},
"<style>": {
"preserve-whitespace": true
},
"<textarea>": {
"preserve-whitespace": true
},
"<unless>": {
"node-factory": "./unless-tag",
"autocomplete": [

File diff suppressed because it is too large Load Diff

83
taglibs/svg/marko.json Normal file
View File

@ -0,0 +1,83 @@
{
"taglib-id": "marko-svg",
"<a>": {},
"<altGlyph>": {},
"<altGlyphDef>": {},
"<altGlyphItem>": {},
"<animate>": {},
"<animateColor>": {},
"<animateMotion>": {},
"<animateTransform>": {},
"<circle>": {},
"<clipPath>": {},
"<color-profile>": {},
"<cursor>": {},
"<defs>": {},
"<desc>": {},
"<ellipse>": {},
"<feBlend>": {},
"<feColorMatrix>": {},
"<feComponentTransfer>": {},
"<feComposite>": {},
"<feConvolveMatrix>": {},
"<feDiffuseLighting>": {},
"<feDisplacementMap>": {},
"<feDistantLight>": {},
"<feFlood>": {},
"<feFuncA>": {},
"<feFuncB>": {},
"<feFuncG>": {},
"<feFuncR>": {},
"<feGaussianBlur>": {},
"<feImage>": {},
"<feMerge>": {},
"<feMergeNode>": {},
"<feMorphology>": {},
"<feOffset>": {},
"<fePointLight>": {},
"<feSpecularLighting>": {},
"<feSpotLight>": {},
"<feTile>": {},
"<feTurbulence>": {},
"<filter>": {},
"<font>": {},
"<font-face>": {},
"<font-face-format>": {},
"<font-face-name>": {},
"<font-face-src>": {},
"<font-face-uri>": {},
"<foreignObject>": {},
"<g>": {},
"<glyph>": {},
"<glyphRef>": {},
"<hkern>": {},
"<image>": {},
"<line>": {},
"<linearGradient>": {},
"<marker>": {},
"<mask>": {},
"<metadata>": {},
"<missing-glyph>": {},
"<mpath>": {},
"<path>": {},
"<pattern>": {},
"<polygon>": {},
"<polyline>": {},
"<radialGradient>": {},
"<rect>": {},
"<script>": {},
"<set>": {},
"<stop>": {},
"<style>": {},
"<svg>": {},
"<switch>": {},
"<symbol>": {},
"<text>": {},
"<textPath>": {},
"<title>": {},
"<tref>": {},
"<tspan>": {},
"<use>": {},
"<view>": {},
"<vkern>": {}
}

View File

@ -0,0 +1,3 @@
{
"<test-home>": {}
}

View File

@ -0,0 +1 @@
<button class="btn active btn-bar">Click me</button>

View File

@ -0,0 +1,3 @@
<div>
<invalid-tag/>
</div>

View File

@ -0,0 +1,15 @@
var expect = require('chai').expect;
exports.templateData = {};
exports.checkError = function(e) {
//includes the tag it broke on
expect(e.message).to.contain('Unrecognized tag: invalid-tag');
expect(e.message).to.contain('https://github.com/marko-js/marko/wiki/Error:-Unrecognized-Tag');
//includes the line number of the template
expect(e.message).to.contain('template.marko:2:4');
};

View File

@ -0,0 +1,3 @@
{
"<another-custom-element>": {}
}

View File

@ -0,0 +1,5 @@
<div>
Hello ${input.name}!
<my-custom-element/>
<another-custom-element/>
</div>

View File

@ -0,0 +1 @@
<div>Hello Frank! <my-custom-element></my-custom-element><another-custom-element></another-custom-element></div>

View File

@ -0,0 +1,3 @@
{
"<my-custom-element>": {}
}

View File

@ -0,0 +1 @@
<hello name="Frank"/>

View File

@ -0,0 +1 @@
exports.templateData = {};

View File

@ -0,0 +1,4 @@
<div>
Hello ${input.name}!
<my-custom-element/>
</div>

View File

@ -0,0 +1 @@
<div>Hello Frank! <my-custom-element></my-custom-element></div>

View File

@ -0,0 +1,3 @@
{
"<my-custom-element>": {}
}

View File

@ -0,0 +1 @@
<hello name="Frank"/>

View File

@ -0,0 +1 @@
exports.templateData = {};

View File

@ -0,0 +1 @@
<my-custom-element></my-custom-element>

View File

@ -0,0 +1,3 @@
{
"<my-custom-element>": {}
}

View File

@ -0,0 +1 @@
<my-custom-element/>

View File

@ -0,0 +1 @@
exports.templateData = {};

View File

@ -0,0 +1 @@
<svg viewBox="0 0 200 200"><circle cx="100" cy="100" r="100"></circle></svg>

After

Width:  |  Height:  |  Size: 76 B

View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 200 200">
<circle cx="100" cy="100" r="100"/>
</svg>

After

Width:  |  Height:  |  Size: 72 B

View File

@ -0,0 +1,2 @@
exports.templateData = {};
exports.vdomSkip = true;

View File

@ -1 +0,0 @@
<TEXTAREA>Hello World</TEXTAREA>

View File

@ -1,4 +0,0 @@
<TEXTAREA>
Hello
World
</TEXTAREA>

View File

@ -1,3 +0,0 @@
exports.templateData = {
"name": "World"
};

View File

@ -0,0 +1,7 @@
exports.check = function(markoCompiler, expect) {
var taglibLookup = markoCompiler.taglibLookup;
var lookup = taglibLookup.buildLookup(__dirname);
var idAttrDef = lookup.getAttribute('div', 'class');
expect(idAttrDef.type).to.equal('cssStyle');
};

View File

@ -0,0 +1,11 @@
{
"<foo>": {
"@hello": "#shared-hello"
},
"<bar>": {
"@hello": "#shared-hello"
},
"@shared-hello": {
"type": "shared"
}
}

View File

@ -0,0 +1,10 @@
exports.check = function(markoCompiler, expect) {
var taglibLookup = markoCompiler.taglibLookup;
var lookup = taglibLookup.buildLookup(__dirname);
var sharedAttrDef = lookup.getAttribute('foo', 'hello');
expect(sharedAttrDef.type).equal('shared');
sharedAttrDef = lookup.getAttribute('bar', 'hello');
expect(sharedAttrDef.type).equal('shared');
};

View File

@ -25,11 +25,7 @@
"macro",
"macro-body",
"marko-preserve-whitespace",
"pre",
"script",
"static",
"style",
"textarea",
"unless",
"var",
"while",
@ -37,6 +33,200 @@
"layout-put",
"layout-placeholder",
"html-comment",
"a",
"abbr",
"address",
"area",
"article",
"aside",
"audio",
"b",
"base",
"bdi",
"bdo",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"cite",
"code",
"col",
"colgroup",
"command",
"datalist",
"dd",
"del",
"details",
"dfn",
"div",
"dl",
"dt",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hgroup",
"hr",
"html",
"i",
"iframe",
"img",
"input",
"ins",
"kbd",
"keygen",
"label",
"legend",
"li",
"link",
"map",
"mark",
"menu",
"meta",
"meter",
"nav",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"param",
"pre",
"progress",
"q",
"rp",
"rt",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"small",
"source",
"span",
"strong",
"style",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"u",
"ul",
"video",
"wbr",
"big",
"dialog",
"ilayer",
"main",
"marquee",
"tt",
"content",
"data",
"menuitem",
"opt",
"template",
"altGlyph",
"altGlyphDef",
"altGlyphItem",
"animate",
"animateColor",
"animateMotion",
"animateTransform",
"circle",
"clipPath",
"color-profile",
"cursor",
"defs",
"desc",
"ellipse",
"feBlend",
"feColorMatrix",
"feComponentTransfer",
"feComposite",
"feConvolveMatrix",
"feDiffuseLighting",
"feDisplacementMap",
"feDistantLight",
"feFlood",
"feFuncA",
"feFuncB",
"feFuncG",
"feFuncR",
"feGaussianBlur",
"feImage",
"feMerge",
"feMergeNode",
"feMorphology",
"feOffset",
"fePointLight",
"feSpecularLighting",
"feSpotLight",
"feTile",
"feTurbulence",
"filter",
"font",
"font-face",
"font-face-format",
"font-face-name",
"font-face-src",
"font-face-uri",
"foreignObject",
"g",
"glyph",
"glyphRef",
"hkern",
"image",
"line",
"linearGradient",
"marker",
"mask",
"metadata",
"missing-glyph",
"mpath",
"path",
"pattern",
"polygon",
"polyline",
"radialGradient",
"rect",
"set",
"stop",
"svg",
"switch",
"symbol",
"text",
"textPath",
"tref",
"tspan",
"use",
"view",
"vkern",
"await",
"await-reorderer",
"await-placeholder",
@ -52,6 +242,5 @@
"init-widgets",
"w-preserve",
"no-update",
"widget-types",
"body"
"widget-types"
]

View File

@ -3,35 +3,145 @@
"_widget",
"*",
"a",
"abbr",
"address",
"altGlyph",
"altGlyphDef",
"altGlyphItem",
"animate",
"animateColor",
"animateMotion",
"animateTransform",
"area",
"article",
"aside",
"assign",
"async-fragment",
"async-fragment-error",
"async-fragment-placeholder",
"async-fragment-timeout",
"async-fragments",
"audio",
"await",
"await-error",
"await-placeholder",
"await-reorderer",
"await-timeout",
"b",
"bar",
"base",
"bdi",
"bdo",
"big",
"blockquote",
"body",
"br",
"browser-refresh",
"button",
"cached-fragment",
"canvas",
"caption",
"circle",
"cite",
"class",
"clipPath",
"code",
"col",
"colgroup",
"color-profile",
"command",
"content",
"cursor",
"data",
"datalist",
"dd",
"defs",
"del",
"desc",
"details",
"dfn",
"dialog",
"div",
"dl",
"dt",
"ellipse",
"else",
"else-if",
"em",
"embed",
"feBlend",
"feColorMatrix",
"feComponentTransfer",
"feComposite",
"feConvolveMatrix",
"feDiffuseLighting",
"feDisplacementMap",
"feDistantLight",
"feFlood",
"feFuncA",
"feFuncB",
"feFuncG",
"feFuncR",
"feGaussianBlur",
"feImage",
"feMerge",
"feMergeNode",
"feMorphology",
"feOffset",
"fePointLight",
"feSpecularLighting",
"feSpotLight",
"feTile",
"feTurbulence",
"fieldset",
"figcaption",
"figure",
"filter",
"font",
"font-face",
"font-face-format",
"font-face-name",
"font-face-src",
"font-face-uri",
"foo",
"footer",
"for",
"foreignObject",
"form",
"function",
"g",
"glyph",
"glyphRef",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hgroup",
"hkern",
"hr",
"html",
"html-comment",
"i",
"if",
"iframe",
"ilayer",
"image",
"img",
"import",
"include",
"include-html",
"include-text",
"init-widgets",
"input",
"ins",
"invoke",
"kbd",
"keygen",
"label",
"lasso-body",
"lasso-head",
"lasso-img",
@ -41,18 +151,96 @@
"layout-placeholder",
"layout-put",
"layout-use",
"legend",
"li",
"line",
"linearGradient",
"link",
"macro",
"macro-body",
"main",
"map",
"mark",
"marker",
"marko-preserve-whitespace",
"marquee",
"mask",
"menu",
"menuitem",
"meta",
"metadata",
"meter",
"missing-glyph",
"mpath",
"nav",
"no-update",
"noscript",
"object",
"ol",
"opt",
"optgroup",
"option",
"output",
"p",
"param",
"path",
"pattern",
"polygon",
"polyline",
"pre",
"progress",
"q",
"radialGradient",
"rect",
"rp",
"rt",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"set",
"small",
"source",
"span",
"static",
"stop",
"strong",
"style",
"sub",
"summary",
"sup",
"svg",
"switch",
"symbol",
"table",
"tbody",
"td",
"template",
"text",
"textarea",
"textPath",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"tref",
"tspan",
"tt",
"u",
"ul",
"unless",
"use",
"var",
"video",
"view",
"vkern",
"w-preserve",
"wbr",
"while",
"widget-types"
]

View File

@ -0,0 +1,11 @@
{
"<foo>": {
"@hello": "#shared-hello"
},
"<bar>": {
"@hello": "#shared-hello"
},
"@shared-hello": {
"type": "shared"
}
}

View File

@ -0,0 +1,8 @@
exports.check = function(markoCompiler, expect) {
var taglibLookup = markoCompiler.taglibLookup;
var lookup = taglibLookup.buildLookup(__dirname);
var attrDef = lookup.getAttribute('div', 'blah');
expect(attrDef).to.be.an('object');
};

View File

@ -1,4 +1,2 @@
<div>
<app-legacy-button size="small" ref="button1">button1</app-legacy-button>
<app-legacy-button size="normal" ref="button2">button2</app-legacy-button>
</div>

View File

@ -1,4 +1,4 @@
<button ${input.rootAttrs}
onClick("handleClick")>
<body-slot/>
<include(input.renderBody)/>
</button>