/* Riot Compiler, @license MIT */
import { types as types$2, parse as parse$1, print } from 'recast';
import { builtin } from 'globals/globals.json';
import { parse as parse$2 } from 'recast/parsers/typescript';
import { composeSourceMaps } from 'recast/lib/util';
import { SourceMapGenerator } from 'source-map';
import cssEscape from 'cssesc';

const TAG_LOGIC_PROPERTY = 'exports';
const TAG_CSS_PROPERTY = 'css';
const TAG_TEMPLATE_PROPERTY = 'template';
const TAG_NAME_PROPERTY = 'name';
const RIOT_MODULE_ID = 'riot';
const RIOT_INTERFACE_WRAPPER_NAME = 'RiotComponentWrapper';
const RIOT_TAG_INTERFACE_NAME = 'RiotComponent';

const JAVASCRIPT_OUTPUT_NAME = 'javascript';
const CSS_OUTPUT_NAME = 'css';
const TEMPLATE_OUTPUT_NAME = 'template';

// Tag names
const JAVASCRIPT_TAG = 'script';
const STYLE_TAG = 'style';
const TEXTAREA_TAG = 'textarea';

// Boolean attributes
const IS_RAW = 'isRaw';
const IS_SELF_CLOSING = 'isSelfClosing';
const IS_VOID = 'isVoid';
const IS_BOOLEAN = 'isBoolean';
const IS_CUSTOM = 'isCustom';
const IS_SPREAD = 'isSpread';

var c = /*#__PURE__*/Object.freeze({
  __proto__: null,
  JAVASCRIPT_OUTPUT_NAME: JAVASCRIPT_OUTPUT_NAME,
  CSS_OUTPUT_NAME: CSS_OUTPUT_NAME,
  TEMPLATE_OUTPUT_NAME: TEMPLATE_OUTPUT_NAME,
  JAVASCRIPT_TAG: JAVASCRIPT_TAG,
  STYLE_TAG: STYLE_TAG,
  TEXTAREA_TAG: TEXTAREA_TAG,
  IS_RAW: IS_RAW,
  IS_SELF_CLOSING: IS_SELF_CLOSING,
  IS_VOID: IS_VOID,
  IS_BOOLEAN: IS_BOOLEAN,
  IS_CUSTOM: IS_CUSTOM,
  IS_SPREAD: IS_SPREAD
});

/**
 * Not all the types are handled in this module.
 *
 * @enum {number}
 * @readonly
 */
const TAG = 1; /* TAG */
const ATTR = 2; /* ATTR */
const TEXT = 3; /* TEXT */
const CDATA = 4; /* CDATA */
const COMMENT = 8; /* COMMENT */
const DOCUMENT = 9; /* DOCUMENT */
const DOCTYPE = 10; /* DOCTYPE */
const DOCUMENT_FRAGMENT = 11; /* DOCUMENT_FRAGMENT */

var types$1 = /*#__PURE__*/Object.freeze({
  __proto__: null,
  TAG: TAG,
  ATTR: ATTR,
  TEXT: TEXT,
  CDATA: CDATA,
  COMMENT: COMMENT,
  DOCUMENT: DOCUMENT,
  DOCTYPE: DOCTYPE,
  DOCUMENT_FRAGMENT: DOCUMENT_FRAGMENT
});

const rootTagNotFound = 'Root tag not found.';
const unclosedTemplateLiteral = 'Unclosed ES6 template literal.';
const unexpectedEndOfFile = 'Unexpected end of file.';
const unclosedComment = 'Unclosed comment.';
const unclosedNamedBlock = 'Unclosed "%1" block.';
const duplicatedNamedTag = 'Multiple inline "<%1>" tags are not supported.';
const unexpectedCharInExpression = 'Unexpected character %1.';
const unclosedExpression = 'Unclosed expression.';

/**
 * Matches the start of valid tags names; used with the first 2 chars after the `'<'`.
 * @const
 * @private
 */
const TAG_2C = /^(?:\/[a-zA-Z]|[a-zA-Z][^\s>/]?)/;
/**
 * Matches valid tags names AFTER the validation with `TAG_2C`.
 * $1: tag name including any `'/'`, $2: non self-closing brace (`>`) w/o attributes.
 * @const
 * @private
 */
const TAG_NAME = /(\/?[^\s>/]+)\s*(>)?/g;
/**
 * Matches an attribute name-value pair (both can be empty).
 * $1: attribute name, $2: value including any quotes.
 * @const
 * @private
 */
const ATTR_START = /(\S[^>/=\s]*)(?:\s*=\s*([^>/])?)?/g;

/**
 * Matches the spread operator
 * it will be used for the spread attributes
 * @type {RegExp}
 */
const SPREAD_OPERATOR = /\.\.\./;
/**
 * Matches the closing tag of a `script` and `style` block.
 * Used by parseText fo find the end of the block.
 * @const
 * @private
 */
const RE_SCRYLE = {
  script: /<\/script\s*>/gi,
  style: /<\/style\s*>/gi,
  textarea: /<\/textarea\s*>/gi
};

// Do not touch text content inside this tags
const RAW_TAGS = /^\/?(?:pre|textarea)$/;

/**
 * Add an item into a collection, if the collection is not an array
 * we create one and add the item to it
 * @param   {Array} collection - target collection
 * @param   {*} item - item to add to the collection
 * @returns {Array} array containing the new item added to it
 */
function addToCollection(collection = [], item) {
  collection.push(item);
  return collection
}

/**
 * Run RegExp.exec starting from a specific position
 * @param   {RegExp} re - regex
 * @param   {number} pos - last index position
 * @param   {string} string - regex target
 * @returns {Array} regex result
 */
function execFromPos(re, pos, string) {
  re.lastIndex = pos;
  return re.exec(string)
}

/**
 * Escape special characters in a given string, in preparation to create a regex.
 *
 * @param   {string} str - Raw string
 * @returns {string} Escaped string.
 */
var escapeStr = (str) => str.replace(/(?=[-[\](){^*+?.$|\\])/g, '\\');

function formatError(data, message, pos) {
  if (!pos) {
    pos = data.length;
  }
  // count unix/mac/win eols
  const line = (data.slice(0, pos).match(/\r\n?|\n/g) || '').length + 1;
  let col = 0;
  while (--pos >= 0 && !/[\r\n]/.test(data[pos])) {
    ++col;
  }
  return `[${line},${col}]: ${message}`
}

const $_ES6_BQ = '`';

/**
 * Searches the next backquote that signals the end of the ES6 Template Literal
 * or the "${" sequence that starts a JS expression, skipping any escaped
 * character.
 *
 * @param   {string}    code  - Whole code
 * @param   {number}    pos   - The start position of the template
 * @param   {string[]}  stack - To save nested ES6 TL count
 * @returns {number}    The end of the string (-1 if not found)
 */
function skipES6TL(code, pos, stack) {
  // we are in the char following the backquote (`),
  // find the next unescaped backquote or the sequence "${"
  const re = /[`$\\]/g;
  let c;
  while (re.lastIndex = pos, re.exec(code)) {
    pos = re.lastIndex;
    c = code[pos - 1];
    if (c === '`') {
      return pos
    }
    if (c === '$' && code[pos++] === '{') {
      stack.push($_ES6_BQ, '}');
      return pos
    }
    // else this is an escaped char
  }
  throw formatError(code, unclosedTemplateLiteral, pos)
}

/**
 * Custom error handler can be implemented replacing this method.
 * The `state` object includes the buffer (`data`)
 * The error position (`loc`) contains line (base 1) and col (base 0).
 * @param {string} data - string containing the error
 * @param {string} msg - Error message
 * @param {number} pos - Position of the error
 * @returns {undefined} throw an exception error
 */
function panic$1(data, msg, pos) {
  const message = formatError(data, msg, pos);
  throw new Error(message)
}

// forked from https://github.com/aMarCruz/skip-regex

// safe characters to precced a regex (including `=>`, `**`, and `...`)
const beforeReChars = '[{(,;:?=|&!^~>%*/';
const beforeReSign = `${beforeReChars}+-`;

// keyword that can preceed a regex (`in` is handled as special case)
const beforeReWords = [
  'case',
  'default',
  'do',
  'else',
  'in',
  'instanceof',
  'prefix',
  'return',
  'typeof',
  'void',
  'yield'
];

// Last chars of all the beforeReWords elements to speed up the process.
const wordsEndChar = beforeReWords.reduce((s, w) => s + w.slice(-1), '');

