Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@riotjs/parser

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@riotjs/parser - npm Package Compare versions

Comparing version 0.7.0 to 0.8.0

4

CHANGELOG.md
# Changes for riot-parser
### v0.8.0
- Add support for the spread attributes `<a {...foo.bar}>`
- Fixed the `isCustom` boolean that will be added also to the root nodes
### v0.6.9

@@ -4,0 +8,0 @@ - Remove the unecessary PUBLIC_JAVASCRIPT and PRIVATE_JAVASCRIPT nodes

1673

index.js

@@ -31,42 +31,2 @@ 'use strict';

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}`
}
/**
* 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} msg - Error message
* @param {pos} [number] - Position of the error
*/
function panic(data, msg, pos) {
const message = formatError(data, msg, pos);
throw new Error(message)
}
/**
* Outputs the last parsed node. Can be used with a builder too.
*
* @param {ParserStore} store - Parsing store
* @private
*/
function flush(store) {
const last = store.last;
store.last = null;
if (last && store.root) {
store.builder.push(last);
}
}
const rootTagNotFound = 'Root tag not found.';

@@ -101,3 +61,10 @@ const unclosedTemplateLiteral = 'Unclosed ES6 template literal.';

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.

@@ -132,250 +99,507 @@ * Used by parseText fo find the end of the block.

const IS_CUSTOM = 'isCustom';
const IS_SPREAD = 'isSpread';
/*---------------------------------------------------------------------
* Tree builder for the riot tag parser.
/**
* 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.
*
* 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.
* @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 = '`';
/**
* Escape the carriage return and the line feed from a string
* @param {string} string - input string
* @returns {string} output string escaped
* 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 escapeReturn(string) {
return string
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
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)
}
/**
* Escape double slashes in a string
* @param {string} string - input string
* @returns {string} output string escaped
* 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 escapeSlashes(string) {
return string.replace(/\\/g, '\\\\')
function panic(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;
/**
* Replace the multiple spaces with only one
* @param {string} string - input string
* @returns {string} string without trailing spaces
* 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 cleanSpaces(string) {
return string.replace(/\s+/g, ' ')
function _prev(code, pos) {
while (--pos >= 0 && /\s/.test(code[pos]));
return pos
}
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
*/
push(node) {
const store = this.store;
switch (node.type) {
case TEXT:
this.pushText(store, node);
break
case TAG: {
const name = node.name;
if (name[0] === '/') {
this.closeTag(store, node, name);
} else {
this.openTag(store, node);
}
break
}
}
},
closeTag(store, node) {
const last = store.scryle || store.last;
/**
* 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++;
last.end = node.end;
// `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 (store.scryle) {
store.scryle = null;
} else {
store.last = store.stack.pop();
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
}
},
openTag(store, node) {
const name = node.name;
const attrs = node.attributes;
if ([JAVASCRIPT_TAG, STYLE_TAG].includes(name)) {
// Only accept one of each
if (store[name]) {
panic(this.store.data, duplicatedNamedTag.replace('%1', name), node.start);
// 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;
}
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 (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 (!node[IS_SELF_CLOSING] && !node[IS_VOID]) {
store.stack.push(lastTag);
newNode.nodes = [];
store.last = newNode;
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;
}
}
}
}
if (attrs) {
this.attrs(attrs);
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}`;
}
},
attrs(attributes) {
for (let i = 0; i < attributes.length; i++) {
const attr = attributes[i];
if (attr.value) {
this.split(attr, attr.value, attr.valueStart, true);
}
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(code, unexpectedCharInExpression.replace('%1', char), index);
}
},
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;
for (let i = 0; i < expressions.length; i++) {
const expr = expressions[i];
const text = source.slice(pos, expr.start - start);
let code = expr.text;
parts.push(this.sanitise(node, text, pack), escapeReturn(escapeSlashes(code).trim()));
pos = expr.end - start;
}
if ((pos += start) < node.end) {
parts.push(this.sanitise(node, source.slice(pos), pack));
}
} else {
parts[0] = this.sanitise(node, source, pack);
if (char === '}' && stack[stack.length - 1] === $_ES6_BQ) {
char = stack.pop();
}
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++;
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
}
}
text = escapeSlashes(text);
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;
}
return pack ? cleanSpaces(text) : escapeReturn(text)
if (stack.length) {
panic(code, unclosedExpression, end);
}
});
}
function createTreeBuilder(data, options) {
const root = {
type: TAG,
name: '',
start: 0,
end: 0,
nodes: []
};
/**
* 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);
}
}
return Object.assign(Object.create(TREE_BUILDER_STRUCT), {
compact: options.compact !== false,
store: {
last: root,
stack: [],
scryle: null,
root,
style: null,
script: null,
data
/**
* 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
}
/**
* 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
* 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 curry(fn, ...acc) {
return (...args) => {
args = [...acc, ...args];
function parseExpressions(state, re) {
const { data, options } = state;
const { brackets } = options;
const expressions = [];
let unescape, pos, match;
return args.length < fn.length ?
curry(fn, ...args) :
fn(...args)
// 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(data, unexpectedEndOfFile, pos);
}
return {
unescape,
expressions,
end: match.index
}
}
/**
* 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
* 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 execFromPos(re, pos, string) {
re.lastIndex = pos;
return re.exec(string)
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]
}

@@ -768,89 +992,153 @@

/**
* Pushes a new *tag* and set `last` to this, so any attributes
* will be included on this and shifts the `end`.
* The more complex parsing is for attributes as it can contain quoted or
* unquoted values or expressions.
*
* @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)
* @param {ParserStore} state - Parser state
* @returns {number} New parser mode.
* @private
*/
function pushTag(state, name, start, end) {
const root = state.root;
const last = { type: TAG, name, start, end };
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);
if (isCustom(name) && !root) {
last[IS_CUSTOM] = true;
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);
}
if (isVoid(name)) {
last[IS_VOID] = true;
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 re = ATTR_START; // (\S[^>/=\s]*)(?:\s*=\s*([^>/])?)? g
const start = re.lastIndex = pos; // first non-whitespace
const match = re.exec(data);
if (match) {
const end = re.lastIndex;
const attr = parseAttribute(state, match, start, end);
//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);
}
}
state.pos = end;
function parseNomalAttribute(state, attr, quote) {
const { data } = state;
let { end } = attr;
if (root) {
if (name === root.name) {
state.count++;
} else if (name === root.close) {
state.count--;
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
}
flush(state);
} else {
// start with root (keep ref to output)
state.root = { name: last.name, close: `/${name}` };
state.count = 1;
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
})
}
state.last = last;
return attr
}
/**
* 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)
function parseSpreadAttribute(state, attr, quote) {
let end = expr(state, attr, quote || '[>/\\s]', attr.start);
return {
[IS_SPREAD]: true,
start: attr.start,
expressions: attr.expressions.map(expr$$1 => Object.assign(expr$$1, {
text: expr$$1.text.replace(SPREAD_OPERATOR, '')
})),
end: quote ? ++end : 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
* @private
* 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
* @returns {Object} attribute object
*/
function pushText(state, start, end, extra = {}) {
const text = getChunk(state.data, start, end);
const expressions = extra.expressions;
const unescape = extra.unescape;
function parseAttribute(state, match, start, end) {
const attr = {
name: match[1],
value: '',
start,
end
};
const quote = match[2]; // first letter of value or nothing
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 (SPREAD_OPERATOR.test(attr.name)) {
return parseSpreadAttribute(state, attr, quote)
}
if (expressions && expressions.length) {
q.expressions = (q.expressions || []).concat(expressions);
}
return parseNomalAttribute(state, attr, quote)
}
if (unescape) {
q.unescape = unescape;
/**
* 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)
}
return TEXT
}

@@ -862,5 +1150,6 @@

*
* @param {ParserState} state - Parser state
* @param {string} data - Buffer to parse
* @param {number} start - Position of the '<!' sequence
* @param {ParserState} state - Parser state
* @param {string} data - Buffer to parse
* @param {number} start - Position of the '<!' sequence
* @returns {number} node type id
* @private

@@ -883,5 +1172,6 @@ */

*
* @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 {ParserState} state - Current parser state
* @param {number} start - Start position of the tag
* @param {number} end - Ending position (last char of the tag)
* @returns {undefined} void function
* @private

@@ -898,2 +1188,43 @@ */

/**
* 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

@@ -941,577 +1272,284 @@ * if an invalid tag name is found.

// 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`.
* Parses regular text and script/style blocks ...scryle for short :-)
* (the content of script and style is text as well)
*
* @param {string} code - Buffer to search
* @param {number} pos - Starting position
* @returns {number} Position of the first non-blank character to the left.
* @param {ParserState} state - Parser state
* @returns {number} New parser mode.
* @private
*/
function _prev(code, pos) {
while (--pos >= 0 && /\s/.test(code[pos]));
return pos
}
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);
/**
* 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
if (!match) {
panic(data, unclosedNamedBlock.replace('%1', name), pos - 1);
}
// 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;
}
}
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 start
return TEXT
}
/**
* Escape special characters in a given string, in preparation to create a regex.
*
* @param {string} str - Raw string
* @returns {string} Escaped string.
* 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
*/
var escapeStr = (str) => str.replace(/(?=[-[\](){^*+?.$|\\])/g, '\\');
function parseSpecialTagsContent(state, name, match) {
const { pos } = state;
const start = match.index;
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
if (name === TEXTAREA_TAG) {
expr(state, null, match[0], pos);
} else {
pushText(state, pos, start);
}
throw formatError(code, unclosedTemplateLiteral, pos)
}
/*
* 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).
/*---------------------------------------------------------------------
* Tree builder for the riot tag parser.
*
* @const
* @private
*/
const S_STRING = `${S_SQ_STR}|${S_SQ_STR.replace(/'/g, '"')}`;
/**
* Regex cache
* The output has a root property and separate arrays for `html`, `css`,
* and `js` tags.
*
* @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.
* The root tag is included as first element in the `html` array.
* Script tags marked with "defer" are included in `html` instead `js`.
*
* @param {string} b - Closing brackets
* @returns {RegExp}
* - 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.
*/
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
* Escape the carriage return and the line feed from a string
* @param {string} string - input string
* @returns {string} output string escaped
*/
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(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 }
function escapeReturn(string) {
return string
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
}
/**
* 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)) {
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(code, unclosedExpression, end);
}
* Escape double slashes in a string
* @param {string} string - input string
* @returns {string} output string escaped
*/
function escapeSlashes(string) {
return string.replace(/\\/g, '\\\\')
}
/**
* 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} pos - Starting position
* @returns {number} Ending position
* @private
* Replace the multiple spaces with only one
* @param {string} string - input string
* @returns {string} string without trailing spaces
*/
function expr(state, node, endingChars, start) {
const re = b0re(state, endingChars);
function cleanSpaces(string) {
return string.replace(/\s+/g, ' ')
}
re.lastIndex = start; // reset re position
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]
}
},
const { unescape, expressions, end } = parseExpressions(state, re);
/**
* 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;
if (node) {
if (unescape) {
node.unescape = unescape;
switch (node.type) {
case TEXT:
this.pushText(store, node);
break
case TAG: {
const name = node.name;
if (name[0] === '/') {
this.closeTag(store, node, name);
} else {
this.openTag(store, node);
}
break
}
if (expressions.length) {
node.expressions = expressions;
}
} else {
pushText(state, start, end, {expressions, unescape});
}
},
closeTag(store, node) {
const last = store.scryle || store.last;
return end
}
last.end = node.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
if (store.scryle) {
store.scryle = null;
} else {
const tmpExpr = exprExtr(data, pos, brackets);
if (tmpExpr) {
expressions.push(tmpExpr);
re.lastIndex = tmpExpr.end;
}
store.last = store.stack.pop();
}
}
},
// Even for text, the parser needs match a closing char
if (!match) {
panic(data, unexpectedEndOfFile, pos);
}
openTag(store, node) {
const name = node.name;
const attrs = node.attributes;
return {
unescape,
expressions,
end: match.index
}
}
if ([JAVASCRIPT_TAG, STYLE_TAG].includes(name)) {
// Only accept one of each
if (store[name]) {
panic(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;
/**
* 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];
lastTag.nodes.push(newNode);
if (re) return re
if (lastTag[IS_RAW] || RAW_TAGS.test(name)) {
node[IS_RAW] = true;
}
const b0 = escapeStr(brackets[0]);
// cache the regex extending the regexCache object
Object.assign(state.regexCache, { [str]: new RegExp(`(${str})|${b0}`, 'g' ) });
if (!node[IS_SELF_CLOSING] && !node[IS_VOID]) {
store.stack.push(lastTag);
newNode.nodes = [];
store.last = newNode;
}
}
return state.regexCache[str]
}
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 = [];
/**
* 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
}
if (expressions) {
let pos = 0;
/**
* 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);
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;
});
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
if ((pos += start) < node.end) {
parts.push(this.sanitise(node, source.slice(pos), pack));
}
} else {
parts[0] = this.sanitise(node, source, pack);
}
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
}
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++;
}
}
/**
* 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
* @private
*/
function setAttribute(state, pos, tag) {
const { data } = state;
const re = ATTR_START; // (\S[^>/=\s]*)(?:\s*=\s*([^>/])?)? g
const start = re.lastIndex = pos; // first non-whitespace
const match = re.exec(data);
text = escapeSlashes(text);
if (match) {
let end = re.lastIndex;
const attr = parseAttribute(state, match, start, end);
//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);
return pack ? cleanSpaces(text) : escapeReturn(text)
}
}
});
/**
* 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
* @returns {object} attribute object
*/
function parseAttribute(state, match, start, end) {
const { data } = state;
const attr = {
name: match[1],
value: '',
start,
end
function createTreeBuilder(data, options) {
const root = {
type: TAG,
name: '',
start: 0,
end: 0,
nodes: []
};
if (isBoolAttribute(attr.name)) {
attr[IS_BOOLEAN] = true;
}
let quote = match[2]; // first letter of value or nothing
// 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
return Object.assign(Object.create(TREE_BUILDER_STRUCT), {
compact: options.compact !== false,
store: {
last: root,
stack: [],
scryle: null,
root,
style: null,
script: null,
data
}
end = expr(state, attr, quote || '[>/\\s]', valueStart);
// adjust the bounds of the value and save its content
Object.assign(attr, {
value: getChunk(data, valueStart, end),
valueStart,
end: quote ? ++end : end
});
}
return attr
})
}
/**
* 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(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} data - Buffer to parse
* @param {string} name - one of the tags matched by the RE_SCRYLE regex
* @returns {array} match - result of the regex matching the content of the parsed tag
*/
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);
}
}
/**
* Factory for the Parser class, exposing only the `parse` method.

@@ -1533,6 +1571,6 @@ * The export adds the Parser class as property.

* Create a new state object
* @param {object} userOptions - parser options
* @param {Function} customBuilder - Tree builder factory
* @param {Object} userOptions - parser options
* @param {Function} builder - Tree builder factory
* @param {string} data - data to parse
* @returns {ParserState}
* @returns {ParserState} it represents the current parser state
*/

@@ -1587,3 +1625,4 @@ function createParserState(userOptions, builder, data) {

* @param {ParserState} state - Current parser state
* @param {string} type - current parsing context
* @param {string} type - current parsing context
* @returns {undefined} void function
*/

@@ -1590,0 +1629,0 @@ function walk(state, type) {

{
"name": "@riotjs/parser",
"version": "0.7.0",
"version": "0.8.0",
"description": "The parser for Riot tags",

@@ -56,3 +56,3 @@ "main": "./index.js",

"nyc": "^13.1.0",
"rollup": "^0.66.6",
"rollup": "^1.0.0",
"rollup-plugin-node-resolve": "^3.4.0"

@@ -59,0 +59,0 @@ },

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc