diff --git a/compiler/Builder.js b/compiler/Builder.js index 01520674c..0f81d5740 100644 --- a/compiler/Builder.js +++ b/compiler/Builder.js @@ -352,10 +352,14 @@ class Builder { return new TemplateRoot({body}); } - text(argument, escape) { + text(argument, escape, preserveWhitespace) { + if (typeof argument === 'object' && !(argument instanceof Node)) { + var def = arguments[0]; + return new Text(def); + } argument = makeNode(argument); - return new Text({argument, escape}); + return new Text({argument, escape, preserveWhitespace}); } thisExpression() { diff --git a/compiler/CodeGenerator.js b/compiler/CodeGenerator.js index 840e9012a..56f6d59d7 100644 --- a/compiler/CodeGenerator.js +++ b/compiler/CodeGenerator.js @@ -327,22 +327,11 @@ class Generator { } addWriteLiteral(value) { - let lastWrite = this._bufferedWrites ? - this._bufferedWrites[this._bufferedWrites.length-1] : - null; - - if (lastWrite instanceof Literal) { - lastWrite.value += value; - return; + if (!(value instanceof Literal)) { + value = new Literal({value}); } - let output = new Literal({value}); - - if (!this._bufferedWrites) { - this._bufferedWrites = [output]; - } else { - this._bufferedWrites.push(output); - } + this.addWrite(value); } addWrite(output) { @@ -355,6 +344,10 @@ class Generator { lastWrite.value += output.value; return; } + } else { + if (!(output instanceof Node)) { + throw new Error('Invalid write: ' + JSON.stringify(output, null, 2)); + } } if (!this._bufferedWrites) { diff --git a/compiler/CompileContext.js b/compiler/CompileContext.js index c372a7bb7..5bcb38453 100644 --- a/compiler/CompileContext.js +++ b/compiler/CompileContext.js @@ -58,6 +58,7 @@ class CompileContext { this._flags = {}; this._errors = []; this._macros = null; + this._preserveWhitespace = null; } getPosInfo(pos) { @@ -144,6 +145,21 @@ class CompileContext { return this._staticCode; } + getTagDef(tagName) { + var taglibLookup = this.taglibLookup; + + if (typeof tagName === 'string') { + return taglibLookup.getTag(tagName); + } else { + let elNode = tagName; + if (elNode.tagDef) { + return elNode.tagDef; + } + + return taglibLookup.getTag(elNode.tagName); + } + } + createNodeForEl(tagName, attributes, argument, openTagOnly, selfClosed) { var elDef; var builder = this.builder; @@ -275,6 +291,14 @@ class CompileContext { var templateVar = this.addStaticVar(removeExt(relativePath), loadFunctionCall); return templateVar; } + + setPreserveWhitespace(preserveWhitespace) { + this._preserveWhitespace = preserveWhitespace; + } + + isPreserveWhitespace() { + return this._preserveWhitespace === true; + } } module.exports = CompileContext; \ No newline at end of file diff --git a/compiler/HtmlJsParser.js b/compiler/HtmlJsParser.js index 3ff331d40..db8592b7f 100644 --- a/compiler/HtmlJsParser.js +++ b/compiler/HtmlJsParser.js @@ -3,7 +3,7 @@ var htmljs = require('htmljs-parser'); class HtmlJsParser { parse(src, handlers) { - var parser = this.parser = htmljs.createParser({ + var listeners = { ontext(event) { handlers.handleCharacters(event.text); }, @@ -55,20 +55,22 @@ class HtmlJsParser { }, onerror(event) { - // Error + handlers.handleError(event); } - }); + }; + + var options = { + parserStateProvider(event) { + if (event.type === 'opentag') { + return handlers.getParserStateForTag(event); + } + } + }; + + var parser = this.parser = htmljs.createParser(listeners, options); parser.parse(src); } - - enterParsedTextContentState() { - this.parser.enterParsedTextContentState(); - } - - enterStaticTextContentState() { - this.parser.enterStaticTextContentState(); - } } module.exports = HtmlJsParser; \ No newline at end of file diff --git a/compiler/Parser.js b/compiler/Parser.js index 5b28df311..9d5ac5f09 100644 --- a/compiler/Parser.js +++ b/compiler/Parser.js @@ -2,15 +2,11 @@ var ok = require('assert').ok; var COMPILER_ATTRIBUTE_HANDLERS = { - whitespace: function(attr, compilerOptions) { - if (attr.value === 'preserve') { - compilerOptions.preserveWhitespace = true; - } + 'preserve-whitespace': function(attr, context) { + context.setPreserveWhitespace(true); }, - comments: function(attr, compilerOptions) { - if (attr.value === 'preserve') { - compilerOptions.preserveComments = true; - } + 'preserve-comments': function(attr, context) { + context.setPreserveComments(true); } }; @@ -70,7 +66,8 @@ class Parser { if (this.prevTextNode && this.prevTextNode.isLiteral()) { this.prevTextNode.appendText(text); } else { - this.prevTextNode = builder.text(builder.literal(text)); + var escape = false; + this.prevTextNode = builder.text(builder.literal(text), escape); this.prevTextNode.pos = text.pos; this.parentNode.appendChild(this.prevTextNode); } @@ -85,18 +82,21 @@ class Parser { var argument = el.argument; // e.g. For , argument will be "color in colors" if (tagName === 'compiler-options') { - var compilerOptions = this.compilerOptions; - attributes.forEach(function (attr) { let attrName = attr.name; - let attrValue = attr.value; - let handler = COMPILER_ATTRIBUTE_HANDLERS[attrValue]; + let handler = COMPILER_ATTRIBUTE_HANDLERS[attrName]; if (!handler) { - throw new Error('Invalid Marko compiler option: ' + attrName + ', Allowed: ' + Object.keys(COMPILER_ATTRIBUTE_HANDLERS)); + context.addError({ + code: 'ERR_INVALID_COMPILER_OPTION', + message: 'Invalid Marko compiler option of "' + attrName + '". Allowed: ' + Object.keys(COMPILER_ATTRIBUTE_HANDLERS).join(', '), + pos: el.pos, + node: el + }); + return; } - handler(attr, compilerOptions); + handler(attr, context); }); return; @@ -138,11 +138,7 @@ class Parser { if (node.tagDef) { var body = tagDef.body; if (body) { - if (body === 'parsed-text') { - this.parserImpl.enterParsedTextContentState(); - } else if (body === 'static-text') { - this.parserImpl.enterStaticTextContentState(); - } + } } @@ -185,15 +181,65 @@ class Parser { var parsedExpression = parseExpression(expression); var builder = this.context.builder; + var preserveWhitespace = true; - var text = builder.text(parsedExpression, escape); + var text = builder.text(parsedExpression, escape, preserveWhitespace); this.parentNode.appendChild(text); } + handleError(event) { + this.context.addError({ + message: event.message, + code: event.code, + pos: event.pos, + endPos: event.endPos + }); + } + get parentNode() { var last = this.stack[this.stack.length-1]; return last.node; } + + getParserStateForTag(el) { + var attributes = el.attributes; + + for (var i=0; i 0); + var hasBody = this.body && this.body.length; if(!selfInvoking && hasBody) { this.setFlag('selfInvoking'); @@ -59,6 +59,13 @@ class Vars extends Node { walk(walker) { this.argument = walker.walk(this.argument); } + + /** + * "noOutput" should be true if the Node.js does not result in any HTML or Text output + */ + get noOutput() { + return !(this.body && this.body.length); + } } module.exports = Vars; \ No newline at end of file diff --git a/taglibs/core/core-transformer.js b/taglibs/core/core-transformer.js index c45a51d70..49a06fa6e 100644 --- a/taglibs/core/core-transformer.js +++ b/taglibs/core/core-transformer.js @@ -77,6 +77,11 @@ var coreAttrHandlers = [ node.addDynamicAttributes(attr.value); } } + ], + [ + 'marko-preserve-whitespace', function(attr, node) { + node.setPreserveWhitespace(true); + } ] ]; @@ -104,6 +109,8 @@ coreAttrHandlers.forEach(function(attrHandler) { var attributeTransformers = AttributeTransformer.prototype; module.exports = function transform(el, context) { + el.removeAttribute('marko-body'); // This attribute is handled at parse time. We can just remove it now + var attributeTransfomer; var node = el; diff --git a/test/fixtures/compiler/autotest/template-init/expected.js b/test/fixtures/compiler/autotest/template-init/expected.js index 93d06b508..7466df751 100644 --- a/test/fixtures/compiler/autotest/template-init/expected.js +++ b/test/fixtures/compiler/autotest/template-init/expected.js @@ -4,7 +4,7 @@ function create(__helpers) { notEmpty = __helpers.ne, escapeXml = __helpers.x; - var name = 'Frank'; + var name = '${name}
'; return function render(data, out) { out.w(" Hello " + diff --git a/test/fixtures/compiler/autotest/template-init/template.marko b/test/fixtures/compiler/autotest/template-init/template.marko index 612fe867a..ea212495a 100644 --- a/test/fixtures/compiler/autotest/template-init/template.marko +++ b/test/fixtures/compiler/autotest/template-init/template.marko @@ -1,5 +1,5 @@ -var name = 'Frank'; +var name = '${name}
';
Hello ${name}! \ No newline at end of file diff --git a/test/fixtures/render/autotest-pending/text-replacement/template.marko b/test/fixtures/render/autotest-pending/text-replacement/template.marko deleted file mode 100644 index decde49c7..000000000 --- a/test/fixtures/render/autotest-pending/text-replacement/template.marko +++ /dev/null @@ -1,3 +0,0 @@ - - -Hello $person.name. You are from ${person.address.city}, $person.address.state Zero: ${data.zero} \ No newline at end of file diff --git a/test/fixtures/render/autotest-pending/whitespace4/template.marko b/test/fixtures/render/autotest-pending/whitespace4/template.marko deleted file mode 100644 index 9cb413ead..000000000 --- a/test/fixtures/render/autotest-pending/whitespace4/template.marko +++ /dev/null @@ -1,4 +0,0 @@ - -A -B -C diff --git a/test/fixtures/render/autotest/body-placeholder-literal-escaped/expected.html b/test/fixtures/render/autotest/body-placeholder-literal-escaped/expected.html new file mode 100644 index 000000000..d133d1ed3 --- /dev/null +++ b/test/fixtures/render/autotest/body-placeholder-literal-escaped/expected.html @@ -0,0 +1 @@ +<div></div> \ No newline at end of file diff --git a/test/fixtures/render/autotest/body-placeholder-literal-escaped/template.marko b/test/fixtures/render/autotest/body-placeholder-literal-escaped/template.marko new file mode 100644 index 000000000..39ba0b38e --- /dev/null +++ b/test/fixtures/render/autotest/body-placeholder-literal-escaped/template.marko @@ -0,0 +1 @@ +${"
"} \ No newline at end of file diff --git a/test/fixtures/render/autotest/body-placeholder-literal-escaped/test.js b/test/fixtures/render/autotest/body-placeholder-literal-escaped/test.js new file mode 100644 index 000000000..c128acd4a --- /dev/null +++ b/test/fixtures/render/autotest/body-placeholder-literal-escaped/test.js @@ -0,0 +1,7 @@ +exports.templateData = { + "myAttrs": { + "style": "background-color: #FF0000; ", + "class": "my-div", + "checked": true + } +}; \ No newline at end of file diff --git a/test/fixtures/render/autotest/body-placeholder-literal-unescaped-escaped/expected.html b/test/fixtures/render/autotest/body-placeholder-literal-unescaped-escaped/expected.html new file mode 100644 index 000000000..9e9d4c9f4 --- /dev/null +++ b/test/fixtures/render/autotest/body-placeholder-literal-unescaped-escaped/expected.html @@ -0,0 +1 @@ +<div></div>
\ No newline at end of file diff --git a/test/fixtures/render/autotest/body-placeholder-literal-unescaped-escaped/template.marko b/test/fixtures/render/autotest/body-placeholder-literal-unescaped-escaped/template.marko new file mode 100644 index 000000000..d6a5f16c5 --- /dev/null +++ b/test/fixtures/render/autotest/body-placeholder-literal-unescaped-escaped/template.marko @@ -0,0 +1 @@ +${"
"} $!{"
"} \ No newline at end of file diff --git a/test/fixtures/render/autotest/body-placeholder-literal-unescaped-escaped/test.js b/test/fixtures/render/autotest/body-placeholder-literal-unescaped-escaped/test.js new file mode 100644 index 000000000..c128acd4a --- /dev/null +++ b/test/fixtures/render/autotest/body-placeholder-literal-unescaped-escaped/test.js @@ -0,0 +1,7 @@ +exports.templateData = { + "myAttrs": { + "style": "background-color: #FF0000; ", + "class": "my-div", + "checked": true + } +}; \ No newline at end of file diff --git a/test/fixtures/render/autotest/body-placeholder-literal-unescaped/expected.html b/test/fixtures/render/autotest/body-placeholder-literal-unescaped/expected.html new file mode 100644 index 000000000..281c6866c --- /dev/null +++ b/test/fixtures/render/autotest/body-placeholder-literal-unescaped/expected.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/fixtures/render/autotest/body-placeholder-literal-unescaped/template.marko b/test/fixtures/render/autotest/body-placeholder-literal-unescaped/template.marko new file mode 100644 index 000000000..ed6d9e6c4 --- /dev/null +++ b/test/fixtures/render/autotest/body-placeholder-literal-unescaped/template.marko @@ -0,0 +1 @@ +$!{"
"} \ No newline at end of file diff --git a/test/fixtures/render/autotest/body-placeholder-literal-unescaped/test.js b/test/fixtures/render/autotest/body-placeholder-literal-unescaped/test.js new file mode 100644 index 000000000..c128acd4a --- /dev/null +++ b/test/fixtures/render/autotest/body-placeholder-literal-unescaped/test.js @@ -0,0 +1,7 @@ +exports.templateData = { + "myAttrs": { + "style": "background-color: #FF0000; ", + "class": "my-div", + "checked": true + } +}; \ No newline at end of file diff --git a/test/fixtures/render/autotest-pending/whitespace2/expected.html b/test/fixtures/render/autotest/body-placeholder-literal/expected.html similarity index 100% rename from test/fixtures/render/autotest-pending/whitespace2/expected.html rename to test/fixtures/render/autotest/body-placeholder-literal/expected.html diff --git a/test/fixtures/render/autotest-pending/whitespace2/template.marko b/test/fixtures/render/autotest/body-placeholder-literal/template.marko similarity index 100% rename from test/fixtures/render/autotest-pending/whitespace2/template.marko rename to test/fixtures/render/autotest/body-placeholder-literal/template.marko diff --git a/test/fixtures/render/autotest-pending/whitespace/test.js b/test/fixtures/render/autotest/body-placeholder-literal/test.js similarity index 100% rename from test/fixtures/render/autotest-pending/whitespace/test.js rename to test/fixtures/render/autotest/body-placeholder-literal/test.js diff --git a/test/fixtures/render/autotest-pending/whitespace3/expected.html b/test/fixtures/render/autotest/body-placeholder-literal2/expected.html similarity index 100% rename from test/fixtures/render/autotest-pending/whitespace3/expected.html rename to test/fixtures/render/autotest/body-placeholder-literal2/expected.html diff --git a/test/fixtures/render/autotest-pending/whitespace3/template.marko b/test/fixtures/render/autotest/body-placeholder-literal2/template.marko similarity index 100% rename from test/fixtures/render/autotest-pending/whitespace3/template.marko rename to test/fixtures/render/autotest/body-placeholder-literal2/template.marko diff --git a/test/fixtures/render/autotest-pending/whitespace2/test.js b/test/fixtures/render/autotest/body-placeholder-literal2/test.js similarity index 100% rename from test/fixtures/render/autotest-pending/whitespace2/test.js rename to test/fixtures/render/autotest/body-placeholder-literal2/test.js diff --git a/test/fixtures/render/autotest-pending/whitespace4/expected.html b/test/fixtures/render/autotest/compiler-option-preserve-whitespace/expected.html similarity index 55% rename from test/fixtures/render/autotest-pending/whitespace4/expected.html rename to test/fixtures/render/autotest/compiler-option-preserve-whitespace/expected.html index 03782e690..9febe9623 100644 --- a/test/fixtures/render/autotest-pending/whitespace4/expected.html +++ b/test/fixtures/render/autotest/compiler-option-preserve-whitespace/expected.html @@ -1,4 +1,4 @@ A -B + B C diff --git a/test/fixtures/render/autotest/compiler-option-preserve-whitespace/template.marko b/test/fixtures/render/autotest/compiler-option-preserve-whitespace/template.marko new file mode 100644 index 000000000..22ef22120 --- /dev/null +++ b/test/fixtures/render/autotest/compiler-option-preserve-whitespace/template.marko @@ -0,0 +1,4 @@ + +A + B +C diff --git a/test/fixtures/render/autotest-pending/whitespace3/test.js b/test/fixtures/render/autotest/compiler-option-preserve-whitespace/test.js similarity index 100% rename from test/fixtures/render/autotest-pending/whitespace3/test.js rename to test/fixtures/render/autotest/compiler-option-preserve-whitespace/test.js diff --git a/test/fixtures/render/autotest/marko-body-attr-static-text/expected.html b/test/fixtures/render/autotest/marko-body-attr-static-text/expected.html new file mode 100644 index 000000000..d722681df --- /dev/null +++ b/test/fixtures/render/autotest/marko-body-attr-static-text/expected.html @@ -0,0 +1 @@ +
Hello ${THIS IS NOT VALID}!
\ No newline at end of file diff --git a/test/fixtures/render/autotest/marko-body-attr-static-text/template.marko b/test/fixtures/render/autotest/marko-body-attr-static-text/template.marko new file mode 100644 index 000000000..26a7255c7 --- /dev/null +++ b/test/fixtures/render/autotest/marko-body-attr-static-text/template.marko @@ -0,0 +1,5 @@ +
+ + Hello ${THIS IS NOT VALID}! + +
\ No newline at end of file diff --git a/test/fixtures/render/autotest/marko-body-attr-static-text/test.js b/test/fixtures/render/autotest/marko-body-attr-static-text/test.js new file mode 100644 index 000000000..c128acd4a --- /dev/null +++ b/test/fixtures/render/autotest/marko-body-attr-static-text/test.js @@ -0,0 +1,7 @@ +exports.templateData = { + "myAttrs": { + "style": "background-color: #FF0000; ", + "class": "my-div", + "checked": true + } +}; \ No newline at end of file diff --git a/test/fixtures/render/autotest/marko-preserve-whitespace-attr/expected.html b/test/fixtures/render/autotest/marko-preserve-whitespace-attr/expected.html new file mode 100644 index 000000000..60b4f843c --- /dev/null +++ b/test/fixtures/render/autotest/marko-preserve-whitespace-attr/expected.html @@ -0,0 +1,4 @@ +
+ Hello + World +
\ No newline at end of file diff --git a/test/fixtures/render/autotest/marko-preserve-whitespace-attr/template.marko b/test/fixtures/render/autotest/marko-preserve-whitespace-attr/template.marko new file mode 100644 index 000000000..727f4c4f8 --- /dev/null +++ b/test/fixtures/render/autotest/marko-preserve-whitespace-attr/template.marko @@ -0,0 +1,4 @@ +
+ Hello + World +
\ No newline at end of file diff --git a/test/fixtures/render/autotest/marko-preserve-whitespace-attr/test.js b/test/fixtures/render/autotest/marko-preserve-whitespace-attr/test.js new file mode 100644 index 000000000..c128acd4a --- /dev/null +++ b/test/fixtures/render/autotest/marko-preserve-whitespace-attr/test.js @@ -0,0 +1,7 @@ +exports.templateData = { + "myAttrs": { + "style": "background-color: #FF0000; ", + "class": "my-div", + "checked": true + } +}; \ No newline at end of file diff --git a/test/fixtures/render/autotest-pending/text-replacement/expected.html b/test/fixtures/render/autotest/text-replacement/expected.html similarity index 100% rename from test/fixtures/render/autotest-pending/text-replacement/expected.html rename to test/fixtures/render/autotest/text-replacement/expected.html diff --git a/test/fixtures/render/autotest/text-replacement/template.marko b/test/fixtures/render/autotest/text-replacement/template.marko new file mode 100644 index 000000000..3af155405 --- /dev/null +++ b/test/fixtures/render/autotest/text-replacement/template.marko @@ -0,0 +1,3 @@ + + +Hello ${person.name}. You are from ${person.address.city}, ${person.address.state} Zero: ${data.zero} \ No newline at end of file diff --git a/test/fixtures/render/autotest-pending/text-replacement/test.js b/test/fixtures/render/autotest/text-replacement/test.js similarity index 100% rename from test/fixtures/render/autotest-pending/text-replacement/test.js rename to test/fixtures/render/autotest/text-replacement/test.js diff --git a/test/fixtures/render/autotest/whitespace-contentplaceholder-literal-string/expected.html b/test/fixtures/render/autotest/whitespace-contentplaceholder-literal-string/expected.html new file mode 100644 index 000000000..c1c6feb15 --- /dev/null +++ b/test/fixtures/render/autotest/whitespace-contentplaceholder-literal-string/expected.html @@ -0,0 +1 @@ +WHITE SPACE \ No newline at end of file diff --git a/test/fixtures/render/autotest/whitespace-contentplaceholder-literal-string/template.marko b/test/fixtures/render/autotest/whitespace-contentplaceholder-literal-string/template.marko new file mode 100644 index 000000000..9d93264ea --- /dev/null +++ b/test/fixtures/render/autotest/whitespace-contentplaceholder-literal-string/template.marko @@ -0,0 +1 @@ +${"WHITE SPACE"} \ No newline at end of file diff --git a/test/fixtures/render/autotest-pending/whitespace4/test.js b/test/fixtures/render/autotest/whitespace-contentplaceholder-literal-string/test.js similarity index 100% rename from test/fixtures/render/autotest-pending/whitespace4/test.js rename to test/fixtures/render/autotest/whitespace-contentplaceholder-literal-string/test.js diff --git a/test/fixtures/render/autotest-pending/whitespace/expected.html b/test/fixtures/render/autotest/whitespace/expected.html similarity index 76% rename from test/fixtures/render/autotest-pending/whitespace/expected.html rename to test/fixtures/render/autotest/whitespace/expected.html index bc5e19cf4..932d7f919 100644 --- a/test/fixtures/render/autotest-pending/whitespace/expected.html +++ b/test/fixtures/render/autotest/whitespace/expected.html @@ -1,7 +1,7 @@ BEGIN this whitespace should be retained END test hello Long paragraph of text should retain spacing between lines.
  • One
  • Two
Hello World!
-   begin      end     
+   begin      end
 
- begin end + begin end
- begin end + begin end
begin end \ No newline at end of file diff --git a/test/fixtures/render/autotest-pending/whitespace/template.marko b/test/fixtures/render/autotest/whitespace/template.marko similarity index 82% rename from test/fixtures/render/autotest-pending/whitespace/template.marko rename to test/fixtures/render/autotest/whitespace/template.marko index dc916944c..7239b5849 100644 --- a/test/fixtures/render/autotest-pending/whitespace/template.marko +++ b/test/fixtures/render/autotest/whitespace/template.marko @@ -1,7 +1,7 @@ -${"BEGIN this whitespace should be retained END"} +${"BEGIN this whitespace should be retained END"} test -${"hello"} +${"hello"} Long paragraph of text @@ -19,18 +19,18 @@ should retain spacing between l
-   begin      end     
+   begin      end
 
-
- begin end +
+ begin end
-
- begin end +
+ begin end
-begin end -end +