// Matches literal regex from the start of the buffer.
// The buffer to search must not include line-endings.
const RE_LIT_REGEX = /^\/(?=[^*>/])[^[/\\]*(?:(?:\\.|\[(?:\\.|[^\]\\]*)*\])[^[\\/]*)*?\/[gimuy]*/;

// Valid characters for JavaScript variable names and literal numbers.
const RE_JS_VCHAR = /[$\w]/;

// Match dot characters that could be part of tricky regex
const RE_DOT_CHAR = /.*/g;

/**
 * Searches the position of the previous non-blank character inside `code`,
 * starting with `pos - 1`.
 *
 * @param   {string} code - Buffer to search
 * @param   {number} pos  - Starting position
 * @returns {number} Position of the first non-blank character to the left.
 * @private
 */
function _prev(code, pos) {
  while (--pos >= 0 && /\s/.test(code[pos]));
  return pos
}



/**
 * Check if the character in the `start` position within `code` can be a regex
 * and returns the position following this regex or `start+1` if this is not
 * one.
 *
 * NOTE: Ensure `start` points to a slash (this is not checked).
 *
 * @function skipRegex
 * @param   {string} code  - Buffer to test in
 * @param   {number} start - Position the first slash inside `code`
 * @returns {number} Position of the char following the regex.
 *
 */
/* istanbul ignore next */
function skipRegex(code, start) {
  let pos = RE_DOT_CHAR.lastIndex = start++;

  // `exec()` will extract from the slash to the end of the line
  //   and the chained `match()` will match the possible regex.
  const match = (RE_DOT_CHAR.exec(code) || ' ')[0].match(RE_LIT_REGEX);

  if (match) {
    const next = pos + match[0].length;      // result comes from `re.match`

    pos = _prev(code, pos);
    let c = code[pos];

    // start of buffer or safe prefix?
    if (pos < 0 || beforeReChars.includes(c)) {
      return next
    }

    // from here, `pos` is >= 0 and `c` is code[pos]
    if (c === '.') {
      // can be `...` or something silly like 5./2
      if (code[pos - 1] === '.') {
        start = next;
      }

    } else {

      if (c === '+' || c === '-') {
        // tricky case
        if (code[--pos] !== c ||            // if have a single operator or
             (pos = _prev(code, pos)) < 0 ||  // ...have `++` and no previous token
             beforeReSign.includes(c = code[pos])) {
          return next                       // ...this is a regex
        }
      }

      if (wordsEndChar.includes(c)) {  // looks like a keyword?
        const end = pos + 1;

        // get the complete (previous) keyword
        while (--pos >= 0 && RE_JS_VCHAR.test(code[pos]));

        // it is in the allowed keywords list?
        if (beforeReWords.includes(code.slice(pos + 1, end))) {
          start = next;
        }
      }
    }
  }

  return start
}

/*
 * Mini-parser for expressions.
 * The main pourpose of this module is to find the end of an expression
 * and return its text without the enclosing brackets.
 * Does not works with comments, but supports ES6 template strings.
 */
/**
 * @exports exprExtr
 */
const S_SQ_STR = /'[^'\n\r\\]*(?:\\(?:\r\n?|[\S\s])[^'\n\r\\]*)*'/.source;
/**
 * Matches double quoted JS strings taking care about nested quotes
 * and EOLs (escaped EOLs are Ok).
 *
 * @const
 * @private
 */
const S_STRING = `${S_SQ_STR}|${S_SQ_STR.replace(/'/g, '"')}`;
/**
 * Regex cache
 *
 * @type {Object.<string, RegExp>}
 * @const
 * @private
 */
const reBr = {};
/**
 * Makes an optimal regex that matches quoted strings, brackets, backquotes
 * and the closing brackets of an expression.
 *
 * @param   {string} b - Closing brackets
 * @returns {RegExp} - optimized regex
 */
function _regex(b) {
  let re = reBr[b];
  if (!re) {
    let s = escapeStr(b);
    if (b.length > 1) {
      s = `${s}|[`;
    } else {
      s = /[{}[\]()]/.test(b) ? '[' : `[${s}`;
    }
    reBr[b] = re = new RegExp(`${S_STRING}|${s}\`/\\{}[\\]()]`, 'g');
  }
  return re
}

/**
 * Update the scopes stack removing or adding closures to it
 * @param   {Array} stack - array stacking the expression closures
 * @param   {string} char - current char to add or remove from the stack
 * @param   {string} idx  - matching index
 * @param   {string} code - expression code
 * @returns {Object} result
 * @returns {Object} result.char - either the char received or the closing braces
 * @returns {Object} result.index - either a new index to skip part of the source code,
 *                                  or 0 to keep from parsing from the old position
 */
function updateStack(stack, char, idx, code) {
  let index = 0;

  switch (char) {
  case '[':
  case '(':
  case '{':
    stack.push(char === '[' ? ']' : char === '(' ? ')' : '}');
    break
  case ')':
  case ']':
  case '}':
    if (char !== stack.pop()) {
      panic$1(code, unexpectedCharInExpression.replace('%1', char), index);
    }

    if (char === '}' && stack[stack.length - 1] === $_ES6_BQ) {
      char = stack.pop();
    }

    index = idx + 1;
    break
  case '/':
    index = skipRegex(code, idx);
  }

  return { char, index }
}

/**
   * Parses the code string searching the end of the expression.
   * It skips braces, quoted strings, regexes, and ES6 template literals.
   *
   * @function exprExtr
   * @param   {string}  code  - Buffer to parse
   * @param   {number}  start - Position of the opening brace
   * @param   {[string,string]} bp - Brackets pair
   * @returns {Object} Expression's end (after the closing brace) or -1
   *                            if it is not an expr.
   */
function exprExtr(code, start, bp) {
  const [openingBraces, closingBraces] = bp;
  const offset = start + openingBraces.length; // skips the opening brace
  const stack = []; // expected closing braces ('`' for ES6 TL)
  const re = _regex(closingBraces);

  re.lastIndex = offset; // begining of the expression

  let end;
  let match;

  while (match = re.exec(code)) { // eslint-disable-line
    const idx = match.index;
    const str = match[0];
    end = re.lastIndex;

    // end the iteration
    if (str === closingBraces && !stack.length) {
      return {
        text: code.slice(offset, idx),
        start,
        end
      }
    }

    const { char, index } = updateStack(stack, str[0], idx, code);
    // update the end value depending on the new index received
    end = index || end;
    // update the regex last index
    re.lastIndex = char === $_ES6_BQ ? skipES6TL(code, end, stack) : end;
  }

  if (stack.length) {
    panic$1(code, unclosedExpression, end);
  }
}

/**
 * Outputs the last parsed node. Can be used with a builder too.
 *
 * @param   {ParserStore} store - Parsing store
 * @returns {undefined} void function
 * @private
 */
function flush(store) {
  const last = store.last;
  store.last = null;
  if (last && store.root) {
    store.builder.push(last);
  }
}

/**
 * Get the code chunks from start and end range
 * @param   {string}  source  - source code
 * @param   {number}  start   - Start position of the chunk we want to extract
 * @param   {number}  end     - Ending position of the chunk we need
 * @returns {string}  chunk of code extracted from the source code received
 * @private
 */
function getChunk(source, start, end) {
  return source.slice(start, end)
}

/**
 * states text in the last text node, or creates a new one if needed.
 *
 * @param {ParserState}   state   - Current parser state
 * @param {number}  start   - Start position of the tag
 * @param {number}  end     - Ending position (last char of the tag)
 * @param {Object}  extra   - extra properties to add to the text node
 * @param {RawExpr[]} extra.expressions  - Found expressions
 * @param {string}    extra.unescape     - Brackets to unescape
 * @returns {undefined} - void function
 * @private
 */
function pushText(state, start, end, extra = {}) {
  const text = getChunk(state.data, start, end);
  const expressions = extra.expressions;
  const unescape = extra.unescape;

  let q = state.last;
  state.pos = end;

  if (q && q.type === TEXT) {
    q.text += text;
    q.end = end;
  } else {
    flush(state);
    state.last = q = { type: TEXT, text, start, end };
  }

  if (expressions && expressions.length) {
    q.expressions = (q.expressions || []).concat(expressions);
  }

  if (unescape) {
    q.unescape = unescape;
  }

  return TEXT
}

/**
 * Find the end of the attribute value or text node
 * Extract expressions.
 * Detect if value have escaped brackets.
 *
 * @param   {ParserState} state  - Parser state
 * @param   {HasExpr} node       - Node if attr, info if text
 * @param   {string} endingChars - Ends the value or text
 * @param   {number} start       - Starting position
 * @returns {number} Ending position
 * @private
 */
function expr(state, node, endingChars, start) {
  const re = b0re(state, endingChars);

  re.lastIndex = start; // reset re position

  const { unescape, expressions, end } = parseExpressions(state, re);

  if (node) {
    if (unescape) {
      node.unescape = unescape;
    }
    if (expressions.length) {
      node.expressions = expressions;
    }
  } else {
    pushText(state, start, end, {expressions, unescape});
  }

  return end
}

/**
 * Parse a text chunk finding all the expressions in it
 * @param   {ParserState} state  - Parser state
 * @param   {RegExp} re - regex to match the expressions contents
 * @returns {Object} result containing the expression found, the string to unescape and the end position
 */
function parseExpressions(state, re) {
  const { data, options } = state;
  const { brackets } = options;
  const expressions = [];
  let unescape, pos, match;

  // Anything captured in $1 (closing quote or character) ends the loop...
  while ((match = re.exec(data)) && !match[1]) {
    // ...else, we have an opening bracket and maybe an expression.
    pos = match.index;
    if (data[pos - 1] === '\\') {
      unescape = match[0]; // it is an escaped opening brace
    } else {
      const tmpExpr = exprExtr(data, pos, brackets);
      if (tmpExpr) {
        expressions.push(tmpExpr);
        re.lastIndex = tmpExpr.end;
      }
    }
  }

  // Even for text, the parser needs match a closing char
  if (!match) {
    panic$1(data, unexpectedEndOfFile, pos);
  }

  return {
    unescape,
    expressions,
    end: match.index
  }
}



/**
 * Creates a regex for the given string and the left bracket.
 * The string is captured in $1.
 *
 * @param   {ParserState} state  - Parser state
 * @param   {string} str - String to search
 * @returns {RegExp} Resulting regex.
 * @private
 */
function b0re(state, str) {
  const { brackets } = state.options;
  const re = state.regexCache[str];

  if (re) return re

  const b0 = escapeStr(brackets[0]);
  // cache the regex extending the regexCache object
  Object.assign(state.regexCache, { [str]: new RegExp(`(${str})|${b0}`, 'g') });

  return state.regexCache[str]
}

// similar to _.uniq
const uniq = l => l.filter((x, i, a) => a.indexOf(x) === i);

/**
 * SVG void elements that cannot be auto-closed and shouldn't contain child nodes.
 * @const {Array}
 */
const VOID_SVG_TAGS_LIST = [
  'circle',
  'ellipse',
  'line',
  'path',
  'polygon',
  'polyline',
  'rect',
  'stop',
  'use'
];

/**
 * List of html elements where the value attribute is allowed
 * @type {Array}
 */
const HTML_ELEMENTS_HAVING_VALUE_ATTRIBUTE_LIST = [
  'button',
  'data',
  'input',
  'select',
  'li',
  'meter',
  'option',
  'output',
  'progress',
  'textarea',
  'param'
];

/**
 * List of all the available svg tags
 * @const {Array}
 * @see {@link https://github.com/wooorm/svg-tag-names}
 */
const SVG_TAGS_LIST = uniq([
  'a',
  'altGlyph',
  'altGlyphDef',
  'altGlyphItem',
  'animate',
  'animateColor',
  'animateMotion',
  'animateTransform',
  'animation',
  'audio',
  'canvas',
  'clipPath',
  'color-profile',
  'cursor',
  'defs',
  'desc',
  'discard',
  'feBlend',
  'feColorMatrix',
  'feComponentTransfer',
  'feComposite',
  'feConvolveMatrix',
  'feDiffuseLighting',
  'feDisplacementMap',
  'feDistantLight',
  'feDropShadow',
  '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',
  'handler',
  'hatch',
  'hatchpath',
  'hkern',
  'iframe',
  'image',
  'linearGradient',
  'listener',
  'marker',
  'mask',
  'mesh',
  'meshgradient',
  'meshpatch',
  'meshrow',
  'metadata',
  'missing-glyph',
  'mpath',
  'pattern',
  'prefetch',
  'radialGradient',
  'script',
  'set',
  'solidColor',
  'solidcolor',
  'style',
  'svg',
  'switch',
  'symbol',
  'tbreak',
  'text',
  'textArea',
  'textPath',
  'title',
  'tref',
  'tspan',
  'unknown',
  'video',
  'view',
  'vkern'
].concat(VOID_SVG_TAGS_LIST)).sort();

/**
 * HTML void elements that cannot be auto-closed and shouldn't contain child nodes.
 * @type {Array}
 * @see   {@link http://www.w3.org/TR/html-markup/syntax.html#syntax-elements}
 * @see   {@link http://www.w3.org/TR/html5/syntax.html#void-elements}
 */
const VOID_HTML_TAGS_LIST = [
  'area',
  'base',
  'br',
  'col',
  'embed',
  'hr',
  'img',
  'input',
  'keygen',
  'link',
  'menuitem',
  'meta',
  'param',
  'source',
  'track',
  'wbr'
];

/**
 * List of all the html tags
 * @const {Array}
 * @see {@link https://github.com/sindresorhus/html-tags}
 */
const HTML_TAGS_LIST = uniq([
  'a',
  'abbr',
  'address',
  'article',
  'aside',
  'audio',
  'b',
  'bdi',
  'bdo',
  'blockquote',
  'body',
  'canvas',
  'caption',
  'cite',
  'code',
  'colgroup',
  'datalist',
  'dd',
  'del',
  'details',
  'dfn',
  'dialog',
  'div',
  'dl',
  'dt',
  'em',
  'fieldset',
  'figcaption',
  'figure',
  'footer',
  'form',
  'h1',
  'h2',
  'h3',
  'h4',
  'h5',
  'h6',
  'head',
  'header',
  'hgroup',
  'html',
  'i',
  'iframe',
  'ins',
  'kbd',
  'label',
  'legend',
  'main',
  'map',
  'mark',
  'math',
  'menu',
  'nav',
  'noscript',
  'object',
  'ol',
  'optgroup',
  'p',
  'picture',
  'pre',
  'q',
  'rb',
  'rp',
  'rt',
  'rtc',
  'ruby',
  's',
  'samp',
  'script',
  'section',
  'select',
  'slot',
  'small',
  'span',
  'strong',
  'style',
  'sub',
  'summary',
  'sup',
  'svg',
  'table',
  'tbody',
  'td',
  'template',
  'tfoot',
  'th',
  'thead',
  'time',
  'title',
  'tr',
  'u',
  'ul',
  'var',
  'video'
]
  .concat(VOID_HTML_TAGS_LIST)
  .concat(HTML_ELEMENTS_HAVING_VALUE_ATTRIBUTE_LIST)
).sort();

/**
 * List of all boolean HTML attributes
 * @const {RegExp}
 * @see {@link https://www.w3.org/TR/html5/infrastructure.html#sec-boolean-attributes}
 */
const BOOLEAN_ATTRIBUTES_LIST = [
  'disabled',
  'visible',
  'checked',
  'readonly',
  'required',
  'allowfullscreen',
  'autofocus',
  'autoplay',
  'compact',
  'controls',
  'default',
  'formnovalidate',
  'hidden',
  'ismap',
  'itemscope',
  'loop',
  'multiple',
  'muted',
  'noresize',
  'noshade',
  'novalidate',
  'nowrap',
  'open',
  'reversed',
  'seamless',
  'selected',
  'sortable',
  'truespeed',
  'typemustmatch'
];

/**
 * Join a list of items with the pipe symbol (usefull for regex list concatenation)
 * @private
 * @param   {Array} list - list of strings
 * @returns {string} the list received joined with pipes
 */
function joinWithPipe(list) {
  return list.join('|')
}

/**
 * Convert list of strings to regex in order to test against it ignoring the cases
 * @private
 * @param   {...Array} lists - array of strings
 * @returns {RegExp} regex that will match all the strings in the array received ignoring the cases
 */
function listsToRegex(...lists) {
  return new RegExp(`^/?(?:${joinWithPipe(lists.map(joinWithPipe))})$`, 'i')
}

/**
 * Regex matching all the html tags ignoring the cases
 * @const {RegExp}
 */
const HTML_TAGS_RE = listsToRegex(HTML_TAGS_LIST);

/**
 * Regex matching all the svg tags ignoring the cases
 * @const {RegExp}
 */
const SVG_TAGS_RE = listsToRegex(SVG_TAGS_LIST);

/**
 * Regex matching all the void html tags ignoring the cases
 * @const {RegExp}
 */
const VOID_HTML_TAGS_RE =  listsToRegex(VOID_HTML_TAGS_LIST);

/**
 * Regex matching all the void svg tags ignoring the cases
 * @const {RegExp}
 */
const VOID_SVG_TAGS_RE =  listsToRegex(VOID_SVG_TAGS_LIST);

/**
 * Regex matching all the html tags where the value tag is allowed
 * @const {RegExp}
 */
const HTML_ELEMENTS_HAVING_VALUE_ATTRIBUTE_RE = listsToRegex(HTML_ELEMENTS_HAVING_VALUE_ATTRIBUTE_LIST);

/**
 * Regex matching all the boolean attributes
 * @const {RegExp}
 */
const BOOLEAN_ATTRIBUTES_RE =  listsToRegex(BOOLEAN_ATTRIBUTES_LIST);

/**
 * True if it's a self closing tag
 * @param   {string}  tag - test tag
 * @returns {boolean} true if void
 * @example
 * isVoid('meta') // true
 * isVoid('circle') // true
 * isVoid('IMG') // true
 * isVoid('div') // false
 * isVoid('mask') // false
 */
function isVoid(tag) {
  return [
    VOID_HTML_TAGS_RE,
    VOID_SVG_TAGS_RE
  ].some(r => r.test(tag))
}

/**
 * True if it's not SVG nor a HTML known tag
 * @param   {string}  tag - test tag
 * @returns {boolean} true if custom element
 * @example
 * isCustom('my-component') // true
 * isCustom('div') // false
 */
function isCustom(tag) {
  return [
    HTML_TAGS_RE,
    SVG_TAGS_RE
  ].every(l => !l.test(tag))
}

/**
 * True if the value attribute is allowed on this tag
 * @param   {string}  tag - test tag
 * @returns {boolean} true if the value attribute is allowed
 * @example
 * hasValueAttribute('input') // true
 * hasValueAttribute('div') // false
 */
function hasValueAttribute(tag) {
  return HTML_ELEMENTS_HAVING_VALUE_ATTRIBUTE_RE.test(tag)
}

/**
 * True if it's a boolean attribute
 * @param   {string} attribute - test attribute
 * @returns {boolean} true if the attribute is a boolean type
 * @example
 * isBoolAttribute('selected') // true
 * isBoolAttribute('class') // false
 */
function isBoolAttribute(attribute) {
  return BOOLEAN_ATTRIBUTES_RE.test(attribute)
}

/**
 * Memoization function
 * @param   {Function} fn - function to memoize
 * @returns {*} return of the function to memoize
 */
function memoize(fn) {
  const cache = new WeakMap();

  return (...args) => {
    if (cache.has(args[0])) return cache.get(args[0])

    const ret = fn(...args);

    cache.set(args[0], ret);

    return ret
  }
}

const expressionsContentRe = memoize(brackets => RegExp(`(${brackets[0]}[^${brackets[1]}]*?${brackets[1]})`, 'g'));
const isSpreadAttribute$1 = name => SPREAD_OPERATOR.test(name);
const isAttributeExpression = (name, brackets) => name[0] === brackets[0];
const getAttributeEnd = (state, attr) => expr(state, attr, '[>/\\s]', attr.start);

/**
 * The more complex parsing is for attributes as it can contain quoted or
 * unquoted values or expressions.
 *
 * @param   {ParserStore} state  - Parser state
 * @returns {number} New parser mode.
 * @private
 */
function attr(state) {
  const { data, last, pos, root } = state;
  const tag = last; // the last (current) tag in the output
  const _CH = /\S/g; // matches the first non-space char
  const ch = execFromPos(_CH, pos, data);

  switch (true) {
  case !ch:
    state.pos = data.length; // reaching the end of the buffer with
    // NodeTypes.ATTR will generate error
    break
  case ch[0] === '>':
    // closing char found. If this is a self-closing tag with the name of the
    // Root tag, we need decrement the counter as we are changing mode.
    state.pos = tag.end = _CH.lastIndex;
    if (tag[IS_SELF_CLOSING]) {
      state.scryle = null; // allow selfClosing script/style tags
      if (root && root.name === tag.name) {
        state.count--; // "pop" root tag
      }
    }
    return TEXT
  case ch[0] === '/':
    state.pos = _CH.lastIndex; // maybe. delegate the validation
    tag[IS_SELF_CLOSING] = true; // the next loop
    break
  default:
    delete tag[IS_SELF_CLOSING]; // ensure unmark as selfclosing tag
    setAttribute(state, ch.index, tag);
  }

  return ATTR
}

/**
 * Parses an attribute and its expressions.
 *
 * @param   {ParserStore}  state  - Parser state
 * @param   {number} pos    - Starting position of the attribute
 * @param   {Object} tag    - Current parent tag
 * @returns {undefined} void function
 * @private
 */
function setAttribute(state, pos, tag) {
  const { data } = state;
  const expressionContent = expressionsContentRe(state.options.brackets);
  const re = ATTR_START; // (\S[^>/=\s]*)(?:\s*=\s*([^>/])?)? g
  const start = re.lastIndex = expressionContent.lastIndex = pos; // first non-whitespace
  const attrMatches = re.exec(data);
  const isExpressionName = isAttributeExpression(attrMatches[1], state.options.brackets);
  const match = isExpressionName ? [null, expressionContent.exec(data)[1], null] : attrMatches;

  if (match) {
    const end = re.lastIndex;
    const attr = parseAttribute(state, match, start, end, isExpressionName);

    //assert(q && q.type === Mode.TAG, 'no previous tag for the attr!')
    // Pushes the attribute and shifts the `end` position of the tag (`last`).
    state.pos = tag.end = attr.end;
    tag.attributes = addToCollection(tag.attributes, attr);
  }
}

function parseNomalAttribute(state, attr, quote) {
  const { data } = state;
  let { end } = attr;

  if (isBoolAttribute(attr.name)) {
    attr[IS_BOOLEAN] = true;
  }

  // parse the whole value (if any) and get any expressions on it
  if (quote) {
    // Usually, the value's first char (`quote`) is a quote and the lastIndex
    // (`end`) is the start of the value.
    let valueStart = end;
    // If it not, this is an unquoted value and we need adjust the start.
    if (quote !== '"' && quote !== '\'') {
      quote = ''; // first char of value is not a quote
      valueStart--; // adjust the starting position
    }

    end = expr(state, attr, quote || '[>/\\s]', valueStart);

    // adjust the bounds of the value and save its content
    return Object.assign(attr, {
      value: getChunk(data, valueStart, end),
      valueStart,
      end: quote ? ++end : end
    })
  }

  return attr
}


/**
 * Parse expression names <a {href}>
 * @param   {ParserStore}  state  - Parser state
 * @param   {Object} attr - attribute object parsed
 * @returns {Object} normalized attribute object
 */
function parseSpreadAttribute(state, attr) {
  const end = getAttributeEnd(state, attr);

  return {
    [IS_SPREAD]: true,
    start: attr.start,
    expressions: attr.expressions.map(expr => Object.assign(expr, {
      text: expr.text.replace(SPREAD_OPERATOR, '').trim()
    })),
    end: end
  }
}

/**
 * Parse expression names <a {href}>
 * @param   {ParserStore}  state  - Parser state
 * @param   {Object} attr - attribute object parsed
 * @returns {Object} normalized attribute object
 */
function parseExpressionNameAttribute(state, attr) {
  const end = getAttributeEnd(state, attr);

  return {
    start: attr.start,
    name: attr.expressions[0].text.trim(),
    expressions: attr.expressions,
    end: end
  }
}

/**
 * Parse the attribute values normalising the quotes
 * @param   {ParserStore}  state  - Parser state
 * @param   {Array} match - results of the attributes regex
 * @param   {number} start - attribute start position
 * @param   {number} end - attribute end position
 * @param   {boolean} isExpressionName - true if the attribute name is an expression
 * @returns {Object} attribute object
 */
function parseAttribute(state, match, start, end, isExpressionName) {
  const attr = {
    name: match[1],
    value: '',
    start,
    end
  };

  const quote = match[2]; // first letter of value or nothing

  switch (true) {
  case isSpreadAttribute$1(attr.name):
    return parseSpreadAttribute(state, attr)
  case isExpressionName === true:
    return parseExpressionNameAttribute(state, attr)
  default:
    return parseNomalAttribute(state, attr, quote)
  }
}

/**
 * Function to curry any javascript method
 * @param   {Function}  fn - the target function we want to curry
 * @param   {...[args]} acc - initial arguments
 * @returns {Function|*} it will return a function until the target function
 *                       will receive all of its arguments
 */
function curry(fn, ...acc) {
  return (...args) => {
    args = [...acc, ...args];

    return args.length < fn.length ?
      curry(fn, ...args) :
      fn(...args)
  }
}

/**
 * Parses comments in long or short form
 * (any DOCTYPE & CDATA blocks are parsed as comments).
 *
 * @param   {ParserState} state  - Parser state
 * @param   {string} data       - Buffer to parse
 * @param   {number} start      - Position of the '<!' sequence
 * @returns {number} node type id
 * @private
 */
function comment(state, data, start) {
  const pos = start + 2; // skip '<!'
  const isLongComment = data.substr(pos, 2) === '--';
  const str = isLongComment ? '-->' : '>';
  const end = data.indexOf(str, pos);

  if (end < 0) {
    panic$1(data, unclosedComment, start);
  }

  pushComment(
    state,
    start,
    end + str.length,
    data.substring(start, end + str.length)
  );

  return TEXT
}

/**
 * Parse a comment.
 *
 * @param   {ParserState}  state - Current parser state
 * @param   {number}  start - Start position of the tag
 * @param   {number}  end   - Ending position (last char of the tag)
 * @param   {string}  text  - Comment content
 * @returns {undefined} void function
 * @private
 */
function pushComment(state, start, end, text) {
  state.pos = end;
  if (state.options.comments === true) {
    flush(state);
    state.last = {
      type: COMMENT,
      start,
      end,
      text
    };
  }
}

/**
 * Pushes a new *tag* and set `last` to this, so any attributes
 * will be included on this and shifts the `end`.
 *
 * @param   {ParserState} state  - Current parser state
 * @param   {string}  name      - Name of the node including any slash
 * @param   {number}  start     - Start position of the tag
 * @param   {number}  end       - Ending position (last char of the tag + 1)
 * @returns {undefined} - void function
 * @private
 */
function pushTag(state, name, start, end) {
  const root = state.root;
  const last = { type: TAG, name, start, end };

  if (isCustom(name)) {
    last[IS_CUSTOM] = true;
  }

  if (isVoid(name)) {
    last[IS_VOID] = true;
  }

  state.pos = end;

  if (root) {
    if (name === root.name) {
      state.count++;
    } else if (name === root.close) {
      state.count--;
    }
    flush(state);
  } else {
    // start with root (keep ref to output)
    state.root = { name: last.name, close: `/${name}` };
    state.count = 1;
  }

  state.last = last;
}

/**
 * Parse the tag following a '<' character, or delegate to other parser
 * if an invalid tag name is found.
 *
 * @param   {ParserState} state  - Parser state
 * @returns {number} New parser mode
 * @private
 */
function tag(state) {
  const { pos, data } = state; // pos of the char following '<'
  const start = pos - 1; // pos of '<'
  const str = data.substr(pos, 2); // first two chars following '<'

  switch (true) {
  case str[0] === '!':
    return comment(state, data, start)
  case TAG_2C.test(str):
    return parseTag(state, start)
  default:
    return pushText(state, start, pos) // pushes the '<' as text
  }
}

function parseTag(state, start) {
  const { data, pos } = state;
  const re = TAG_NAME; // (\/?[^\s>/]+)\s*(>)? g
  const match = execFromPos(re, pos, data);
  const end = re.lastIndex;
  const name = match[1].toLowerCase(); // $1: tag name including any '/'
  // script/style block is parsed as another tag to extract attributes
  if (name in RE_SCRYLE) {
    state.scryle = name; // used by parseText
  }

  pushTag(state, name, start, end);
  // only '>' can ends the tag here, the '/' is handled in parseAttribute
  if (!match[2]) {
    return ATTR
  }

  return TEXT
}

/**
 * Parses regular text and script/style blocks ...scryle for short :-)
 * (the content of script and style is text as well)
 *
 * @param   {ParserState} state - Parser state
 * @returns {number} New parser mode.
 * @private
 */
function text(state) {
  const { pos, data, scryle } = state;

  switch (true) {
  case typeof scryle === 'string': {
    const name = scryle;
    const re = RE_SCRYLE[name];
    const match = execFromPos(re, pos, data);

    if (!match) {
      panic$1(data, unclosedNamedBlock.replace('%1', name), pos - 1);
    }

    const start = match.index;
    const end = re.lastIndex;
    state.scryle = null; // reset the script/style flag now
    // write the tag content, if any
    if (start > pos) {
      parseSpecialTagsContent(state, name, match);
    }
    // now the closing tag, either </script> or </style>
    pushTag(state, `/${name}`, start, end);
    break
  }
  case data[pos] === '<':
    state.pos++;
    return TAG
  default:
    expr(state, null, '<', pos);
  }

  return TEXT
}

/**
 * Parse the text content depending on the name
 * @param   {ParserState} state - Parser state
 * @param   {string} name  - one of the tags matched by the RE_SCRYLE regex
 * @param   {Array}  match - result of the regex matching the content of the parsed tag
 * @returns {undefined} void function
 */
function parseSpecialTagsContent(state, name, match) {
  const { pos } = state;
  const start = match.index;

  if (name === TEXTAREA_TAG) {
    expr(state, null, match[0], pos);
  } else {
    pushText(state, pos, start);
  }
}

/*---------------------------------------------------------------------
 * Tree builder for the riot tag parser.
 *
 * The output has a root property and separate arrays for `html`, `css`,
 * and `js` tags.
 *
 * The root tag is included as first element in the `html` array.
 * Script tags marked with "defer" are included in `html` instead `js`.
 *
 * - Mark SVG tags
 * - Mark raw tags
 * - Mark void tags
 * - Split prefixes from expressions
 * - Unescape escaped brackets and escape EOLs and backslashes
 * - Compact whitespace (option `compact`) for non-raw tags
 * - Create an array `parts` for text nodes and attributes
 *
 * Throws on unclosed tags or closing tags without start tag.
 * Selfclosing and void tags has no nodes[] property.
 */

/**
 * Escape the carriage return and the line feed from a string
 * @param   {string} string - input string
 * @returns {string} output string escaped
 */
function escapeReturn(string) {
  return string
    .replace(/\r/g, '\\r')
    .replace(/\n/g, '\\n')
}

// check whether a tag has the 'src' attribute set like for example `<script src="">`
const hasSrcAttribute = node => (node.attributes || []).some(attr => attr.name === 'src');

/**
 * Escape double slashes in a string
 * @param   {string} string - input string
 * @returns {string} output string escaped
 */
function escapeSlashes(string) {
  return string.replace(/\\/g, '\\\\')
}

/**
 * Replace the multiple spaces with only one
 * @param   {string} string - input string
 * @returns {string} string without trailing spaces
 */
function cleanSpaces(string) {
  return string.replace(/\s+/g, ' ')
}

const TREE_BUILDER_STRUCT = Object.seal({
  get() {
    const store = this.store;
    // The real root tag is in store.root.nodes[0]
    return {
      [TEMPLATE_OUTPUT_NAME]: store.root.nodes[0],
      [CSS_OUTPUT_NAME]: store[STYLE_TAG],
      [JAVASCRIPT_OUTPUT_NAME]: store[JAVASCRIPT_TAG]
    }
  },

  /**
  * Process the current tag or text.
  * @param {Object} node - Raw pseudo-node from the parser
  * @returns {undefined} void function
  */
  push(node) {
    const store = this.store;

    switch (node.type) {
    case COMMENT:
      this.pushComment(store, node);
      break
    case TEXT:
      this.pushText(store, node);
      break
    case TAG: {
      const name = node.name;
      const closingTagChar = '/';
      const [firstChar] = name;

      if (firstChar === closingTagChar && !node.isVoid) {
        this.closeTag(store, node, name);
      } else if (firstChar !== closingTagChar) {
        this.openTag(store, node);
      }
      break
    }
    }
  },
  pushComment(store, node) {
    const parent = store.last;

    parent.nodes.push(node);
  },
  closeTag(store, node) {
    const last = store.scryle || store.last;

    last.end = node.end;

    // update always the root node end position
    if (store.root.nodes[0]) store.root.nodes[0].end = node.end;

    if (store.scryle) {
      store.scryle = null;
    } else {
      store.last = store.stack.pop();
    }
  },

  openTag(store, node) {
    const name = node.name;
    const attrs = node.attributes;
    const isCoreTag = (JAVASCRIPT_TAG === name && !hasSrcAttribute(node) || name === STYLE_TAG);

    if (isCoreTag) {
      // Only accept one of each
      if (store[name]) {
        panic$1(this.store.data, duplicatedNamedTag.replace('%1', name), node.start);
      }

      store[name] = node;
      store.scryle = store[name];
    } else {
      // store.last holds the last tag pushed in the stack and this are
      // non-void, non-empty tags, so we are sure the `lastTag` here
      // have a `nodes` property.
      const lastTag = store.last;
      const newNode = node;

      lastTag.nodes.push(newNode);

      if (lastTag[IS_RAW] || RAW_TAGS.test(name)) {
        node[IS_RAW] = true;
      }

      if (!node[IS_SELF_CLOSING] && !node[IS_VOID]) {
        store.stack.push(lastTag);
        newNode.nodes = [];
        store.last = newNode;
      }
    }

    if (attrs) {
      this.attrs(attrs);
    }
  },
  attrs(attributes) {
    attributes.forEach(attr => {
      if (attr.value) {
        this.split(attr, attr.value, attr.valueStart, true);
      }
    });
  },
  pushText(store, node) {
    const text = node.text;
    const empty = !/\S/.test(text);
    const scryle = store.scryle;
    if (!scryle) {
      // store.last always have a nodes property
      const parent = store.last;

      const pack = this.compact && !parent[IS_RAW];
      if (pack && empty) {
        return
      }
      this.split(node, text, node.start, pack);
      parent.nodes.push(node);
    } else if (!empty) {
      scryle.text = node;
    }
  },
  split(node, source, start, pack) {
    const expressions = node.expressions;
    const parts = [];

    if (expressions) {
      let pos = 0;

      expressions.forEach(expr => {
        const text = source.slice(pos, expr.start - start);
        const code = expr.text;
        parts.push(this.sanitise(node, text, pack), escapeReturn(escapeSlashes(code).trim()));
        pos = expr.end - start;
      });

      if (pos < node.end) {
        parts.push(this.sanitise(node, source.slice(pos), pack));
      }
    } else {
      parts[0] = this.sanitise(node, source, pack);
    }

    node.parts = parts.filter(p => p); // remove the empty strings
  },
  // unescape escaped brackets and split prefixes of expressions
  sanitise(node, text, pack) {
    let rep = node.unescape;
    if (rep) {
      let idx = 0;
      rep = `\\${rep}`;
      while ((idx = text.indexOf(rep, idx)) !== -1) {
        text = text.substr(0, idx) + text.substr(idx + 1);
        idx++;
      }
    }

    text = escapeSlashes(text);

    return pack ? cleanSpaces(text) : escapeReturn(text)
  }
});

function createTreeBuilder(data, options) {
  const root = {
    type: TAG,
    name: '',
    start: 0,
    end: 0,
    nodes: []
  };

  return Object.assign(Object.create(TREE_BUILDER_STRUCT), {
    compact: options.compact !== false,
    store: {
      last: root,
      stack: [],
      scryle: null,
      root,
      style: null,
      script: null,
      data
    }
  })
}

/**
 * Factory for the Parser class, exposing only the `parse` method.
 * The export adds the Parser class as property.
 *
 * @param   {Object}   options - User Options
 * @param   {Function} customBuilder - Tree builder factory
 * @returns {Function} Public Parser implementation.
 */
function parser(options, customBuilder) {
  const state = curry(createParserState)(options, customBuilder || createTreeBuilder);
  return {
    parse: (data) => parse(state(data))
  }
}

/**
 * Create a new state object
 * @param   {Object} userOptions - parser options
 * @param   {Function} builder - Tree builder factory
 * @param   {string} data - data to parse
 * @returns {ParserState} it represents the current parser state
 */
function createParserState(userOptions, builder, data) {
  const options = Object.assign({
    brackets: ['{', '}']
  }, userOptions);

  return {
    options,
    regexCache: {},
    pos: 0,
    count: -1,
    root: null,
    last: null,
    scryle: null,
    builder: builder(data, options),
    data
  }
}

/**
 * It creates a raw output of pseudo-nodes with one of three different types,
 * all of them having a start/end position:
 *
 * - TAG     -- Opening or closing tags
 * - TEXT    -- Raw text
 * - COMMENT -- Comments
 *
 * @param   {ParserState}  state - Current parser state
 * @returns {ParserResult} Result, contains data and output properties.
 */
function parse(state) {
  const { data } = state;

  walk(state);
  flush(state);

  if (state.count) {
    panic$1(data, state.count > 0 ? unexpectedEndOfFile : rootTagNotFound, state.pos);
  }

  return {
    data,
    output: state.builder.get()
  }
}

/**
 * Parser walking recursive function
 * @param {ParserState}  state - Current parser state
 * @param {string} type - current parsing context
 * @returns {undefined} void function
 */
function walk(state, type) {
  const { data } = state;
  // extend the state adding the tree builder instance and the initial data
  const length = data.length;

  // The "count" property is set to 1 when the first tag is found.
  // This becomes the root and precedent text or comments are discarded.
  // So, at the end of the parsing count must be zero.
  if (state.pos < length && state.count) {
    walk(state, eat(state, type));
  }
}

/**
 * Function to help iterating on the current parser state
 * @param {ParserState}  state - Current parser state
 * @param   {string} type - current parsing context
 * @returns {string} parsing context
 */
function eat(state, type) {
  switch (type) {
  case TAG:
    return tag(state)
  case ATTR:
    return attr(state)
  default:
    return text(state)
  }
}

/**
 * Expose the internal constants
 */
const constants = c;

/**
 * The nodeTypes definition
 */
const nodeTypes = types$1;

const BINDING_TYPES = 'bindingTypes';
const EACH_BINDING_TYPE = 'EACH';
const IF_BINDING_TYPE = 'IF';
const TAG_BINDING_TYPE = 'TAG';
const SLOT_BINDING_TYPE = 'SLOT';


const EXPRESSION_TYPES = 'expressionTypes';
const ATTRIBUTE_EXPRESSION_TYPE = 'ATTRIBUTE';
const VALUE_EXPRESSION_TYPE = 'VALUE';
const TEXT_EXPRESSION_TYPE = 'TEXT';
const EVENT_EXPRESSION_TYPE = 'EVENT';

const TEMPLATE_FN = 'template';
const SCOPE = '_scope';
const GET_COMPONENT_FN = 'getComponent';

// keys needed to create the DOM bindings
const BINDING_SELECTOR_KEY = 'selector';
const BINDING_GET_COMPONENT_KEY = 'getComponent';
const BINDING_TEMPLATE_KEY = 'template';
const BINDING_TYPE_KEY = 'type';
const BINDING_REDUNDANT_ATTRIBUTE_KEY = 'redundantAttribute';
const BINDING_CONDITION_KEY = 'condition';
const BINDING_ITEM_NAME_KEY = 'itemName';
const BINDING_GET_KEY_KEY = 'getKey';
const BINDING_INDEX_NAME_KEY = 'indexName';
const BINDING_EVALUATE_KEY = 'evaluate';
const BINDING_NAME_KEY = 'name';
const BINDING_SLOTS_KEY = 'slots';
const BINDING_EXPRESSIONS_KEY = 'expressions';
const BINDING_CHILD_NODE_INDEX_KEY = 'childNodeIndex';
// slots keys
const BINDING_BINDINGS_KEY = 'bindings';
const BINDING_ID_KEY = 'id';
const BINDING_HTML_KEY = 'html';
const BINDING_ATTRIBUTES_KEY = 'attributes';

// DOM directives
const IF_DIRECTIVE = 'if';
const EACH_DIRECTIVE = 'each';
const KEY_ATTRIBUTE = 'key';
const SLOT_ATTRIBUTE = 'slot';
const NAME_ATTRIBUTE = 'name';
const IS_DIRECTIVE = 'is';

// Misc
const DEFAULT_SLOT_NAME = 'default';
const TEXT_NODE_EXPRESSION_PLACEHOLDER = ' ';
const BINDING_SELECTOR_PREFIX = 'expr';
const SLOT_TAG_NODE_NAME = 'slot';
const PROGRESS_TAG_NODE_NAME = 'progress';
const TEMPLATE_TAG_NODE_NAME = 'template';

// Riot Parser constants
constants.IS_RAW;
const IS_VOID_NODE = constants.IS_VOID;
const IS_CUSTOM_NODE = constants.IS_CUSTOM;
const IS_BOOLEAN_ATTRIBUTE = constants.IS_BOOLEAN;
const IS_SPREAD_ATTRIBUTE = constants.IS_SPREAD;

const types = types$2;
const builders = types$2.builders;
const namedTypes = types$2.namedTypes;

const browserAPIs = ['window', 'document', 'console'];
const builtinAPIs = Object.keys(builtin);

const isIdentifier = n => namedTypes.Identifier.check(n);
const isLiteral = n => namedTypes.Literal.check(n);
const isExpressionStatement = n => namedTypes.ExpressionStatement.check(n);
const isThisExpression = n => namedTypes.ThisExpression.check(n);
const isObjectExpression = n => namedTypes.ObjectExpression.check(n);
const isThisExpressionStatement = n =>
  isExpressionStatement(n) &&
  isMemberExpression(n.expression.left) &&
  isThisExpression(n.expression.left.object);
const isNewExpression = n => namedTypes.NewExpression.check(n);
const isSequenceExpression = n => namedTypes.SequenceExpression.check(n);
const isExportDefaultStatement = n => namedTypes.ExportDefaultDeclaration.check(n);
const isMemberExpression = n => namedTypes.MemberExpression.check(n);
const isImportDeclaration = n => namedTypes.ImportDeclaration.check(n);
const isTypeAliasDeclaration = n => namedTypes.TSTypeAliasDeclaration.check(n);
const isInterfaceDeclaration = n => namedTypes.TSInterfaceDeclaration.check(n);
const isExportNamedDeclaration = n => namedTypes.ExportNamedDeclaration.check(n);

const isBrowserAPI = ({name}) => browserAPIs.includes(name);
const isBuiltinAPI = ({name}) => builtinAPIs.includes(name);
const isRaw = n => n && n.raw;

/**
 * Similar to compose but performs from left-to-right function composition.<br/>
 * {@link https://30secondsofcode.org/function#composeright see also}
 * @param   {...[function]} fns) - list of unary function
 * @returns {*} result of the computation
 */

/**
 * Performs right-to-left function composition.<br/>
 * Use Array.prototype.reduce() to perform right-to-left function composition.<br/>
 * The last (rightmost) function can accept one or more arguments; the remaining functions must be unary.<br/>
 * {@link https://30secondsofcode.org/function#compose original source code}
 * @param   {...[function]} fns) - list of unary function
 * @returns {*} result of the computation
 */
function compose(...fns) {
  return fns.reduce((f, g) => (...args) => f(g(...args)))
}

/**
 * Quick type checking
 * @param   {*} element - anything
 * @param   {string} type - type definition
 * @returns {boolean} true if the type corresponds
 */

/**
 * Check if a value is an Object
 * @param   {*}  value - anything
 * @returns {boolean} true only for the value is an object
 */
function isObject(value) {
  return !isNil(value) && value.constructor === Object
}

/**
 * Check if a value is null or undefined
 * @param   {*}  value - anything
 * @returns {boolean} true only for the 'undefined' and 'null' types
 */
function isNil(value) {
  return value === null || value === undefined
}

/**
 * Detect node js environements
 * @returns {boolean} true if the runtime is node
 */
function isNode() {
  return typeof process !== 'undefined'
}

/**
 * True if the node has not expression set nor bindings directives
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {boolean} true only if it's a static node that doesn't need bindings or expressions
 */
function isStaticNode(node) {
  return [
    hasExpressions,
    findEachAttribute,
    findIfAttribute,
    isCustomNode,
    isSlotNode
  ].every(test => !test(node))
}

/**
 * Check if a node should be rendered in the final component HTML
 * For example slot <template slot="content"> tags not using `each` or `if` directives can be removed
 * see also https://github.com/riot/riot/issues/2888
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {boolean} true if we can remove this tag from the component rendered HTML
 */
function isRemovableNode(node) {
  return isTemplateNode(node) && !isNil(findAttribute(SLOT_ATTRIBUTE, node)) && !hasEachAttribute(node) && !hasIfAttribute(node)
}

/**
 * Check if a node name is part of the browser or builtin javascript api or it belongs to the current scope
 * @param   { types.NodePath } path - containing the current node visited
 * @returns {boolean} true if it's a global api variable
 */
function isGlobal({ scope, node }) {
  // recursively find the identifier of this AST path
  if (node.object) {
    return isGlobal({ node: node.object, scope })
  }

  return Boolean(
    isRaw(node) ||
    isBuiltinAPI(node) ||
    isBrowserAPI(node) ||
    isNewExpression(node) ||
    isNodeInScope(scope, node)
  )
}

/**
 * Checks if the identifier of a given node exists in a scope
 * @param {Scope} scope - scope where to search for the identifier
 * @param {types.Node} node - node to search for the identifier
 * @returns {boolean} true if the node identifier is defined in the given scope
 */
function isNodeInScope(scope, node) {
  const traverse = (isInScope = false) => {
    types.visit(node, {
      visitIdentifier(path) {
        if (scope.lookup(getName(path.node))) {
          isInScope = true;
        }

        this.abort();
      }
    });

    return isInScope
  };

  return traverse()
}

/**
 * True if the node has the isCustom attribute set
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {boolean} true if either it's a riot component or a custom element
 */
function isCustomNode(node) {
  return !!(node[IS_CUSTOM_NODE] || hasIsAttribute(node))
}

/**
 * True the node is <slot>
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {boolean} true if it's a slot node
 */
function isSlotNode(node) {
  return node.name === SLOT_TAG_NODE_NAME
}

/**
 * True if the node has the isVoid attribute set
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {boolean} true if the node is self closing
 */
function isVoidNode(node) {
  return !!node[IS_VOID_NODE]
}

/**
 * True if the riot parser did find a tag node
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {boolean} true only for the tag nodes
 */
function isTagNode(node) {
  return node.type === nodeTypes.TAG
}

/**
 * True if the riot parser did find a text node
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {boolean} true only for the text nodes
 */
function isTextNode(node) {
  return node.type === nodeTypes.TEXT
}

/**
 * True if the node parsed is the root one
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {boolean} true only for the root nodes
 */
function isRootNode(node) {
  return node.isRoot
}

/**
 * True if the attribute parsed is of type spread one
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {boolean} true if the attribute node is of type spread
 */
function isSpreadAttribute(node) {
  return node[IS_SPREAD_ATTRIBUTE]
}

/**
 * True if the node is an attribute and its name is "value"
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {boolean} true only for value attribute nodes
 */
function isValueAttribute(node) {
  return node.name === 'value'
}

/**
 * True if the DOM node is a progress tag
 * @param   {RiotParser.Node}  node - riot parser node
 * @returns {boolean} true for the progress tags
 */
function isProgressNode(node) {
  return node.name === PROGRESS_TAG_NODE_NAME
}

/**
 * True if the DOM node is a <template> tag
 * @param   {RiotParser.Node}  node - riot parser node
 * @returns {boolean} true for the progress tags
 */
function isTemplateNode(node) {
  return node.name === TEMPLATE_TAG_NODE_NAME
}

/**
 * True if the node is an attribute and a DOM handler
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {boolean} true only for dom listener attribute nodes
 */
const isEventAttribute = (() => {
  const EVENT_ATTR_RE = /^on/;
  return node => EVENT_ATTR_RE.test(node.name)
})();


/**
 * Check if a string is an html comment
 * @param   {string}  string - test string
 * @returns {boolean} true if html comment
 */
function isCommentString(string) {
  return string.trim().indexOf('<!') === 0
}

/**
 * True if the node has expressions or expression attributes
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {boolean} ditto
 */
function hasExpressions(node) {
  return !!(
    node.expressions ||
    // has expression attributes
    (getNodeAttributes(node).some(attribute => hasExpressions(attribute))) ||
    // has child text nodes with expressions
    (node.nodes && node.nodes.some(node => isTextNode(node) && hasExpressions(node)))
  )
}

/**
 * True if the node is a directive having its own template
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {boolean} true only for the IF EACH and TAG bindings
 */
function hasItsOwnTemplate(node) {
  return [
    findEachAttribute,
    findIfAttribute,
    isCustomNode
  ].some(test => test(node))
}

const hasIfAttribute = compose(Boolean, findIfAttribute);
const hasEachAttribute = compose(Boolean, findEachAttribute);
const hasIsAttribute = compose(Boolean, findIsAttribute);
compose(Boolean, findKeyAttribute);

/**
 * Find the attribute node
 * @param   { string } name -  name of the attribute we want to find
 * @param   { riotParser.nodeTypes.TAG } node - a tag node
 * @returns { riotParser.nodeTypes.ATTR } attribute node
 */
function findAttribute(name, node) {
  return node.attributes && node.attributes.find(attr => getName(attr) === name)
}

function findIfAttribute(node) {
  return findAttribute(IF_DIRECTIVE, node)
}

function findEachAttribute(node) {
  return findAttribute(EACH_DIRECTIVE, node)
}

function findKeyAttribute(node) {
  return findAttribute(KEY_ATTRIBUTE, node)
}

function findIsAttribute(node) {
  return findAttribute(IS_DIRECTIVE, node)
}

/**
 * Find all the node attributes that are not expressions
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {Array} list of all the static attributes
 */
function findStaticAttributes(node) {
  return getNodeAttributes(node).filter(attribute => !hasExpressions(attribute))
}

/**
 * Find all the node attributes that have expressions
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {Array} list of all the dynamic attributes
 */
function findDynamicAttributes(node) {
  return getNodeAttributes(node).filter(hasExpressions)
}

function nullNode() {
  return builders.literal(null)
}

function simplePropertyNode(key, value) {
  const property = builders.property('init', builders.identifier(key), value, false);

  property.sho;
  return property
}

const LINES_RE = /\r\n?|\n/g;

/**
 * Split a string into a rows array generated from its EOL matches
 * @param   { string } string [description]
 * @returns { Array } array containing all the string rows
 */
function splitStringByEOL(string) {
  return string.split(LINES_RE)
}

/**
 * Get the line and the column of a source text based on its position in the string
 * @param   { string } string - target string
 * @param   { number } position - target position
 * @returns { Object } object containing the source text line and column
 */
function getLineAndColumnByPosition(string, position) {
  const lines = splitStringByEOL(string.slice(0, position));

  return {
    line: lines.length,
    column: lines[lines.length - 1].length
  }
}

/**
 * Add the offset to the code that must be parsed in order to generate properly the sourcemaps
 * @param {string} input - input string
 * @param {string} source - original source code
 * @param {RiotParser.Node} node - node that we are going to transform
 * @return {string} the input string with the offset properly set
 */
function addLineOffset(input, source, node) {
  const {column, line} = getLineAndColumnByPosition(source, node.start);
  return `${'\n'.repeat(line - 1)}${' '.repeat(column + 1)}${input}`
}

/**
 * Create a simple attribute expression
 * @param   {RiotParser.Node.Attr} sourceNode - the custom tag
 * @param   {string} sourceFile - source file path
 * @param   {string} sourceCode - original source
 * @returns {AST.Node} object containing the expression binding keys
 */
function createAttributeExpression(sourceNode, sourceFile, sourceCode) {
  return builders.objectExpression([
    simplePropertyNode(BINDING_TYPE_KEY,
      builders.memberExpression(
        builders.identifier(EXPRESSION_TYPES),
        builders.identifier(ATTRIBUTE_EXPRESSION_TYPE),
        false
      )
    ),
    simplePropertyNode(BINDING_NAME_KEY, isSpreadAttribute(sourceNode) ? nullNode() : builders.literal(sourceNode.name)),
    simplePropertyNode(
      BINDING_EVALUATE_KEY,
      createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode)
    )
  ])
}

/**
 * Create a simple event expression
 * @param   {RiotParser.Node.Attr} sourceNode - attribute containing the event handlers
 * @param   {string} sourceFile - source file path
 * @param   {string} sourceCode - original source
 * @returns {AST.Node} object containing the expression binding keys
 */
function createEventExpression(sourceNode, sourceFile, sourceCode) {
  return builders.objectExpression([
    simplePropertyNode(BINDING_TYPE_KEY,
      builders.memberExpression(
        builders.identifier(EXPRESSION_TYPES),
        builders.identifier(EVENT_EXPRESSION_TYPE),
        false
      )
    ),
    simplePropertyNode(BINDING_NAME_KEY, builders.literal(sourceNode.name)),
    simplePropertyNode(
      BINDING_EVALUATE_KEY,
      createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode)
    )
  ])
}

