'use strict'; var ok = require('assert').ok; var path = require('path'); var complain = require('complain'); var taglibLookup = require('./taglib-lookup'); var charProps = require('char-props'); var UniqueVars = require('./util/UniqueVars'); var PosInfo = require('./util/PosInfo'); var CompileError = require('./CompileError'); var path = require('path'); var Node = require('./ast/Node'); var macros = require('./util/macros'); var extend = require('raptor-util/extend'); var Walker = require('./Walker'); var EventEmitter = require('events').EventEmitter; var utilFingerprint = require('./util/finger-print'); var htmlElements = require('./util/html-elements'); var markoModules = require('./modules'); const markoPkgVersion = require('../package.json').version; const FLAG_PRESERVE_WHITESPACE = 'PRESERVE_WHITESPACE'; function getTaglibPath(taglibPath) { if (typeof window === 'undefined') { return path.relative(process.cwd(), taglibPath); } else { return taglibPath; } } function removeExt(filename) { var ext = path.extname(filename); if (ext) { return filename.slice(0, 0 - ext.length); } else { return filename; } } function requireResolve(builder, path) { var requireResolveNode = builder.memberExpression( builder.identifier('require'), builder.identifier('resolve')); return builder.functionCall(requireResolveNode, [ path ]); } const helpers = { 'attr': 'a', 'attrs': 'as', 'classAttr': 'ca', 'classList': 'cl', 'const': 'const', 'createElement': 'e', 'createInlineTemplate': { vdom: { module: 'marko/runtime/vdom/helper-createInlineTemplate'}, html: { module: 'marko/runtime/html/helper-createInlineTemplate'} }, 'escapeXml': 'x', 'escapeXmlAttr': 'xa', 'escapeScript': 'xs', 'escapeStyle': 'xc', 'forEach': 'f', 'forEachProp': { module: 'marko/runtime/helper-forEachProperty' }, 'forEachPropStatusVar': { module: 'marko/runtime/helper-forEachPropStatusVar' }, 'forEachWithStatusVar': { module: 'marko/runtime/helper-forEachWithStatusVar' }, 'forRange': { module: 'marko/runtime/helper-forRange' }, 'include': 'i', 'loadNestedTag': { module: 'marko/runtime/helper-loadNestedTag' }, 'loadTag': 't', 'loadTemplate': { module: 'marko/runtime/helper-loadTemplate' }, 'mergeNestedTagsHelper': { module: 'marko/runtime/helper-mergeNestedTags' }, 'merge': { module: 'marko/runtime/helper-merge' }, 'renderComponent': { module: 'marko/components/taglib/helpers/renderComponent' }, 'str': 's', 'styleAttr': { vdom: { module: 'marko/runtime/vdom/helper-styleAttr'}, html: 'sa' }, 'createText': 't' }; class CompileContext extends EventEmitter { constructor(src, filename, builder, options) { super(); ok(typeof src === 'string', '"src" string is required'); ok(filename, '"filename" is required'); this.src = src; this.filename = filename; this.builder = builder; this.dirname = path.dirname(filename); this.taglibLookup = taglibLookup.buildLookup(this.dirname); this.data = {}; this._dataStacks = {}; this.meta = {}; this.options = options || {}; const writeVersionComment = this.options.writeVersionComment; this.outputType = this.options.output || 'html'; this.compilerType = this.options.compilerType || 'marko'; this.compilerVersion = this.options.compilerVersion || markoPkgVersion; this.writeVersionComment = writeVersionComment !== 'undefined' ? writeVersionComment : true; this._vars = {}; this._uniqueVars = new UniqueVars(); this._staticVars = {}; this._staticCode = null; this._uniqueStaticVars = new UniqueVars(); this._srcCharProps = null; this._flags = {}; this._errors = []; this._macros = null; this._preserveWhitespace = null; this._preserveComments = null; this.inline = this.options.inline === true; this.useMeta = this.options.meta !== false; this._moduleRuntimeTarget = this.outputType === 'vdom' ? 'marko/vdom' : 'marko/html'; this.unrecognizedTags = []; this._parsingFinished = false; this._helpersIdentifier = null; if (this.options.preserveWhitespace) { this.setPreserveWhitespace(true); } this._helpers = {}; this._imports = {}; this._fingerprint = undefined; this._optimizers = undefined; } setInline(isInline) { this.inline = isInline === true; } getPosInfo(pos) { var srcCharProps = this._srcCharProps || (this._srcCharProps = charProps(this.src)); let line = srcCharProps.lineAt(pos)+1; let column = srcCharProps.columnAt(pos); return new PosInfo(this.filename, line, column); } getNodePos(node) { if (node.pos) { return this.getPosInfo(node.pos); } else { return new PosInfo(this.filename); } } setFlag(name) { this.pushFlag(name); } clearFlag(name) { delete this._flags[name]; } isFlagSet(name) { return this._flags.hasOwnProperty(name); } pushFlag(name) { if (this._flags.hasOwnProperty(name)) { this._flags[name]++; } else { this._flags[name] = 1; } } popFlag(name) { if (!this._flags.hasOwnProperty(name)) { throw new Error('popFlag() called for "' + name + '" when flag was not set'); } if (--this._flags[name] === 0) { delete this._flags[name]; } } pushData(key, data) { var dataStack = this._dataStacks[key]; if (!dataStack) { dataStack = this._dataStacks[key] = []; } dataStack.push(data); return { pop: () => { this.popData(key); } }; } popData(key) { var dataStack = this._dataStacks[key]; if (!dataStack || dataStack.length === 0) { throw new Error('No data pushed for "' + key + '"'); } dataStack.pop(); if (dataStack.length === 0) { delete this.data[key]; } } getData(name) { var dataStack = this._dataStacks[name]; if (dataStack) { return dataStack[dataStack.length - 1]; } return this.data[name]; } deprecate(message, node) { var currentNode = node || this._currentNode; var location = currentNode && currentNode.pos; if (location != null) { location = this.getPosInfo(location).toString(); } complain(message, { location }); } addError(errorInfo) { if (errorInfo instanceof Node) { let node = arguments[0]; let message = arguments[1]; let code = arguments[2]; let pos = arguments[3]; errorInfo = { node, message, code, pos }; } else if (typeof errorInfo === 'string') { let message = arguments[0]; let code = arguments[1]; let pos = arguments[2]; errorInfo = { message, code, pos }; } if(errorInfo && !errorInfo.node) { errorInfo.node = this._currentNode; } this._errors.push(new CompileError(errorInfo, this)); } hasErrors() { return this._errors.length !== 0; } getErrors() { return this._errors; } getRequirePath(targetFilename) { return markoModules.deresolve(targetFilename, this.dirname); } importModule(varName, path) { if (typeof path !== 'string') { throw new Error('"path" should be a string'); } var varId = this._imports[path]; if (!varId) { var builder = this.builder; var requireFuncCall = this.builder.require(builder.literal(path)); this._imports[path] = varId = this.addStaticVar(varName, requireFuncCall); } return varId; } addVar(name, init) { var actualVarName = this._uniqueVars.addVar(name, init); this._vars[actualVarName] = init; return this.builder.identifier(actualVarName); } getVars() { return this._vars; } addStaticVar(name, init) { var actualVarName = this._uniqueStaticVars.addVar(name, init); this._staticVars[actualVarName] = init; return this.builder.identifier(actualVarName); } getStaticVars() { return this._staticVars; } addStaticCode(code) { if (!code) { return; } if (typeof code === 'string') { // Wrap the String code in a Code AST node so that // the code will be indented properly code = this.builder.code(code); } if (this._staticCode == null) { this._staticCode = [code]; } else { this._staticCode.push(code); } } getStaticCode() { 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); } } 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; if (typeof tagName === 'object') { elDef = tagName; tagName = elDef.tagName; attributes = elDef.attributes; } else { elDef = { tagName, argument, attributes, openTagOnly, selfClosed }; } if (elDef.tagName === '') { elDef.tagName = tagName = 'assign'; } if (!attributes) { attributes = elDef.attributes = []; } else if (typeof attributes === 'object') { if (!Array.isArray(attributes)) { attributes = elDef.attributes = Object.keys(attributes).map((attrName) => { var attrDef = { name: attrName }; var val = attributes[attrName]; if (val == null) { } if (val instanceof Node) { attrDef.value = val; } else { extend(attrDef, val); } return attrDef; }); } } else { throw new Error('Invalid attributes'); } var node; var elNode = builder.htmlElement(elDef); elNode.pos = elDef.pos; this._currentNode = elNode; var tagDef; var taglibLookup = this.taglibLookup; if (typeof tagName === 'string' && tagName.startsWith('@')) { // NOTE: The tag definition can't be determined now since it will be // determined by the parent custom tag. node = builder.customTag(elNode); node.body = node.makeContainer(node.body.items); } else { 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) { var newNode = nodeFactoryFunc(elNode, this); if (!(newNode instanceof Node)) { throw new Error('Invalid node returned from node factory for tag "' + tagName + '".'); } if (newNode != node) { // Make sure the body container is associated with the correct node if (newNode.body && newNode.body !== node) { newNode.body = newNode.makeContainer(newNode.body.items); } node = newNode; } } } if (!node) { node = elNode; } } if (tagDef && tagDef.noOutput) { node.noOutput = true; } node.pos = elDef.pos; var foundAttrs = {}; // Validate the attributes attributes.forEach((attr) => { let attrName = attr.name; if (!attrName) { // Attribute will be name for placeholder attributes. For example: