From c43a99833525b836edbfd6033dc2a3d3dc5bd0dc Mon Sep 17 00:00:00 2001 From: Patrick Steele-Idem Date: Wed, 8 Feb 2017 19:55:53 -0800 Subject: [PATCH] Fixes #560 - Error for unrecognized tags --- compiler/CompileContext.js | 32 +- compiler/Compiler.js | 13 + compiler/index.js | 1 + compiler/taglib-loader/loader-attribute.js | 6 +- compiler/taglib-loader/loader-tag.js | 9 + compiler/taglib-loader/loader-taglib.js | 36 +- compiler/taglib-lookup/TaglibLookup.js | 61 +- compiler/util/html-elements.js | 78 + taglibs/core/marko.json | 23 - taglibs/html/marko.json | 1842 ++++++++++++++++- taglibs/svg/marko.json | 83 + .../render/custom-tag-autodiscover/marko.json | 3 + .../error-unrecognized-tag/expected.html | 1 + .../error-unrecognized-tag/template.marko | 3 + .../render/error-unrecognized-tag/test.js | 15 + .../components/hello/html-elements.json | 3 + .../components/hello/template.marko | 5 + .../expected.html | 1 + .../html-elements.json | 3 + .../template.marko | 1 + .../test.js | 1 + .../components/hello/template.marko | 4 + .../expected.html | 1 + .../html-elements.json | 3 + .../template.marko | 1 + .../html-elements-register-nested/test.js | 1 + .../html-elements-register/expected.html | 1 + .../html-elements-register/html-elements.json | 3 + .../html-elements-register/template.marko | 1 + .../render/html-elements-register/test.js | 1 + test/autotests/render/svg/expected.html | 1 + test/autotests/render/svg/template.marko | 3 + test/autotests/render/svg/test.js | 2 + .../expected.html | 1 - .../template.marko | 4 - .../whitespace-textarea-capitalized/test.js | 3 - .../taglib-lookup/attribute-groups/test.js | 7 + .../taglib-lookup/attribute-ref/marko.json | 11 + .../taglib-lookup/attribute-ref/test.js | 10 + .../taglib-lookup/forEachTag/expected.json | 201 +- .../taglib-lookup/getTagsSorted/expected.json | 188 ++ .../taglib-lookup/html-any-attr/marko.json | 11 + .../taglib-lookup/html-any-attr/test.js | 8 + .../index.marko | 2 - .../app-stateful-button/index.marko | 2 +- 45 files changed, 2638 insertions(+), 52 deletions(-) create mode 100644 compiler/util/html-elements.js create mode 100644 taglibs/svg/marko.json create mode 100644 test/autotests/render/custom-tag-autodiscover/marko.json create mode 100644 test/autotests/render/error-unrecognized-tag/expected.html create mode 100644 test/autotests/render/error-unrecognized-tag/template.marko create mode 100644 test/autotests/render/error-unrecognized-tag/test.js create mode 100644 test/autotests/render/html-elements-register-nested-multiple/components/hello/html-elements.json create mode 100644 test/autotests/render/html-elements-register-nested-multiple/components/hello/template.marko create mode 100644 test/autotests/render/html-elements-register-nested-multiple/expected.html create mode 100644 test/autotests/render/html-elements-register-nested-multiple/html-elements.json create mode 100644 test/autotests/render/html-elements-register-nested-multiple/template.marko create mode 100644 test/autotests/render/html-elements-register-nested-multiple/test.js create mode 100644 test/autotests/render/html-elements-register-nested/components/hello/template.marko create mode 100644 test/autotests/render/html-elements-register-nested/expected.html create mode 100644 test/autotests/render/html-elements-register-nested/html-elements.json create mode 100644 test/autotests/render/html-elements-register-nested/template.marko create mode 100644 test/autotests/render/html-elements-register-nested/test.js create mode 100644 test/autotests/render/html-elements-register/expected.html create mode 100644 test/autotests/render/html-elements-register/html-elements.json create mode 100644 test/autotests/render/html-elements-register/template.marko create mode 100644 test/autotests/render/html-elements-register/test.js create mode 100644 test/autotests/render/svg/expected.html create mode 100644 test/autotests/render/svg/template.marko create mode 100644 test/autotests/render/svg/test.js delete mode 100644 test/autotests/render/whitespace-textarea-capitalized/expected.html delete mode 100644 test/autotests/render/whitespace-textarea-capitalized/template.marko delete mode 100644 test/autotests/render/whitespace-textarea-capitalized/test.js create mode 100644 test/autotests/taglib-lookup/attribute-groups/test.js create mode 100644 test/autotests/taglib-lookup/attribute-ref/marko.json create mode 100644 test/autotests/taglib-lookup/attribute-ref/test.js create mode 100644 test/autotests/taglib-lookup/html-any-attr/marko.json create mode 100644 test/autotests/taglib-lookup/html-any-attr/test.js diff --git a/compiler/CompileContext.js b/compiler/CompileContext.js index 5e07e6d5d..7eb238561 100644 --- a/compiler/CompileContext.js +++ b/compiler/CompileContext.js @@ -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) { diff --git a/compiler/Compiler.js b/compiler/Compiler.js index 6b560d83d..cf390cbd4 100644 --- a/compiler/Compiler.js +++ b/compiler/Compiler.js @@ -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 { + 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) { diff --git a/compiler/taglib-lookup/TaglibLookup.js b/compiler/taglib-lookup/TaglibLookup.js index 51a5355c3..1ac76b8f9 100644 --- a/compiler/taglib-lookup/TaglibLookup.js +++ b/compiler/taglib-lookup/TaglibLookup.js @@ -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') { + 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; \ No newline at end of file diff --git a/taglibs/core/marko.json b/taglibs/core/marko.json index fe1f3da5b..fea21d015 100644 --- a/taglibs/core/marko.json +++ b/taglibs/core/marko.json @@ -171,32 +171,9 @@ } ] }, - "
": {
-        "preserve-whitespace": true
-    },
-    "