var quot = "\"";
var amp = "&";
var apos = "'";
var lt = "<";
var gt = ">";
var nbsp = " ";
var iexcl = "¡";
var cent = "¢";
var pound = "£";
var curren = "¤";
var yen = "¥";
var brvbar = "¦";
var sect = "§";
var uml = "¨";
var copy = "©";
var ordf = "ª";
var laquo = "«";
var not = "¬";
var shy = "­";
var reg = "®";
var macr = "¯";
var deg = "°";
var plusmn = "±";
var sup2 = "²";
var sup3 = "³";
var acute = "´";
var micro = "µ";
var para = "¶";
var middot = "·";
var cedil = "¸";
var sup1 = "¹";
var ordm = "º";
var raquo = "»";
var frac14 = "¼";
var frac12 = "½";
var frac34 = "¾";
var iquest = "¿";
var Agrave = "À";
var Aacute = "Á";
var Acirc = "Â";
var Atilde = "Ã";
var Auml = "Ä";
var Aring = "Å";
var AElig = "Æ";
var Ccedil = "Ç";
var Egrave = "È";
var Eacute = "É";
var Ecirc = "Ê";
var Euml = "Ë";
var Igrave = "Ì";
var Iacute = "Í";
var Icirc = "Î";
var Iuml = "Ï";
var ETH = "Ð";
var Ntilde = "Ñ";
var Ograve = "Ò";
var Oacute = "Ó";
var Ocirc = "Ô";
var Otilde = "Õ";
var Ouml = "Ö";
var times = "×";
var Oslash = "Ø";
var Ugrave = "Ù";
var Uacute = "Ú";
var Ucirc = "Û";
var Uuml = "Ü";
var Yacute = "Ý";
var THORN = "Þ";
var szlig = "ß";
var agrave = "à";
var aacute = "á";
var acirc = "â";
var atilde = "ã";
var auml = "ä";
var aring = "å";
var aelig = "æ";
var ccedil = "ç";
var egrave = "è";
var eacute = "é";
var ecirc = "ê";
var euml = "ë";
var igrave = "ì";
var iacute = "í";
var icirc = "î";
var iuml = "ï";
var eth = "ð";
var ntilde = "ñ";
var ograve = "ò";
var oacute = "ó";
var ocirc = "ô";
var otilde = "õ";
var ouml = "ö";
var divide = "÷";
var oslash = "ø";
var ugrave = "ù";
var uacute = "ú";
var ucirc = "û";
var uuml = "ü";
var yacute = "ý";
var thorn = "þ";
var yuml = "ÿ";
var OElig = "Œ";
var oelig = "œ";
var Scaron = "Š";
var scaron = "š";
var Yuml = "Ÿ";
var fnof = "ƒ";
var circ = "ˆ";
var tilde = "˜";
var Alpha = "Α";
var Beta = "Β";
var Gamma = "Γ";
var Delta = "Δ";
var Epsilon = "Ε";
var Zeta = "Ζ";
var Eta = "Η";
var Theta = "Θ";
var Iota = "Ι";
var Kappa = "Κ";
var Lambda = "Λ";
var Mu = "Μ";
var Nu = "Ν";
var Xi = "Ξ";
var Omicron = "Ο";
var Pi = "Π";
var Rho = "Ρ";
var Sigma = "Σ";
var Tau = "Τ";
var Upsilon = "Υ";
var Phi = "Φ";
var Chi = "Χ";
var Psi = "Ψ";
var Omega = "Ω";
var alpha = "α";
var beta = "β";
var gamma = "γ";
var delta = "δ";
var epsilon = "ε";
var zeta = "ζ";
var eta = "η";
var theta = "θ";
var iota = "ι";
var kappa = "κ";
var lambda = "λ";
var mu = "μ";
var nu = "ν";
var xi = "ξ";
var omicron = "ο";
var pi = "π";
var rho = "ρ";
var sigmaf = "ς";
var sigma = "σ";
var tau = "τ";
var upsilon = "υ";
var phi = "φ";
var chi = "χ";
var psi = "ψ";
var omega = "ω";
var thetasym = "ϑ";
var upsih = "ϒ";
var piv = "ϖ";
var ensp = " ";
var emsp = " ";
var thinsp = " ";
var zwnj = "‌";
var zwj = "‍";
var lrm = "‎";
var rlm = "‏";
var ndash = "–";
var mdash = "—";
var lsquo = "‘";
var rsquo = "’";
var sbquo = "‚";
var ldquo = "“";
var rdquo = "”";
var bdquo = "„";
var dagger = "†";
var Dagger = "‡";
var bull = "•";
var hellip = "…";
var permil = "‰";
var prime = "′";
var Prime = "″";
var lsaquo = "‹";
var rsaquo = "›";
var oline = "‾";
var frasl = "⁄";
var euro = "€";
var image = "ℑ";
var weierp = "℘";
var real = "ℜ";
var trade = "™";
var alefsym = "ℵ";
var larr = "←";
var uarr = "↑";
var rarr = "→";
var darr = "↓";
var harr = "↔";
var crarr = "↵";
var lArr = "⇐";
var uArr = "⇑";
var rArr = "⇒";
var dArr = "⇓";
var hArr = "⇔";
var forall = "∀";
var part = "∂";
var exist = "∃";
var empty = "∅";
var nabla = "∇";
var isin = "∈";
var notin = "∉";
var ni = "∋";
var prod = "∏";
var sum = "∑";
var minus = "−";
var lowast = "∗";
var radic = "√";
var prop = "∝";
var infin = "∞";
var ang = "∠";
var and = "∧";
var or = "∨";
var cap = "∩";
var cup = "∪";
var int = "∫";
var there4 = "∴";
var sim = "∼";
var cong = "≅";
var asymp = "≈";
var ne = "≠";
var equiv = "≡";
var le = "≤";
var ge = "≥";
var sub = "⊂";
var sup = "⊃";
var nsub = "⊄";
var sube = "⊆";
var supe = "⊇";
var oplus = "⊕";
var otimes = "⊗";
var perp = "⊥";
var sdot = "⋅";
var lceil = "⌈";
var rceil = "⌉";
var lfloor = "⌊";
var rfloor = "⌋";
var lang = "〈";
var rang = "〉";
var loz = "◊";
var spades = "♠";
var clubs = "♣";
var hearts = "♥";
var diams = "♦";
var entities = {
	quot: quot,
	amp: amp,
	apos: apos,
	lt: lt,
	gt: gt,
	nbsp: nbsp,
	iexcl: iexcl,
	cent: cent,
	pound: pound,
	curren: curren,
	yen: yen,
	brvbar: brvbar,
	sect: sect,
	uml: uml,
	copy: copy,
	ordf: ordf,
	laquo: laquo,
	not: not,
	shy: shy,
	reg: reg,
	macr: macr,
	deg: deg,
	plusmn: plusmn,
	sup2: sup2,
	sup3: sup3,
	acute: acute,
	micro: micro,
	para: para,
	middot: middot,
	cedil: cedil,
	sup1: sup1,
	ordm: ordm,
	raquo: raquo,
	frac14: frac14,
	frac12: frac12,
	frac34: frac34,
	iquest: iquest,
	Agrave: Agrave,
	Aacute: Aacute,
	Acirc: Acirc,
	Atilde: Atilde,
	Auml: Auml,
	Aring: Aring,
	AElig: AElig,
	Ccedil: Ccedil,
	Egrave: Egrave,
	Eacute: Eacute,
	Ecirc: Ecirc,
	Euml: Euml,
	Igrave: Igrave,
	Iacute: Iacute,
	Icirc: Icirc,
	Iuml: Iuml,
	ETH: ETH,
	Ntilde: Ntilde,
	Ograve: Ograve,
	Oacute: Oacute,
	Ocirc: Ocirc,
	Otilde: Otilde,
	Ouml: Ouml,
	times: times,
	Oslash: Oslash,
	Ugrave: Ugrave,
	Uacute: Uacute,
	Ucirc: Ucirc,
	Uuml: Uuml,
	Yacute: Yacute,
	THORN: THORN,
	szlig: szlig,
	agrave: agrave,
	aacute: aacute,
	acirc: acirc,
	atilde: atilde,
	auml: auml,
	aring: aring,
	aelig: aelig,
	ccedil: ccedil,
	egrave: egrave,
	eacute: eacute,
	ecirc: ecirc,
	euml: euml,
	igrave: igrave,
	iacute: iacute,
	icirc: icirc,
	iuml: iuml,
	eth: eth,
	ntilde: ntilde,
	ograve: ograve,
	oacute: oacute,
	ocirc: ocirc,
	otilde: otilde,
	ouml: ouml,
	divide: divide,
	oslash: oslash,
	ugrave: ugrave,
	uacute: uacute,
	ucirc: ucirc,
	uuml: uuml,
	yacute: yacute,
	thorn: thorn,
	yuml: yuml,
	OElig: OElig,
	oelig: oelig,
	Scaron: Scaron,
	scaron: scaron,
	Yuml: Yuml,
	fnof: fnof,
	circ: circ,
	tilde: tilde,
	Alpha: Alpha,
	Beta: Beta,
	Gamma: Gamma,
	Delta: Delta,
	Epsilon: Epsilon,
	Zeta: Zeta,
	Eta: Eta,
	Theta: Theta,
	Iota: Iota,
	Kappa: Kappa,
	Lambda: Lambda,
	Mu: Mu,
	Nu: Nu,
	Xi: Xi,
	Omicron: Omicron,
	Pi: Pi,
	Rho: Rho,
	Sigma: Sigma,
	Tau: Tau,
	Upsilon: Upsilon,
	Phi: Phi,
	Chi: Chi,
	Psi: Psi,
	Omega: Omega,
	alpha: alpha,
	beta: beta,
	gamma: gamma,
	delta: delta,
	epsilon: epsilon,
	zeta: zeta,
	eta: eta,
	theta: theta,
	iota: iota,
	kappa: kappa,
	lambda: lambda,
	mu: mu,
	nu: nu,
	xi: xi,
	omicron: omicron,
	pi: pi,
	rho: rho,
	sigmaf: sigmaf,
	sigma: sigma,
	tau: tau,
	upsilon: upsilon,
	phi: phi,
	chi: chi,
	psi: psi,
	omega: omega,
	thetasym: thetasym,
	upsih: upsih,
	piv: piv,
	ensp: ensp,
	emsp: emsp,
	thinsp: thinsp,
	zwnj: zwnj,
	zwj: zwj,
	lrm: lrm,
	rlm: rlm,
	ndash: ndash,
	mdash: mdash,
	lsquo: lsquo,
	rsquo: rsquo,
	sbquo: sbquo,
	ldquo: ldquo,
	rdquo: rdquo,
	bdquo: bdquo,
	dagger: dagger,
	Dagger: Dagger,
	bull: bull,
	hellip: hellip,
	permil: permil,
	prime: prime,
	Prime: Prime,
	lsaquo: lsaquo,
	rsaquo: rsaquo,
	oline: oline,
	frasl: frasl,
	euro: euro,
	image: image,
	weierp: weierp,
	real: real,
	trade: trade,
	alefsym: alefsym,
	larr: larr,
	uarr: uarr,
	rarr: rarr,
	darr: darr,
	harr: harr,
	crarr: crarr,
	lArr: lArr,
	uArr: uArr,
	rArr: rArr,
	dArr: dArr,
	hArr: hArr,
	forall: forall,
	part: part,
	exist: exist,
	empty: empty,
	nabla: nabla,
	isin: isin,
	notin: notin,
	ni: ni,
	prod: prod,
	sum: sum,
	minus: minus,
	lowast: lowast,
	radic: radic,
	prop: prop,
	infin: infin,
	ang: ang,
	and: and,
	or: or,
	cap: cap,
	cup: cup,
	int: int,
	there4: there4,
	sim: sim,
	cong: cong,
	asymp: asymp,
	ne: ne,
	equiv: equiv,
	le: le,
	ge: ge,
	sub: sub,
	sup: sup,
	nsub: nsub,
	sube: sube,
	supe: supe,
	oplus: oplus,
	otimes: otimes,
	perp: perp,
	sdot: sdot,
	lceil: lceil,
	rceil: rceil,
	lfloor: lfloor,
	rfloor: rfloor,
	lang: lang,
	rang: rang,
	loz: loz,
	spades: spades,
	clubs: clubs,
	hearts: hearts,
	diams: diams
};

