'use strict'; var ok = require('assert').ok; var path = require('path'); var taglibLookup = require('./taglib-lookup'); var charProps = require('char-props'); var deresolve = require('./util/deresolve'); 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'); 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 ]); } class CompileContext { constructor(src, filename, builder) { 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._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; } 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); } setFlag(name) { this._flags[name] = true; } clearFlag(name) { delete this._flags[name]; } isFlagSet(name) { return this._flags.hasOwnProperty(name); } addError(errorInfo) { if (errorInfo instanceof Node) { let node = arguments[0]; let message = arguments[1]; let code = arguments[2]; errorInfo = { node, message, code }; } else if (typeof errorInfo === 'string') { let message = arguments[0]; let code = arguments[1]; errorInfo = { message, code }; } this._errors.push(new CompileError(errorInfo, this)); } hasErrors() { return this._errors.length !== 0; } getErrors() { return this._errors; } getRequirePath(targetFilename) { return deresolve(targetFilename, this.dirname); } importModule(varName, path) { if (typeof path !== 'string') { throw new Error('"path" should be a string'); } return this.addStaticVar(varName, 'require("' + path + '")'); } 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; } getEscapeXmlAttrVar() { return this.addStaticVar('escapeXmlAttr', '__helpers.xa'); } 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; if (typeof tagName === 'object') { elDef = tagName; tagName = elDef.tagName; attributes = elDef.attributes; } else { elDef = { tagName, argument, attributes, openTagOnly, selfClosed }; } 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); var taglibLookup = this.taglibLookup; var tagDef = typeof tagName === 'string' ? taglibLookup.getTag(tagName) : null; 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; } 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:
return; } let attrDef = taglibLookup.getAttribute(tagName, attrName); if (!attrDef) { if (tagDef) { if (node.removeAttribute) { node.removeAttribute(attrName); } // var isAttrForTaglib = compiler.taglibs.isTaglib(attrUri); //Tag doesn't allow dynamic attributes this.addError({ node: node, message: 'The tag "' + tagName + '" in taglib "' + getTaglibPath(tagDef.taglibId) + '" does not support attribute "' + attrName + '"' }); } return; } if (attrDef.setFlag) { node.setFlag(attrDef.setFlag); } attr.def = attrDef; foundAttrs[attrName] = true; }); if (tagDef) { // Add default values for any attributes. If an attribute has a declared // default value and the attribute was not found on the element // then add the attribute with the specified default value tagDef.forEachAttribute((attrDef) => { var attrName = attrDef.name; if (attrDef.hasOwnProperty('defaultValue') && !foundAttrs.hasOwnProperty(attrName)) { attributes.push({ name: attrName, value: builder.literal(attrDef.defaultValue) }); } else if (attrDef.required === true) { // TODO Only throw an error if there is no data argument provided (just HTML attributes) if (!foundAttrs.hasOwnProperty(attrName)) { this.addError({ node: node, message: 'The "' + attrName + '" attribute is required for tag "' + tagName + '" in taglib "' + getTaglibPath(tagDef.taglibId) + '".' }); } } }); node.tagDef = tagDef; } return node; } isMacro(name) { if (!this._macros) { return false; } return this._macros.isMacro(name); } getRegisteredMacro(name) { if (!this._macros) { return undefined; } return this._macros.getRegisteredMacro(name); } registerMacro(name, params) { if (!this._macros) { this._macros = macros.createMacrosContext(); } return this._macros.registerMacro(name, params); } importTemplate(relativePath) { ok(typeof relativePath === 'string', '"path" should be a string'); var builder = this.builder; // We want to add the following import: // var loadTemplate = __helpers.t; // var template = loadTemplate(require.resolve()) var loadTemplateVar = this.addStaticVar('loadTemplate', '__helpers.l'); var requireResolveTemplate = requireResolve(builder, builder.literal(relativePath)); var loadFunctionCall = builder.functionCall(loadTemplateVar, [ requireResolveTemplate ]); var templateVar = this.addStaticVar(removeExt(relativePath), loadFunctionCall); return templateVar; } setPreserveWhitespace(preserveWhitespace) { this._preserveWhitespace = preserveWhitespace; } isPreserveWhitespace() { return this._preserveWhitespace === true; } setPreserveComments(preserveComments) { this._preserveComments = preserveComments; } isPreserveComments() { return this._preserveComments === true; } createWalker(options) { return new Walker(options); } /** * Statically resolves a path if it is a literal string. Otherwise, it returns the input expression. */ resolvePath(pathExpression) { ok(pathExpression, '"pathExpression" is required'); if (pathExpression.type === 'Literal') { let path = pathExpression.value; if (typeof path === 'string') { return this.addStaticVar(path, this.builder.requireResolve(pathExpression)); } } return pathExpression; } resolveTemplate(pathExpression) { ok(pathExpression, '"pathExpression" is required'); if (pathExpression.type === 'Literal') { let path = pathExpression.value; if (typeof path === 'string') { return this.importTemplate(path); } } return pathExpression; } } CompileContext.prototype.util = { isValidJavaScriptIdentifier: require('./util/isValidJavaScriptIdentifier') }; module.exports = CompileContext;