const HTMLEntityRe = /&(\S+);/g;
const HEX_NUMBER = /^[\da-fA-F]+$/;
const DECIMAL_NUMBER = /^\d+$/;

/**
 * Encode unicode hex html entities like for example &#x222;
 * @param   {string} string - input string
 * @returns {string} encoded string
 */
function encodeHex(string) {
  const hex = string.substr(2);

  return HEX_NUMBER.test(hex) ?
    String.fromCodePoint(parseInt(hex, 16)) :
    string
}

/**
 * Encode unicode decimal html entities like for example &#222;
 * @param   {string} string - input string
 * @returns {string} encoded string
 */
function encodeDecimal(string) {
  const nr = string.substr(1);

  return DECIMAL_NUMBER.test(nr) ?
    String.fromCodePoint(parseInt(nr, 10))
    : string
}

/**
 * Encode html entities in strings like &nbsp;
 * @param   {string} string - input string
 * @returns {string} encoded string
 */
function encodeHTMLEntities(string) {
  return string.replace(HTMLEntityRe, (match, entity) => {
    const [firstChar, secondChar] = entity;

    if (firstChar === '#') {
      return secondChar === 'x' ?
        encodeHex(entity) :
        encodeDecimal(entity)
    } else {
      return entities[entity] || entity
    }
  })
}

/**
 * Native String.prototype.trimEnd method with fallback to String.prototype.trimRight
 * Edge doesn't support the first one
 * @param   {string} string - input string
 * @returns {string} trimmed output
 */
function trimEnd(string) {
  return (string.trimEnd || string.trimRight).apply(string)
}

/**
 * Native String.prototype.trimStart method with fallback to String.prototype.trimLeft
 * Edge doesn't support the first one
 * @param   {string} string - input string
 * @returns {string} trimmed output
 */
function trimStart(string) {
  return (string.trimStart || string.trimLeft).apply(string)
}

/**
 * Unescape the user escaped chars
 * @param   {string} string - input string
 * @param   {string} char - probably a '{' or anything the user want's to escape
 * @returns {string} cleaned up string
 */
function unescapeChar(string, char) {
  return string.replace(RegExp(`\\\\${char}`, 'gm'), char)
}

/**
 * Generate the pure immutable string chunks from a RiotParser.Node.Text
 * @param   {RiotParser.Node.Text} node - riot parser text node
 * @param   {string} sourceCode sourceCode - source code
 * @returns {Array} array containing the immutable string chunks
 */
function generateLiteralStringChunksFromNode(node, sourceCode) {
  return node.expressions.reduce((chunks, expression, index) => {
    const start = index ? node.expressions[index - 1].end : node.start;
    const string = encodeHTMLEntities(
      sourceCode.substring(start, expression.start)
    );

    // trimStart the first string
    chunks.push(index === 0 ? trimStart(string) : string);

    // add the tail to the string
    if (index === node.expressions.length - 1)
      chunks.push(
        encodeHTMLEntities(
          trimEnd(sourceCode.substring(expression.end, node.end))
        )
      );

    return chunks
  }, [])
    // comments are not supported here
    .filter(str => !isCommentString(str))
    .map(str => node.unescape ? unescapeChar(str, node.unescape) : str)
}

/**
 * Simple bindings might contain multiple expressions like for example: "{foo} and {bar}"
 * This helper aims to merge them in a template literal if it's necessary
 * @param   {RiotParser.Node} node - riot parser node
 * @param   {string} sourceFile - original tag file
 * @param   {string} sourceCode - original tag source code
 * @returns { Object } a template literal expression object
 */
function mergeNodeExpressions(node, sourceFile, sourceCode) {
  if (node.parts.length === 1)
    return transformExpression(node.expressions[0], sourceFile, sourceCode)

  const pureStringChunks = generateLiteralStringChunksFromNode(node, sourceCode);
  const stringsArray = pureStringChunks.reduce((acc, str, index) => {
    const expr = node.expressions[index];

    return [
      ...acc,
      builders.literal(str),
      expr ? transformExpression(expr, sourceFile, sourceCode) : nullNode()
    ]
  }, [])
    // filter the empty literal expressions
    .filter(expr => !isLiteral(expr) || expr.value);

  return createArrayString(stringsArray)
}

/**
 * Create a text expression
 * @param   {RiotParser.Node.Text} sourceNode - text node to parse
 * @param   {string} sourceFile - source file path
 * @param   {string} sourceCode - original source
 * @param   {number} childNodeIndex - position of the child text node in its parent children nodes
 * @returns {AST.Node} object containing the expression binding keys
 */
function createTextExpression(sourceNode, sourceFile, sourceCode, childNodeIndex) {
  return builders.objectExpression([
    simplePropertyNode(BINDING_TYPE_KEY,
      builders.memberExpression(
        builders.identifier(EXPRESSION_TYPES),
        builders.identifier(TEXT_EXPRESSION_TYPE),
        false
      )
    ),
    simplePropertyNode(
      BINDING_CHILD_NODE_INDEX_KEY,
      builders.literal(childNodeIndex)
    ),
    simplePropertyNode(
      BINDING_EVALUATE_KEY,
      wrapASTInFunctionWithScope(
        mergeNodeExpressions(sourceNode, sourceFile, sourceCode)
      )
    )
  ])
}

function createValueExpression(sourceNode, sourceFile, sourceCode) {
  return builders.objectExpression([
    simplePropertyNode(BINDING_TYPE_KEY,
      builders.memberExpression(
        builders.identifier(EXPRESSION_TYPES),
        builders.identifier(VALUE_EXPRESSION_TYPE),
        false
      )
    ),
    simplePropertyNode(
      BINDING_EVALUATE_KEY,
      createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode)
    )
  ])
}

function createExpression(sourceNode, sourceFile, sourceCode, childNodeIndex, parentNode) {
  switch (true) {
  case isTextNode(sourceNode):
    return createTextExpression(sourceNode, sourceFile, sourceCode, childNodeIndex)
  // progress nodes value attributes will be rendered as attributes
  // see https://github.com/riot/compiler/issues/122
  case isValueAttribute(sourceNode) && hasValueAttribute(parentNode.name) && !isProgressNode(parentNode):
    return createValueExpression(sourceNode, sourceFile, sourceCode)
  case isEventAttribute(sourceNode):
    return createEventExpression(sourceNode, sourceFile, sourceCode)
  default:
    return createAttributeExpression(sourceNode, sourceFile, sourceCode)
  }
}

/**
 * Create the attribute expressions
 * @param   {RiotParser.Node} sourceNode - any kind of node parsed via riot parser
 * @param   {string} sourceFile - source file path
 * @param   {string} sourceCode - original source
 * @returns {Array} array containing all the attribute expressions
 */
function createAttributeExpressions(sourceNode, sourceFile, sourceCode) {
  return findDynamicAttributes(sourceNode)
    .map(attribute => createExpression(attribute, sourceFile, sourceCode, 0, sourceNode))
}

/**
 * Parse a js source to generate the AST
 * @param   {string} source - javascript source
 * @param   {Object} options - parser options
 * @returns {AST} AST tree
 */
function generateAST(source, options) {
  return parse$1(source, {
    parser: {
      parse: (source, opts) => parse$2(source, {
        ...opts,
        ecmaVersion: 'latest'
      })
    },
    ...options
  })
}

const scope = builders.identifier(SCOPE);
const getName = node => node && node.name ? node.name : node;

/**
 * Replace the path scope with a member Expression
 * @param   { types.NodePath } path - containing the current node visited
 * @param   { types.Node } property - node we want to prefix with the scope identifier
 * @returns {undefined} this is a void function
 */
function replacePathScope(path, property) {
  path.replace(builders.memberExpression(
    scope,
    property,
    false
  ));
}

/**
 * Change the nodes scope adding the `scope` prefix
 * @param   { types.NodePath } path - containing the current node visited
 * @returns { boolean } return false if we want to stop the tree traversal
 * @context { types.visit }
 */
function updateNodeScope(path) {
  if (!isGlobal(path)) {
    replacePathScope(path, path.node);

    return false
  }

  this.traverse(path);
}

/**
 * Change the scope of the member expressions
 * @param   { types.NodePath } path - containing the current node visited
 * @returns { boolean } return always false because we want to check only the first node object
 */
function visitMemberExpression(path) {
  const traversePathObject = () => this.traverse(path.get('object'));
  const currentObject = path.node.object;

  switch (true) {
  case isGlobal(path):
    if (currentObject.arguments && currentObject.arguments.length) {
      traversePathObject();
    }
    break
  case !path.value.computed && isIdentifier(currentObject):
    replacePathScope(
      path,
      path.node
    );
    break
  default:
    this.traverse(path);
  }

  return false
}

/**
 * Objects properties should be handled a bit differently from the Identifier
 * @param   { types.NodePath } path - containing the current node visited
 * @returns { boolean } return false if we want to stop the tree traversal
 */
function visitObjectProperty(path) {
  const value = path.node.value;
  const isShorthand = path.node.shorthand;

  if (isIdentifier(value) || isMemberExpression(value) || isShorthand) {
    // disable shorthand object properties
    if (isShorthand) path.node.shorthand = false;

    updateNodeScope.call(this, path.get('value'));
  } else {
    this.traverse(path.get('value'));
  }

  return false
}

/**
 * The this expressions should be replaced with the scope
 * @param   { types.NodePath } path - containing the current node visited
 * @returns { boolean|undefined } return false if we want to stop the tree traversal
 */
function visitThisExpression(path) {
  path.replace(scope);
  this.traverse(path);
}

/**
 * Replace the identifiers with the node scope
 * @param   { types.NodePath } path - containing the current node visited
 * @returns { boolean|undefined } return false if we want to stop the tree traversal
 */
function visitIdentifier(path) {
  const parentValue = path.parent.value;

  if (!isMemberExpression(parentValue) || parentValue.computed) {
    updateNodeScope.call(this, path);
  }

  return false
}

/**
 * Update the scope of the global nodes
 * @param   { Object } ast - ast program
 * @returns { Object } the ast program with all the global nodes updated
 */
function updateNodesScope(ast) {
  const ignorePath = () => false;

  types.visit(ast, {
    visitIdentifier,
    visitMemberExpression,
    visitObjectProperty,
    visitThisExpression,
    visitClassExpression: ignorePath
  });

  return ast
}

/**
 * Convert any expression to an AST tree
 * @param   { Object } expression - expression parsed by the riot parser
 * @param   { string } sourceFile - original tag file
 * @param   { string } sourceCode - original tag source code
 * @returns { Object } the ast generated
 */
function createASTFromExpression(expression, sourceFile, sourceCode) {
  const code = sourceFile ?
    addLineOffset(expression.text, sourceCode, expression) :
    expression.text;

  return generateAST(`(${code})`, {
    sourceFileName: sourceFile
  })
}

/**
 * Create the bindings template property
 * @param   {Array} args - arguments to pass to the template function
 * @returns {ASTNode} a binding template key
 */
function createTemplateProperty(args) {
  return simplePropertyNode(
    BINDING_TEMPLATE_KEY,
    args ? callTemplateFunction(...args) : nullNode()
  )
}

/**
 * Try to get the expression of an attribute node
 * @param   { RiotParser.Node.Attribute } attribute - riot parser attribute node
 * @returns { RiotParser.Node.Expression } attribute expression value
 */
function getAttributeExpression(attribute) {
  return attribute.expressions ? attribute.expressions[0] : {
    // if no expression was found try to typecast the attribute value
    ...attribute,
    text: attribute.value
  }
}

/**
 * Wrap the ast generated in a function call providing the scope argument
 * @param   {Object} ast - function body
 * @returns {FunctionExpresion} function having the scope argument injected
 */
function wrapASTInFunctionWithScope(ast) {
  const fn = builders.arrowFunctionExpression([scope], ast);

  // object expressions need to be wrapped in parenthesis
  // recast doesn't allow it
  // see also https://github.com/benjamn/recast/issues/985
  if (isObjectExpression(ast)) {
    // doing a small hack here
    // trying to figure out how the recast printer works internally
    ast.extra = {
      parenthesized: true
    };
  }

  return fn
}

/**
 * Convert any parser option to a valid template one
 * @param   { RiotParser.Node.Expression } expression - expression parsed by the riot parser
 * @param   { string } sourceFile - original tag file
 * @param   { string } sourceCode - original tag source code
 * @returns { Object } a FunctionExpression object
 *
 * @example
 *  toScopedFunction('foo + bar') // scope.foo + scope.bar
 *
 * @example
 *  toScopedFunction('foo.baz + bar') // scope.foo.baz + scope.bar
 */
function toScopedFunction(expression, sourceFile, sourceCode) {
  return compose(
    wrapASTInFunctionWithScope,
    transformExpression
  )(expression, sourceFile, sourceCode)
}

/**
 * Transform an expression node updating its global scope
 * @param   {RiotParser.Node.Expr} expression - riot parser expression node
 * @param   {string} sourceFile - source file
 * @param   {string} sourceCode - source code
 * @returns {ASTExpression} ast expression generated from the riot parser expression node
 */
function transformExpression(expression, sourceFile, sourceCode) {
  return compose(
    getExpressionAST,
    updateNodesScope,
    createASTFromExpression
  )(expression, sourceFile, sourceCode)
}

/**
 * Get the parsed AST expression of riot expression node
 * @param   {AST.Program} sourceAST - raw node parsed
 * @returns {AST.Expression} program expression output
 */
function getExpressionAST(sourceAST) {
  const astBody = sourceAST.program.body;

  return astBody[0] ? astBody[0].expression : astBody
}

/**
 * Create the template call function
 * @param   {Array|string|Node.Literal} template - template string
 * @param   {Array<AST.Nodes>} bindings - template bindings provided as AST nodes
 * @returns {Node.CallExpression} template call expression
 */
function callTemplateFunction(template, bindings) {
  return builders.callExpression(builders.identifier(TEMPLATE_FN), [
    template ? builders.literal(template) : nullNode(),
    bindings ? builders.arrayExpression(bindings) : nullNode()
  ])
}

/**
 * Create the template wrapper function injecting the dependencies needed to render the component html
 * @param {Array<AST.Nodes>|AST.BlockStatement} body - function body
 * @returns {AST.Node} arrow function expression
 */
const createTemplateDependenciesInjectionWrapper = (body) => builders.arrowFunctionExpression(
  [
    TEMPLATE_FN,
    EXPRESSION_TYPES,
    BINDING_TYPES,
    GET_COMPONENT_FN
  ].map(builders.identifier),
  body
);

/**
 * Convert any DOM attribute into a valid DOM selector useful for the querySelector API
 * @param   { string } attributeName - name of the attribute to query
 * @returns { string } the attribute transformed to a query selector
 */
const attributeNameToDOMQuerySelector = attributeName => `[${attributeName}]`;

/**
 * Create the properties to query a DOM node
 * @param   { string } attributeName - attribute name needed to identify a DOM node
 * @returns { Array<AST.Node> } array containing the selector properties needed for the binding
 */
function createSelectorProperties(attributeName) {
  return attributeName ? [
    simplePropertyNode(BINDING_REDUNDANT_ATTRIBUTE_KEY, builders.literal(attributeName)),
    simplePropertyNode(BINDING_SELECTOR_KEY,
      compose(builders.literal, attributeNameToDOMQuerySelector)(attributeName)
    )
  ] : []
}

/**
 * Clone the node filtering out the selector attribute from the attributes list
 * @param   {RiotParser.Node} node - riot parser node
 * @param   {string} selectorAttribute - name of the selector attribute to filter out
 * @returns {RiotParser.Node} the node with the attribute cleaned up
 */
function cloneNodeWithoutSelectorAttribute(node, selectorAttribute) {
  return {
    ...node,
    attributes: getAttributesWithoutSelector(getNodeAttributes(node), selectorAttribute)
  }
}


/**
 * Get the node attributes without the selector one
 * @param   {Array<RiotParser.Attr>} attributes - attributes list
 * @param   {string} selectorAttribute - name of the selector attribute to filter out
 * @returns {Array<RiotParser.Attr>} filtered attributes
 */
function getAttributesWithoutSelector(attributes, selectorAttribute) {
  if (selectorAttribute)
    return attributes.filter(attribute => attribute.name !== selectorAttribute)

  return attributes
}

/**
 * Clean binding or custom attributes
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {Array<RiotParser.Node.Attr>} only the attributes that are not bindings or directives
 */
function cleanAttributes(node) {
  return getNodeAttributes(node).filter(attribute => ![
    IF_DIRECTIVE,
    EACH_DIRECTIVE,
    KEY_ATTRIBUTE,
    SLOT_ATTRIBUTE,
    IS_DIRECTIVE
  ].includes(attribute.name))
}

/**
 * Root node factory function needed for the top root nodes and the nested ones
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {RiotParser.Node} root node
 */
function rootNodeFactory(node) {
  return {
    nodes: getChildrenNodes(node),
    isRoot: true
  }
}

/**
 * Create a root node proxing only its nodes and attributes
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {RiotParser.Node} root node
 */
function createRootNode(node) {
  return {
    ...rootNodeFactory(node),
    attributes: compose(
      // root nodes should always have attribute expressions
      transformStatiAttributesIntoExpressions,
      // root nodes shouldn't have directives
      cleanAttributes
    )(node)
  }
}

/**
 * Create nested root node. Each and If directives create nested root nodes for example
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {RiotParser.Node} root node
 */
function createNestedRootNode(node) {
  return {
    ...rootNodeFactory(node),
    attributes: cleanAttributes(node)
  }
}

/**
 * Transform the static node attributes into expressions, useful for the root nodes
 * @param   {Array<RiotParser.Node.Attr>} attributes - riot parser node
 * @returns {Array<RiotParser.Node.Attr>} all the attributes received as attribute expressions
 */
function transformStatiAttributesIntoExpressions(attributes) {
  return attributes.map(attribute => {
    if (attribute.expressions) return attribute

    return {
      ...attribute,
      expressions: [{
        start: attribute.valueStart,
        end: attribute.end,
        text: `'${attribute.value || attribute.name}'`
      }]
    }
  })
}

/**
 * Get all the child nodes of a RiotParser.Node
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {Array<RiotParser.Node>} all the child nodes found
 */
function getChildrenNodes(node) {
  return node && node.nodes ? node.nodes : []
}

/**
 * Get all the attributes of a riot parser node
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {Array<RiotParser.Node.Attribute>} all the attributes find
 */
function getNodeAttributes(node) {
  return node.attributes ? node.attributes : []
}

/**
 * Create custom tag name function
 * @param   {RiotParser.Node} node - riot parser node
 * @param   {string} sourceFile - original tag file
 * @param   {string} sourceCode - original tag source code
 * @returns {RiotParser.Node.Attr} the node name as expression attribute
 */
function createCustomNodeNameEvaluationFunction(node, sourceFile, sourceCode) {
  const isAttribute = findIsAttribute(node);
  const toRawString = val => `'${val}'`;

  if (isAttribute) {
    return isAttribute.expressions ? wrapASTInFunctionWithScope(
      mergeAttributeExpressions(isAttribute, sourceFile, sourceCode)
    ) :
      toScopedFunction({
        ...isAttribute,
        text: toRawString(isAttribute.value)
      }, sourceFile, sourceCode)
  }

  return toScopedFunction({ ...node, text: toRawString(getName(node)) }, sourceFile, sourceCode)
}

/**
 * Convert all the node static attributes to strings
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {string} all the node static concatenated as string
 */
function staticAttributesToString(node) {
  return findStaticAttributes(node)
    .map(attribute => attribute[IS_BOOLEAN_ATTRIBUTE] || !attribute.value ?
      attribute.name :
      `${attribute.name}="${unescapeNode(attribute, 'value').value}"`
    ).join(' ')
}

/**
 * Make sure that node escaped chars will be unescaped
 * @param   {RiotParser.Node} node - riot parser node
 * @param   {string} key - key property to unescape
 * @returns {RiotParser.Node} node with the text property unescaped
 */
function unescapeNode(node, key) {
  if (node.unescape) {
    return {
      ...node,
      [key]: unescapeChar(node[key], node.unescape)
    }
  }

  return node
}

/**
 * Convert a riot parser opening node into a string
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {string} the node as string
 */
function nodeToString(node) {
  const attributes = staticAttributesToString(node);

  switch (true) {
  case isTagNode(node):
    return `<${node.name}${attributes ? ` ${attributes}` : ''}${isVoidNode(node) ? '/' : ''}>`
  case isTextNode(node):
    return hasExpressions(node) ? TEXT_NODE_EXPRESSION_PLACEHOLDER : unescapeNode(node, 'text').text
  default:
    return node.text || ''
  }
}

/**
 * Close an html node
 * @param   {RiotParser.Node} node - riot parser node
 * @returns {string} the closing tag of the html tag node passed to this function
 */
function closeTag(node) {
  return node.name ? `</${node.name}>` : ''
}

/**
 * Create a strings array with the `join` call to transform it into a string
 * @param   {Array} stringsArray - array containing all the strings to concatenate
 * @returns {AST.CallExpression} array with a `join` call
 */
function createArrayString(stringsArray) {
  return builders.callExpression(
    builders.memberExpression(
      builders.arrayExpression(stringsArray),
      builders.identifier('join'),
      false
    ),
    [builders.literal('')]
  )
}

/**
 * Simple expression bindings might contain multiple expressions like for example: "class="{foo} red {bar}""
 * This helper aims to merge them in a template literal if it's necessary
 * @param   {RiotParser.Attr} node - riot parser node
 * @param   {string} sourceFile - original tag file
 * @param   {string} sourceCode - original tag source code
 * @returns { Object } a template literal expression object
 */
function mergeAttributeExpressions(node, sourceFile, sourceCode) {
  if (!node.parts || node.parts.length === 1) {
    return transformExpression(node.expressions[0], sourceFile, sourceCode)
  }
  const stringsArray = [
    ...node.parts.reduce((acc, str) => {
      const expression = node.expressions.find(e => e.text.trim() === str);

      return [
        ...acc,
        expression ?
          transformExpression(expression, sourceFile, sourceCode) :
          builders.literal(encodeHTMLEntities(str))
      ]
    }, [])
  ].filter(expr => !isLiteral(expr) || expr.value);


  return createArrayString(stringsArray)
}

/**
 * Create a selector that will be used to find the node via dom-bindings
 * @param   {number} id - temporary variable that will be increased anytime this function will be called
 * @returns {string} selector attribute needed to bind a riot expression
 */
const createBindingSelector = (function createSelector(id = 0) {
  return () => `${BINDING_SELECTOR_PREFIX}${id++}`
}());

/**
 * Create the AST array containing the attributes to bind to this node
 * @param   { RiotParser.Node.Tag } sourceNode - the custom tag
 * @param   { string } selectorAttribute - attribute needed to select the target node
 * @param   { string } sourceFile - source file path
 * @param   { string } sourceCode - original source
 * @returns {AST.ArrayExpression} array containing the slot objects
 */
function createBindingAttributes(sourceNode, selectorAttribute, sourceFile, sourceCode) {
  return builders.arrayExpression([
    ...compose(
      attributes => attributes.map(attribute => createExpression(attribute, sourceFile, sourceCode, 0, sourceNode)),
      attributes => attributes.filter(hasExpressions),
      attributes => getAttributesWithoutSelector(attributes, selectorAttribute),
      cleanAttributes
    )(sourceNode)
  ])
}

/**
 * Create an attribute evaluation function
 * @param   {RiotParser.Attr} sourceNode - riot parser node
 * @param   {string} sourceFile - original tag file
 * @param   {string} sourceCode - original tag source code
 * @returns { AST.Node } an AST function expression to evaluate the attribute value
 */
function createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) {
  return hasExpressions(sourceNode) ?
    // dynamic attribute
    wrapASTInFunctionWithScope(mergeAttributeExpressions(sourceNode, sourceFile, sourceCode)) :
    // static attribute
    builders.arrowFunctionExpression(
      [],
      builders.literal(sourceNode.value || true)
    )
}

/**
 * Return a source map as JSON, it it has not the toJSON method it means it can
 * be used right the way
 * @param   { SourceMapGenerator|Object } map - a sourcemap generator or simply an json object
 * @returns { Object } the source map as JSON
 */
function sourcemapAsJSON(map) {
  if (map && map.toJSON) return map.toJSON()
  return map
}

/**
 * Compose two sourcemaps
 * @param   { SourceMapGenerator } formerMap - original sourcemap
 * @param   { SourceMapGenerator } latterMap - target sourcemap
 * @returns { Object } sourcemap json
 */
function composeSourcemaps(formerMap, latterMap) {
  if (
    isNode() &&
    formerMap && latterMap && latterMap.mappings
  ) {
    return composeSourceMaps(sourcemapAsJSON(formerMap), sourcemapAsJSON(latterMap))
  } else if (isNode() && formerMap) {
    return sourcemapAsJSON(formerMap)
  }

  return {}
}

/**
 * Create a new sourcemap generator
 * @param   { Object } options - sourcemap options
 * @returns { SourceMapGenerator } SourceMapGenerator instance
 */
function createSourcemap(options) {
  return new SourceMapGenerator(options)
}

const Output = Object.freeze({
  code: '',
  ast: [],
  meta: {},
  map: null
});

/**
 * Create the right output data result of a parsing
 * @param   { Object } data - output data
 * @param   { string } data.code - code generated
 * @param   { AST } data.ast - ast representing the code
 * @param   { SourceMapGenerator } data.map - source map generated along with the code
 * @param   { Object } meta - compilation meta infomration
 * @returns { Output } output container object
 */
function createOutput(data, meta) {
  const output = {
    ...Output,
    ...data,
    meta
  };

  if (!output.map && meta && meta.options && meta.options.file)
    return {
      ...output,
      map: createSourcemap({ file: meta.options.file })
    }

  return output
}

/**
 * Transform the source code received via a compiler function
 * @param   { Function } compiler - function needed to generate the output code
 * @param   { Object } meta - compilation meta information
 * @param   { string } source - source code
 * @returns { Output } output - the result of the compiler
 */
function transform(compiler, meta, source) {
  const result = (compiler ? compiler(source, meta) : { code: source });
  return createOutput(result, meta)
}

/**
 * Throw an error with a descriptive message
 * @param   { string } message - error message
 * @returns { undefined } hoppla.. at this point the program should stop working
 */
function panic(message) {
  throw new Error(message)
}

const postprocessors = new Set();

/**
 * Register a postprocessor that will be used after the parsing and compilation of the riot tags
 * @param { Function } postprocessor - transformer that will receive the output code ans sourcemap
 * @returns { Set } the postprocessors collection
 */
function register$1(postprocessor) {
  if (postprocessors.has(postprocessor)) {
    panic(`This postprocessor "${postprocessor.name || postprocessor.toString()}" was already registered`);
  }

  postprocessors.add(postprocessor);

  return postprocessors
}

/**
 * Exec all the postprocessors in sequence combining the sourcemaps generated
 * @param   { Output } compilerOutput - output generated by the compiler
 * @param   { Object } meta - compiling meta information
 * @returns { Output } object containing output code and source map
 */
function execute$1(compilerOutput, meta) {
  return Array.from(postprocessors).reduce(function(acc, postprocessor) {
    const { code, map } = acc;
    const output = postprocessor(code, meta);

    return {
      code: output.code,
      map: composeSourcemaps(map, output.map)
    }
  }, createOutput(compilerOutput, meta))
}

/**
 * Parsers that can be registered by users to preparse components fragments
 * @type { Object }
 */
const preprocessors = Object.freeze({
  javascript: new Map(),
  css: new Map(),
  template: new Map().set('default', code => ({ code }))
});

// throw a processor type error
function preprocessorTypeError(type) {
  panic(`No preprocessor of type "${type}" was found, please make sure to use one of these: 'javascript', 'css' or 'template'`);
}

// throw an error if the preprocessor was not registered
function preprocessorNameNotFoundError(name) {
  panic(`No preprocessor named "${name}" was found, are you sure you have registered it?'`);
}

/**
 * Register a custom preprocessor
 * @param   { string } type - preprocessor type either 'js', 'css' or 'template'
 * @param   { string } name - unique preprocessor id
 * @param   { Function } preprocessor - preprocessor function
 * @returns { Map } - the preprocessors map
 */
function register(type, name, preprocessor) {
  if (!type) panic('Please define the type of preprocessor you want to register \'javascript\', \'css\' or \'template\'');
  if (!name) panic('Please define a name for your preprocessor');
  if (!preprocessor) panic('Please provide a preprocessor function');
  if (!preprocessors[type]) preprocessorTypeError(type);
  if (preprocessors[type].has(name)) panic(`The preprocessor ${name} was already registered before`);

  preprocessors[type].set(name, preprocessor);

  return preprocessors
}

/**
 * Exec the compilation of a preprocessor
 * @param   { string } type - preprocessor type either 'js', 'css' or 'template'
 * @param   { string } name - unique preprocessor id
 * @param   { Object } meta - preprocessor meta information
 * @param   { string } source - source code
 * @returns { Output } object containing a sourcemap and a code string
 */
function execute(type, name, meta, source) {
  if (!preprocessors[type]) preprocessorTypeError(type);
  if (!preprocessors[type].has(name)) preprocessorNameNotFoundError(name);

  return transform(preprocessors[type].get(name), meta, source)
}

/**
 * Simple clone deep function, do not use it for classes or recursive objects!
 * @param   {*} source - possibily an object to clone
 * @returns {*} the object we wanted to clone
 */
function cloneDeep(source) {
  return JSON.parse(JSON.stringify(source))
}

/**
 * Generate the javascript from an ast source
 * @param   {AST} ast - ast object
 * @param   {Object} options - printer options
 * @returns {Object} code + map
 */
function generateJavascript(ast, options) {
  return print(ast, {
    ...options,
    parser: {
      parse: (source, opts) => parse$2(source, {
        ...opts,
        ecmaVersion: 'latest'
      })
    },
    tabWidth: 2,
    wrapColumn: 0,
    quote: 'single'
  })
}

const getEachItemName = expression => isSequenceExpression(expression.left) ? expression.left.expressions[0] : expression.left;
const getEachIndexName = expression => isSequenceExpression(expression.left) ? expression.left.expressions[1] : null;
const getEachValue = expression => expression.right;
const nameToliteral = compose(builders.literal, getName);

const generateEachItemNameKey = expression => simplePropertyNode(
  BINDING_ITEM_NAME_KEY,
  compose(nameToliteral, getEachItemName)(expression)
);

const generateEachIndexNameKey = expression => simplePropertyNode(
  BINDING_INDEX_NAME_KEY,
  compose(nameToliteral, getEachIndexName)(expression)
);

const generateEachEvaluateKey = (expression, eachExpression, sourceFile, sourceCode) => simplePropertyNode(
  BINDING_EVALUATE_KEY,
  compose(
    e => toScopedFunction(e, sourceFile, sourceCode),
    e => ({
      ...eachExpression,
      text: generateJavascript(e).code
    }),
    getEachValue
  )(expression)
);

/**
 * Get the each expression properties to create properly the template binding
 * @param   { DomBinding.Expression } eachExpression - original each expression data
 * @param   { string } sourceFile - original tag file
 * @param   { string } sourceCode - original tag source code
 * @returns { Array } AST nodes that are needed to build an each binding
 */
function generateEachExpressionProperties(eachExpression, sourceFile, sourceCode) {
  const ast = createASTFromExpression(eachExpression, sourceFile, sourceCode);
  const body = ast.program.body;
  const firstNode = body[0];

  if (!isExpressionStatement(firstNode)) {
    panic(`The each directives supported should be of type "ExpressionStatement",you have provided a "${firstNode.type}"`);
  }

  const { expression } = firstNode;

  return [
    generateEachItemNameKey(expression),
    generateEachIndexNameKey(expression),
    generateEachEvaluateKey(expression, eachExpression, sourceFile, sourceCode)
  ]
}

/**
 * Transform a RiotParser.Node.Tag into an each binding
 * @param   { RiotParser.Node.Tag } sourceNode - tag containing the each attribute
 * @param   { string } selectorAttribute - attribute needed to select the target node
 * @param   { string } sourceFile - source file path
 * @param   { string } sourceCode - original source
 * @returns { AST.Node } an each binding node
 */
function createEachBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) {
  const [ifAttribute, eachAttribute, keyAttribute] = [
    findIfAttribute,
    findEachAttribute,
    findKeyAttribute
  ].map(f => f(sourceNode));
  const attributeOrNull = attribute => attribute ? toScopedFunction(getAttributeExpression(attribute), sourceFile, sourceCode) : nullNode();

  return builders.objectExpression([
    simplePropertyNode(BINDING_TYPE_KEY,
      builders.memberExpression(
        builders.identifier(BINDING_TYPES),
        builders.identifier(EACH_BINDING_TYPE),
        false
      )
    ),
    simplePropertyNode(BINDING_GET_KEY_KEY, attributeOrNull(keyAttribute)),
    simplePropertyNode(BINDING_CONDITION_KEY, attributeOrNull(ifAttribute)),
    createTemplateProperty(createNestedBindings(sourceNode, sourceFile, sourceCode, selectorAttribute)),
    ...createSelectorProperties(selectorAttribute),
    ...compose(generateEachExpressionProperties, getAttributeExpression)(eachAttribute)
  ])
}

/**
 * Transform a RiotParser.Node.Tag into an if binding
 * @param   { RiotParser.Node.Tag } sourceNode - tag containing the if attribute
 * @param   { string } selectorAttribute - attribute needed to select the target node
 * @param   { stiring } sourceFile - source file path
 * @param   { string } sourceCode - original source
 * @returns { AST.Node } an if binding node
 */
function createIfBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) {
  const ifAttribute = findIfAttribute(sourceNode);

  return builders.objectExpression([
    simplePropertyNode(BINDING_TYPE_KEY,
      builders.memberExpression(
        builders.identifier(BINDING_TYPES),
        builders.identifier(IF_BINDING_TYPE),
        false
      )
    ),
    simplePropertyNode(
      BINDING_EVALUATE_KEY,
      toScopedFunction(ifAttribute.expressions[0], sourceFile, sourceCode)
    ),
    ...createSelectorProperties(selectorAttribute),
    createTemplateProperty(createNestedBindings(sourceNode, sourceFile, sourceCode, selectorAttribute))
  ])
}

/**
 * Create the text node expressions
 * @param   {RiotParser.Node} sourceNode - any kind of node parsed via riot parser
 * @param   {string} sourceFile - source file path
 * @param   {string} sourceCode - original source
 * @returns {Array} array containing all the text node expressions
 */
function createTextNodeExpressions(sourceNode, sourceFile, sourceCode) {
  const childrenNodes = getChildrenNodes(sourceNode);

  return childrenNodes
    .filter(isTextNode)
    .filter(hasExpressions)
    .map(node => createExpression(
      node,
      sourceFile,
      sourceCode,
      childrenNodes.indexOf(node),
      sourceNode
    ))
}

/**
 * Add a simple binding to a riot parser node
 * @param   { RiotParser.Node.Tag } sourceNode - tag containing the if attribute
 * @param   { string } selectorAttribute - attribute needed to select the target node
 * @param   { string } sourceFile - source file path
 * @param   { string } sourceCode - original source
 * @returns { AST.Node } an each binding node
 */
function createSimpleBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) {
  return builders.objectExpression([
    // root or removable nodes do not need selectors
    ...(isRemovableNode(sourceNode) || isRootNode(sourceNode) ? [] : createSelectorProperties(selectorAttribute)),
    simplePropertyNode(
      BINDING_EXPRESSIONS_KEY,
      builders.arrayExpression([
        ...createTextNodeExpressions(sourceNode, sourceFile, sourceCode),
        ...createAttributeExpressions(sourceNode, sourceFile, sourceCode)
      ])
    )
  ])
}

/**
 * Transform a RiotParser.Node.Tag of type slot into a slot binding
 * @param   { RiotParser.Node.Tag } sourceNode - slot node
 * @param   { string } selectorAttribute - attribute needed to select the target node
 * @param   { string } sourceFile - source file path
 * @param   { string } sourceCode - original source
 * @returns { AST.Node } a slot binding node
 */
function createSlotBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) {
  const slotNameAttribute = findAttribute(NAME_ATTRIBUTE, sourceNode);
  const slotName = slotNameAttribute ? slotNameAttribute.value : DEFAULT_SLOT_NAME;

  return builders.objectExpression([
    simplePropertyNode(BINDING_TYPE_KEY,
      builders.memberExpression(
        builders.identifier(BINDING_TYPES),
        builders.identifier(SLOT_BINDING_TYPE),
        false
      )
    ),
    simplePropertyNode(
      BINDING_ATTRIBUTES_KEY,
      createBindingAttributes(
        {
          ...sourceNode,
          // filter the name attribute
          attributes: getNodeAttributes(sourceNode)
            .filter(attribute => getName(attribute) !== NAME_ATTRIBUTE)
        },
        selectorAttribute,
        sourceFile,
        sourceCode
      )
    ),
    simplePropertyNode(
      BINDING_NAME_KEY,
      builders.literal(slotName)
    ),
    ...createSelectorProperties(selectorAttribute)
  ])
}

/**
 * Find the slots in the current component and group them under the same id
 * @param   {RiotParser.Node.Tag} sourceNode - the custom tag
 * @returns {Object} object containing all the slots grouped by name
 */
function groupSlots(sourceNode) {
  return getChildrenNodes(sourceNode).reduce((acc, node) => {
    const slotAttribute = findSlotAttribute(node);

    if (slotAttribute) {
      acc[slotAttribute.value] = node;
    } else {
      acc.default = createNestedRootNode({
        nodes: [...getChildrenNodes(acc.default), node]
      });
    }

    return acc
  }, {
    default: null
  })
}

/**
 * Create the slot entity to pass to the riot-dom bindings
 * @param   {string} id - slot id
 * @param   {RiotParser.Node.Tag} sourceNode - slot root node
 * @param   {string} sourceFile - source file path
 * @param   {string} sourceCode - original source
 * @returns {AST.Node} ast node containing the slot object properties
 */
function buildSlot(id, sourceNode, sourceFile, sourceCode) {
  const cloneNode = {
    ...sourceNode,
    attributes: getNodeAttributes(sourceNode)
  };
  const [html, bindings] = build(cloneNode, sourceFile, sourceCode);

  return builders.objectExpression([
    simplePropertyNode(BINDING_ID_KEY, builders.literal(id)),
    simplePropertyNode(BINDING_HTML_KEY, builders.literal(html)),
    simplePropertyNode(BINDING_BINDINGS_KEY, builders.arrayExpression(bindings))
  ])
}

/**
 * Create the AST array containing the slots
 * @param   { RiotParser.Node.Tag } sourceNode - the custom tag
 * @param   { string } sourceFile - source file path
 * @param   { string } sourceCode - original source
 * @returns {AST.ArrayExpression} array containing the attributes to bind
 */
function createSlotsArray(sourceNode, sourceFile, sourceCode) {
  return builders.arrayExpression([
    ...compose(
      slots => slots.map(([key, value]) => buildSlot(key, value, sourceFile, sourceCode)),
      slots => slots.filter(([, value]) => value),
      Object.entries,
      groupSlots
    )(sourceNode)
  ])
}

/**
 * Find the slot attribute if it exists
 * @param   {RiotParser.Node.Tag} sourceNode - the custom tag
 * @returns {RiotParser.Node.Attr|undefined} the slot attribute found
 */
function findSlotAttribute(sourceNode) {
  return getNodeAttributes(sourceNode).find(attribute => attribute.name === SLOT_ATTRIBUTE)
}

/**
 * Transform a RiotParser.Node.Tag into a tag binding
 * @param   { RiotParser.Node.Tag } sourceNode - the custom tag
 * @param   { string } selectorAttribute - attribute needed to select the target node
 * @param   { string } sourceFile - source file path
 * @param   { string } sourceCode - original source
 * @returns { AST.Node } tag binding node
 */
function createTagBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) {
  return builders.objectExpression([
    simplePropertyNode(BINDING_TYPE_KEY,
      builders.memberExpression(
        builders.identifier(BINDING_TYPES),
        builders.identifier(TAG_BINDING_TYPE),
        false
      )
    ),
    simplePropertyNode(BINDING_GET_COMPONENT_KEY, builders.identifier(GET_COMPONENT_FN)),
    simplePropertyNode(
      BINDING_EVALUATE_KEY,
      createCustomNodeNameEvaluationFunction(sourceNode, sourceFile, sourceCode)
    ),
    simplePropertyNode(BINDING_SLOTS_KEY, createSlotsArray(sourceNode, sourceFile, sourceCode)),
    simplePropertyNode(
      BINDING_ATTRIBUTES_KEY,
      createBindingAttributes(sourceNode, selectorAttribute, sourceFile, sourceCode)
    ),
    ...createSelectorProperties(selectorAttribute)
  ])
}

const BuildingState = Object.freeze({
  html: [],
  bindings: [],
  parent: null
});

/**
 * Nodes having bindings should be cloned and new selector properties should be added to them
 * @param   {RiotParser.Node} sourceNode - any kind of node parsed via riot parser
 * @param   {string} bindingsSelector - temporary string to identify the current node
 * @returns {RiotParser.Node} the original node parsed having the new binding selector attribute
 */
function createBindingsTag(sourceNode, bindingsSelector) {
  if (!bindingsSelector) return sourceNode

  return {
    ...sourceNode,
    // inject the selector bindings into the node attributes
    attributes: [{
      name: bindingsSelector,
      value: bindingsSelector
    }, ...getNodeAttributes(sourceNode)]
  }
}

/**
 * Create a generic dynamic node (text or tag) and generate its bindings
 * @param   {RiotParser.Node} sourceNode - any kind of node parsed via riot parser
 * @param   {string} sourceFile - source file path
 * @param   {string} sourceCode - original source
 * @param   {BuildingState} state - state representing the current building tree state during the recursion
 * @returns {Array} array containing the html output and bindings for the current node
 */
function createDynamicNode(sourceNode, sourceFile, sourceCode, state) {
  switch (true) {
  case isTextNode(sourceNode):
    // text nodes will not have any bindings
    return [nodeToString(sourceNode), []]
  default:
    return createTagWithBindings(sourceNode, sourceFile, sourceCode)
  }
}

/**
 * Create only a dynamic tag node with generating a custom selector and its bindings
 * @param   {RiotParser.Node} sourceNode - any kind of node parsed via riot parser
 * @param   {string} sourceFile - source file path
 * @param   {string} sourceCode - original source
 * @param   {BuildingState} state - state representing the current building tree state during the recursion
 * @returns {Array} array containing the html output and bindings for the current node
 */
function createTagWithBindings(sourceNode, sourceFile, sourceCode) {
  const bindingsSelector = isRootNode(sourceNode) ? null : createBindingSelector();
  const cloneNode = createBindingsTag(sourceNode, bindingsSelector);
  const tagOpeningHTML = nodeToString(cloneNode);

  switch (true) {
  case hasEachAttribute(cloneNode):
    // EACH bindings have prio 1
    return [tagOpeningHTML, [createEachBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]]
  case hasIfAttribute(cloneNode):
    // IF bindings have prio 2
    return [tagOpeningHTML, [createIfBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]]
  case isCustomNode(cloneNode):
    // TAG bindings have prio 3
    return [tagOpeningHTML, [createTagBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]]
  case isSlotNode(cloneNode):
    // slot tag
    return [tagOpeningHTML, [createSlotBinding(cloneNode, bindingsSelector)]]
  default:
    // this node has expressions bound to it
    return [tagOpeningHTML, [createSimpleBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]]
  }
}

/**
 * Parse a node trying to extract its template and bindings
 * @param   {RiotParser.Node} sourceNode - any kind of node parsed via riot parser
 * @param   {string} sourceFile - source file path
 * @param   {string} sourceCode - original source
 * @param   {BuildingState} state - state representing the current building tree state during the recursion
 * @returns {Array} array containing the html output and bindings for the current node
 */
function parseNode(sourceNode, sourceFile, sourceCode, state) {
  // static nodes have no bindings
  if (isStaticNode(sourceNode)) return [nodeToString(sourceNode), []]
  return createDynamicNode(sourceNode, sourceFile, sourceCode)
}

/**
 * Create the tag binding
 * @param   { RiotParser.Node.Tag } sourceNode - tag containing the each attribute
 * @param   { string } sourceFile - source file path
 * @param   { string } sourceCode - original source
 * @param   { string } selector - binding selector
 * @returns { Array } array with only the tag binding AST
 */
function createNestedBindings(sourceNode, sourceFile, sourceCode, selector) {
  const mightBeARiotComponent = isCustomNode(sourceNode);
  const node = cloneNodeWithoutSelectorAttribute(sourceNode, selector);

  return mightBeARiotComponent ? [null, [
    createTagBinding(
      node,
      null,
      sourceFile,
      sourceCode
    )]
  ] : build(createNestedRootNode(node), sourceFile, sourceCode)
}

/**
 * Build the template and the bindings
 * @param   {RiotParser.Node} sourceNode - any kind of node parsed via riot parser
 * @param   {string} sourceFile - source file path
 * @param   {string} sourceCode - original source
 * @param   {BuildingState} state - state representing the current building tree state during the recursion
 * @returns {Array} array containing the html output and the dom bindings
 */
function build(
  sourceNode,
  sourceFile,
  sourceCode,
  state
) {
  if (!sourceNode) panic('Something went wrong with your tag DOM parsing, your tag template can\'t be created');

  const [nodeHTML, nodeBindings] = parseNode(sourceNode, sourceFile, sourceCode);
  const childrenNodes = getChildrenNodes(sourceNode);
  const canRenderNodeHTML = isRemovableNode(sourceNode) === false;
  const currentState = { ...cloneDeep(BuildingState), ...state };

  // mutate the original arrays
  canRenderNodeHTML && currentState.html.push(...nodeHTML);
  currentState.bindings.push(...nodeBindings);

  // do recursion if
  // this tag has children and it has no special directives bound to it
  if (childrenNodes.length && !hasItsOwnTemplate(sourceNode)) {
    childrenNodes.forEach(node => build(node, sourceFile, sourceCode, { parent: sourceNode, ...currentState }));
  }

  // close the tag if it's not a void one
  if (canRenderNodeHTML && isTagNode(sourceNode) && !isVoidNode(sourceNode)) {
    currentState.html.push(closeTag(sourceNode));
  }

  return [
    currentState.html.join(''),
    currentState.bindings
  ]
}

const ATTRIBUTE_TYPE_NAME = 'type';

/**
 * Get the type attribute from a node generated by the riot parser
 * @param   { Object} sourceNode - riot parser node
 * @returns { string|null } a valid type to identify the preprocessor to use or nothing
 */
function getPreprocessorTypeByAttribute(sourceNode) {
  const typeAttribute = sourceNode.attributes ?
    sourceNode.attributes.find(attribute => attribute.name === ATTRIBUTE_TYPE_NAME) :
    null;

  return typeAttribute ? normalize(typeAttribute.value) : null
}


/**
 * Remove the noise in case a user has defined the preprocessor type='text/scss'
 * @param   { string } value - input string
 * @returns { string } normalized string
 */
function normalize(value) {
  return value.replace('text/', '')
}

/**
 * Preprocess a riot parser node
 * @param   { string } preprocessorType - either css, js
 * @param   { string } preprocessorName - preprocessor id
 * @param   { Object } meta - compilation meta information
 * @param   { RiotParser.nodeTypes } node - css node detected by the parser
 * @returns { Output } code and sourcemap generated by the preprocessor
 */
function preprocess(preprocessorType, preprocessorName, meta, node) {
  const code = node.text;

  return (preprocessorName ?
    execute(preprocessorType, preprocessorName, meta, code) :
    { code }
  )
}

/**
 * Matches valid, multiline JavaScript comments in almost all its forms.
 * @const {RegExp}
 * @static
 */
const R_MLCOMMS = /\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//g;

/**
 * Source for creating regexes matching valid quoted, single-line JavaScript strings.
 * It recognizes escape characters, including nested quotes and line continuation.
 * @const {string}
 */
const S_LINESTR = /"[^"\n\\]*(?:\\[\S\s][^"\n\\]*)*"|'[^'\n\\]*(?:\\[\S\s][^'\n\\]*)*'/.source;

/**
 * Matches CSS selectors, excluding those beginning with '@' and quoted strings.
 * @const {RegExp}
 */

const CSS_SELECTOR = RegExp(`([{}]|^)[; ]*((?:[^@ ;{}][^{}]*)?[^@ ;{}:] ?)(?={)|${S_LINESTR}`, 'g');

/**
 * Parses styles enclosed in a "scoped" tag
 * The "css" string is received without comments or surrounding spaces.
 *
 * @param   {string} tag - Tag name of the root element
 * @param   {string} css - The CSS code
 * @returns {string} CSS with the styles scoped to the root element
 */
function scopedCSS(tag, css) {
  const host = ':host';
  const selectorsBlacklist = ['from', 'to'];

  return css.replace(CSS_SELECTOR, function(m, p1, p2) {
    // skip quoted strings
    if (!p2) return m

    // we have a selector list, parse each individually
    p2 = p2.replace(/[^,]+/g, function(sel) {
      const s = sel.trim();

      // skip selectors already using the tag name
      if (s.indexOf(tag) === 0) {
        return sel
      }

      // skips the keywords and percents of css animations
      if (!s || selectorsBlacklist.indexOf(s) > -1 || s.slice(-1) === '%') {
        return sel
      }

      // replace the `:host` pseudo-selector, where it is, with the root tag name;
      // if `:host` was not included, add the tag name as prefix, and mirror all
      // `[data-is]`
      if (s.indexOf(host) < 0) {
        return `${tag} ${s},[is="${tag}"] ${s}`
      } else {
        return `${s.replace(host, tag)},${
          s.replace(host, `[is="${tag}"]`)}`
      }
    });

    // add the danling bracket char and return the processed selector list
    return p1 ? `${p1} ${p2}` : p2
  })
}

/**
 * Remove comments, compact and trim whitespace
 * @param { string } code - compiled css code
 * @returns { string } css code normalized
 */
function compactCss(code) {
  return code.replace(R_MLCOMMS, '').replace(/\s+/g, ' ').trim()
}

const escapeBackslashes = s => s.replace(/\\/g, '\\\\');
const escapeIdentifier = identifier => escapeBackslashes(cssEscape(identifier, {
  isIdentifier: true
}));

/**
 * Generate the component css
 * @param   { Object } sourceNode - node generated by the riot compiler
 * @param   { string } source - original component source code
 * @param   { Object } meta - compilation meta information
 * @param   { AST } ast - current AST output
 * @returns { AST } the AST generated
 */
function css(sourceNode, source, meta, ast) {
  const preprocessorName = getPreprocessorTypeByAttribute(sourceNode);
  const { options } = meta;
  const preprocessorOutput = preprocess('css', preprocessorName, meta, sourceNode.text);
  const normalizedCssCode = compactCss(preprocessorOutput.code);
  const escapedCssIdentifier = escapeIdentifier(meta.tagName);

  const cssCode = (options.scopedCss ?
    scopedCSS(escapedCssIdentifier, escapeBackslashes(normalizedCssCode)) :
    escapeBackslashes(normalizedCssCode)
  ).trim();

  types.visit(ast, {
    visitProperty(path) {
      if (path.value.key.name === TAG_CSS_PROPERTY) {
        path.value.value = builders.templateLiteral(
          [builders.templateElement({ raw: cssCode, cooked: '' }, false)],
          []
        );

        return false
      }

      this.traverse(path);
    }
  });

  return ast
}

/**
 * Find whether there is html code outside of the root node
 * @param   {RiotParser.Node} root - node generated by the riot compiler
 * @param   {string}  code - riot tag source code
 * @param   {Function} parse - riot parser function
 * @returns {boolean} true if extra markup is detected
 */
function hasHTMLOutsideRootNode(root, code, parse) {
  const additionalCode = root ? [
    // head
    code.substr(0, root.start),
    // tail
    code.substr(root.end, code.length)
  ].join('').trim() : '';

  if (additionalCode) {
    // if there are parsing errors we assume that there are no html
    // tags outside of the root node
    try {
      const { template, javascript, css } = parse(additionalCode).output;

      return [template, javascript, css].some(isObject)
    } catch (error) {
      return false
    }
  }

  return false
}

/**
 * Ckeck if an Array-like object has empty length
 * @param {Array} target - Array-like object
 * @returns {boolean} target is empty or null
 */
function isEmptyArray(target) {
  return !target || !target.length
}

/**
 * True if the sourcemap has no mappings, it is empty
 * @param   {Object}  map - sourcemap json
 * @returns {boolean} true if empty
 */
function isEmptySourcemap(map) {
  return !map || isEmptyArray(map.mappings)
}

/**
 * Find the export default statement
 * @param   { Array } body - tree structure containing the program code
 * @returns { Object } node containing only the code of the export default statement
 */
function findExportDefaultStatement(body) {
  return body.find(isExportDefaultStatement)
}

/**
 * Find all import declarations
 * @param   { Array } body - tree structure containing the program code
 * @returns { Array } array containing all the import declarations detected
 */
function findAllImportDeclarations(body) {
  return body.filter(isImportDeclaration)
}

/**
 * Find all the named export declarations
 * @param   { Array } body - tree structure containing the program code
 * @returns { Array } array containing all the named export declarations detected
 */
function findAllExportNamedDeclarations(body) {
  return body.filter(isExportNamedDeclaration)
}

/**
 * Filter all the import declarations
 * @param   { Array } body - tree structure containing the program code
 * @returns { Array } array containing all the ast expressions without the import declarations
 */
function filterOutAllImportDeclarations(body) {
  return body.filter(n => !isImportDeclaration(n))
}

/**
 * Filter all the export declarations
 * @param   { Array } body - tree structure containing the program code
 * @returns { Array } array containing all the ast expressions without the export declarations
 */
function filterOutAllExportDeclarations(body) {
  return body.filter(n => !isExportNamedDeclaration(n) || isExportDefaultStatement(n))
}

/**
 * Find the component interface exported
 * @param   { Array } body - tree structure containing the program code
 * @returns { Object|null } the object referencing the component interface if found
 */
function findComponentInterface(body) {
  const exportNamedDeclarations = body.filter(isExportNamedDeclaration).map(n => n.declaration);
  const types = exportNamedDeclarations.filter(isTypeAliasDeclaration);
  const interfaces = exportNamedDeclarations.filter(isInterfaceDeclaration);
  const isRiotComponentTypeName = ({ typeName }) => typeName && typeName.name ? typeName.name === RIOT_TAG_INTERFACE_NAME : false;
  const extendsRiotComponent = ({ expression }) => expression.name === RIOT_TAG_INTERFACE_NAME;

  return types.find(
    node => (node.typeAnnotation.types && node.typeAnnotation.types.some(isRiotComponentTypeName)) || isRiotComponentTypeName(node.typeAnnotation)
  ) || interfaces.find(
    node =>  node.extends && node.extends.some(extendsRiotComponent)
  )
}

/**
 * Add the component interface to the export declaration
 * @param   { Object } ast - ast object generated by recast
 * @param   { Object } componentInterface - the component typescript interface
 * @returns { Object } the component object exported combined with the riot typescript interfaces
 */
function addComponentInterfaceToExportedObject(ast, componentInterface) {
  const body = getProgramBody(ast);
  const RiotComponentWrapperImportSpecifier = builders.importSpecifier(
    builders.identifier(RIOT_INTERFACE_WRAPPER_NAME)
  );
  const componentInterfaceName = componentInterface.id.name;
  const riotImportDeclaration = findAllImportDeclarations(body).find(node => node.source.value === RIOT_MODULE_ID);
  const exportDefaultStatement = body.find(isExportDefaultStatement);
  const objectExport = exportDefaultStatement.declaration;

  // add the RiotComponentWrapper to this component imports
  if (riotImportDeclaration) {
    riotImportDeclaration.specifiers.push(RiotComponentWrapperImportSpecifier);
  } else {
    // otherwise create the whole import statement from riot
    body.unshift(0, builders.importDeclaration(
      [RiotComponentWrapperImportSpecifier],
      builders.stringLiteral(RIOT_MODULE_ID)
    ));
  }

  // override the object export adding the types detected
  exportDefaultStatement.declaration = builders.tsAsExpression(
    objectExport,
    builders.tsTypeReference(
      builders.identifier(RIOT_INTERFACE_WRAPPER_NAME),
      builders.tsTypeParameterInstantiation(
        [builders.tsTypeReference(builders.identifier(componentInterfaceName))]
      )
    )
  );

  return ast
}

/**
 * Create the default export declaration interpreting the old riot syntax relying on "this" statements
 * @param   { Array } body - tree structure containing the program code
 * @returns { Object } ExportDefaultDeclaration
 */
function createDefaultExportFromLegacySyntax(body) {
  return builders.exportDefaultDeclaration(
    builders.functionDeclaration(
      builders.identifier(TAG_LOGIC_PROPERTY),
      [],
      builders.blockStatement([
        ...compose(filterOutAllImportDeclarations, filterOutAllExportDeclarations)(body),
        builders.returnStatement(builders.thisExpression())
      ])
    )
  )
}

/**
 * Find all the code in an ast program except for the export default statements
 * @param   { Array } body - tree structure containing the program code
 * @returns { Array } array containing all the program code except the export default expressions
 */
function filterNonExportDefaultStatements(body) {
  return body.filter(node => !isExportDefaultStatement(node) && !isThisExpressionStatement(node))
}

/**
 * Get the body of the AST structure
 * @param   { Object } ast - ast object generated by recast
 * @returns { Array } array containing the program code
 */
function getProgramBody(ast) {
  return ast.body || ast.program.body
}

/**
 * Extend the AST adding the new tag method containing our tag sourcecode
 * @param   { Object } ast - current output ast
 * @param   { Object } exportDefaultNode - tag export default node
 * @returns { Object } the output ast having the "tag" key extended with the content of the export default
 */
function extendTagProperty(ast, exportDefaultNode) {
  types.visit(ast, {
    visitProperty(path) {
      if (path.value.key.name === TAG_LOGIC_PROPERTY) {
        path.value.value = exportDefaultNode.declaration;
        return false
      }

      this.traverse(path);
    }
  });

  return ast
}

/**
 * Generate the component javascript logic
 * @param   { Object } sourceNode - node generated by the riot compiler
 * @param   { string } source - original component source code
 * @param   { Object } meta - compilation meta information
 * @param   { AST } ast - current AST output
 * @returns { AST } the AST generated
 */
function javascript(sourceNode, source, meta, ast) {
  const preprocessorName = getPreprocessorTypeByAttribute(sourceNode);
  const javascriptNode = addLineOffset(sourceNode.text.text, source, sourceNode);
  const { options } = meta;
  const preprocessorOutput = preprocess('javascript', preprocessorName, meta, {
    ...sourceNode,
    text: javascriptNode
  });
  const inputSourceMap = sourcemapAsJSON(preprocessorOutput.map);
  const generatedAst = generateAST(preprocessorOutput.code, {
    sourceFileName: options.file,
    inputSourceMap: isEmptySourcemap(inputSourceMap) ? null : inputSourceMap
  });
  const generatedAstBody = getProgramBody(generatedAst);
  const exportDefaultNode = findExportDefaultStatement(generatedAstBody);
  const isLegacyRiotSyntax = isNil(exportDefaultNode);
  const outputBody = getProgramBody(ast);
  const componentInterface = findComponentInterface(generatedAstBody);

  // throw in case of mixed component exports
  if (exportDefaultNode && generatedAstBody.some(isThisExpressionStatement))
    throw new Error('You can\t use "export default {}" and root this statements in the same component')

  // add to the ast the "private" javascript content of our tag script node
  outputBody.unshift(
    ...(
      // for the legacy riot syntax we need to move all the import and (named) export statements outside of the function body
      isLegacyRiotSyntax ?
        [...findAllImportDeclarations(generatedAstBody), ...findAllExportNamedDeclarations(generatedAstBody)] :
        // modern riot syntax will hoist all the private stuff outside of the export default statement
        filterNonExportDefaultStatements(generatedAstBody)
    ));

  // create the public component export properties from the root this statements
  if (isLegacyRiotSyntax) extendTagProperty(
    ast,
    createDefaultExportFromLegacySyntax(generatedAstBody)
  );

  // convert the export default adding its content to the component property exported
  if (exportDefaultNode) extendTagProperty(ast, exportDefaultNode);

  return componentInterface ?
    // add the component interface to the component object exported
    addComponentInterfaceToExportedObject(ast, componentInterface) :
    ast
}

/**
 * Create the content of the template function
 * @param   { RiotParser.Node } sourceNode - node generated by the riot compiler
 * @param   { string } sourceFile - source file path
 * @param   { string } sourceCode - original source
 * @returns {AST.BlockStatement} the content of the template function
 */
function createTemplateFunctionContent(sourceNode, sourceFile, sourceCode) {
  return callTemplateFunction(
    ...build(
      createRootNode(sourceNode),
      sourceFile,
      sourceCode
    )
  )
}

/**
 * Extend the AST adding the new template property containing our template call to render the component
 * @param   { Object } ast - current output ast
 * @param   { string } sourceFile - source file path
 * @param   { string } sourceCode - original source
 * @param   { RiotParser.Node } sourceNode - node generated by the riot compiler
 * @returns { Object } the output ast having the "template" key
 */
function extendTemplateProperty(ast, sourceFile, sourceCode, sourceNode) {
  types.visit(ast, {
    visitProperty(path) {
      if (path.value.key.name === TAG_TEMPLATE_PROPERTY) {
        path.value.value = createTemplateDependenciesInjectionWrapper(
          createTemplateFunctionContent(sourceNode, sourceFile, sourceCode)
        );

        return false
      }

      this.traverse(path);
    }
  });

  return ast
}

/**
 * Generate the component template logic
 * @param   { RiotParser.Node } sourceNode - node generated by the riot compiler
 * @param   { string } source - original component source code
 * @param   { Object } meta - compilation meta information
 * @param   { AST } ast - current AST output
 * @returns { AST } the AST generated
 */
function template(sourceNode, source, meta, ast) {
  const { options } = meta;
  return extendTemplateProperty(ast, options.file, source, sourceNode)
}

const DEFAULT_OPTIONS = {
  template: 'default',
  file: '[unknown-source-file]',
  scopedCss: true
};

/**
 * Create the initial AST
 * @param {string} tagName - the name of the component we have compiled
 * @returns { AST } the initial AST
 *
 * @example
 * // the output represents the following string in AST
 */
function createInitialInput({ tagName }) {
  /*
  generates
  export default {
     ${TAG_CSS_PROPERTY}: null,
     ${TAG_LOGIC_PROPERTY}: null,
     ${TAG_TEMPLATE_PROPERTY}: null
  }
  */
  return builders.program([
    builders.exportDefaultDeclaration(
      builders.objectExpression([
        simplePropertyNode(TAG_CSS_PROPERTY, nullNode()),
        simplePropertyNode(TAG_LOGIC_PROPERTY, nullNode()),
        simplePropertyNode(TAG_TEMPLATE_PROPERTY, nullNode()),
        simplePropertyNode(TAG_NAME_PROPERTY, builders.literal(tagName))
      ])
    )]
  )
}

/**
 * Make sure the input sourcemap is valid otherwise we ignore it
 * @param   {SourceMapGenerator} map - preprocessor source map
 * @returns {Object} sourcemap as json or nothing
 */
function normaliseInputSourceMap(map) {
  const inputSourceMap = sourcemapAsJSON(map);
  return isEmptySourcemap(inputSourceMap) ? null : inputSourceMap
}

/**
 * Override the sourcemap content making sure it will always contain the tag source code
 * @param   {Object} map - sourcemap as json
 * @param   {string} source - component source code
 * @returns {Object} original source map with the "sourcesContent" property overridden
 */
function overrideSourcemapContent(map, source) {
  return {
    ...map,
    sourcesContent: [source]
  }
}

/**
 * Create the compilation meta object
 * @param { string } source - source code of the tag we will need to compile
 * @param { string } options - compiling options
 * @returns {Object} meta object
 */
function createMeta(source, options) {
  return {
    tagName: null,
    fragments: null,
    options: {
      ...DEFAULT_OPTIONS,
      ...options
    },
    source
  }
}

/**
 * Parse a string to simply get its template AST
 * @param { string } source - string to parse
 * @param { Object } options - parser options
 * @returns {Object} riot parser template output
 */
const parseSimpleString = (source, options) => {
  const { parse } = parser(options);
  return parse(source).output.template
};

/**
 * Generate the component slots creation function from the root node
 * @param { string } source - component outer html
 * @param { Object } parserOptions - riot parser options
 * @returns { string } content of the function that can be used to crate the slots in runtime
 */
function generateSlotsFromString(source, parserOptions) {
  return compose(
    ({ code }) => code,
    generateJavascript,
    createTemplateDependenciesInjectionWrapper,
    createSlotsArray
  )(parseSimpleString(source, parserOptions), DEFAULT_OPTIONS.file, source)
}

/**
 * Generate the Riot.js binding template function from a template string
 * @param { string } source - template string
 * @param { Object } parserOptions - riot parser options
 * @returns { string } Riot.js bindings template function generated
 */
function generateTemplateFunctionFromString(source, parserOptions) {
  return compose(
    ({ code }) => code,
    generateJavascript,
    callTemplateFunction
  )(
    ...build(
      parseSimpleString(source, parserOptions),
      DEFAULT_OPTIONS.file,
      source
    )
  )
}

/**
 * Generate the output code source together with the sourcemap
 * @param { string } source - source code of the tag we will need to compile
 * @param { Object } opts - compiling options
 * @returns { Output } object containing output code and source map
 */
function compile(source, opts = {}) {
  const meta = createMeta(source, opts);
  const { options } = meta;
  const { code, map } = execute('template', options.template, meta, source);
  const { parse } = parser(options);
  const { template: template$1, css: css$1, javascript: javascript$1 } = parse(code).output;

  // see also https://github.com/riot/compiler/issues/130
  if (hasHTMLOutsideRootNode(template$1 || css$1 || javascript$1, code, parse)) {
    throw new Error('Multiple HTML root nodes are not supported')
  }

  // extend the meta object with the result of the parsing
  Object.assign(meta, {
    tagName: template$1.name,
    fragments: { template: template$1, css: css$1, javascript: javascript$1 }
  });

  return compose(
    result => ({ ...result, meta }),
    result => execute$1(result, meta),
    result => ({
      ...result,
      map: overrideSourcemapContent(result.map, source)
    }),
    ast => meta.ast = ast && generateJavascript(ast, {
      sourceMapName: `${options.file}.map`,
      inputSourceMap: normaliseInputSourceMap(map)
    }),
    hookGenerator(template, template$1, code, meta),
    hookGenerator(javascript, javascript$1, code, meta),
    hookGenerator(css, css$1, code, meta)
  )(createInitialInput(meta))
}

/**
 * Prepare the riot parser node transformers
 * @param   { Function } transformer - transformer function
 * @param   { Object } sourceNode - riot parser node
 * @param   { string } source - component source code
 * @param   { Object } meta - compilation meta information
 * @returns { function(): Promise<Output> } Function what resolves to object containing output code and source map
 */
function hookGenerator(transformer, sourceNode, source, meta) {
  const hasContent = sourceNode && (sourceNode.text || !isEmptyArray(sourceNode.nodes) || !isEmptyArray(sourceNode.attributes));

  return hasContent ? curry(transformer)(sourceNode, source, meta) : result => result
}

// This function can be used to register new preprocessors
// a preprocessor can target either only the css or javascript nodes
// or the complete tag source file ('template')
const registerPreprocessor = register;

// This function can allow you to register postprocessors that will parse the output code
// here we can run prettifiers, eslint fixes...
const registerPostprocessor = register$1;

export { compile, createInitialInput, generateSlotsFromString, generateTemplateFunctionFromString, registerPostprocessor, registerPreprocessor };
