This commit is contained in:
lalBi94
2023-03-05 13:23:23 +01:00
commit 7bc56c09b5
14034 changed files with 1834369 additions and 0 deletions

111
node_modules/@riotjs/parser/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,111 @@
# Changes for riot-parser
### v4.3.1
- Improve the inline `<script>` tags check
### v4.3.0
- Add support for inline script tags (`<script src='path/to/the/script'>`)
### v4.2.1
- Fix make sure comments nodes will be generated via tree builder
### v4.2.0
- Add the extraction of comment nodes text
- Fix https://github.com/riot/riot/issues/2836
### v4.1.2
- Update generated bundle fixing discrepancy between source files and bundled output
### v4.1.1
- Fix end value of the root node
### v4.1.0
- Add expose the internal constants to the public API
### v4.0.3
- Fix https://github.com/riot/riot/issues/2723 for real this time
### v4.0.2
- Fix parsing of nested svg nodes https://github.com/riot/riot/issues/2723
### v4.0.1
- Fix the creation of the `parts` array in nodes containing expressions
### v4.0.0
- Stable release
- Add more tests for the new feautures listed below
### v4.0.0-rc.2
- Fix: support spread attributes together with other attribute expressions on the same DOM node
### v4.0.0-rc.1
- Fix https://github.com/riot/riot/issues/2679
- Add support for `<a {href}>` expression attributes shortcuts
### v0.8.1
- Add the `src` folder to the npm publishing files
### 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
- Remove the unecessary PUBLIC_JAVASCRIPT and PRIVATE_JAVASCRIPT nodes
### v0.5.0
- Remove the the useless prefix option
- Improve the coverage
- Improve the quality of the source code
### v0.4.0
- Add the [`dom-nodes`](https://github.com/riot/dom-nodes) dependecy to improve the output
- Add the `isCustom`, `isBoolean`, `isVoid`, `isSelfClosing` and `isRaw` boolean node attributes
### v0.3.0
- Fix treeBuilder issues
- Improve coverage
- Improve code maintainability
### v0.2.0
- Add `voidTags` to the exports
### v0.1.0
- Enhance the javascript parsing: the javascript node will contain nested nodes containing the private and the public javascript methods
- Add the PUBLIC_JAVASCRIPT and PRIVATE_JAVASCRIPT nodes
- Change the `attr` to `attributes` and `expr` to `expressions` keys
### v0.0.6
- Tree-builder support for 'if/else/elseif' tags (avoid unexpected closing tag errors).
- Fix to text nodes only escaping the fist block of whitespace.
### v0.0.5
- Now, attribute names are lowercased in the builder, only for empty namespaces (i.e. not svg).
### v0.0.4
- Included TEXTAREA as special tag that can contain only raw text and expressions.
- For SVG tags, now the `ns` property is the full URI http://www.w3.org/2000/svg.
- The `children` property of TAGs is renamed to `nodes`.
### v0.0.3
- The default builder is integrated in this module and injected in the parser.
- Only two versions, node CommonJS (transpiled to ES5) and ES6 modules (untranspiled).
- The `nodeTypes` property of TagParser is removed, now is in a separated submodule.
- Exposing `skipES6TL` to skip ES6 Template Literals.
- Reduction of code size, `skipRegex` is imported from npm.
- Source files (ES6) are moved to the "lib/" directory.
- Remove dependency on `Object.assign`.
- Updated devDependencies.
### v0.0.2 (UNPUBLISHED)
- Added suport for SVG en the tests.
- Added test/builder/tree-builder2.js as sample.
- Support for self-closing script/style tags.
- The `replace` property of attributes and text is discarded and there's a new property `unescape` is an array containing the positions of the escape characters (relative to the whole buffer).
- Matching literal regexes is a bit faster now.
- Fixes incorrect regex that matches literal regexes.
### v0.0.1
- First public release
# TODO
- Support for case sensitive properties in SVG elements.

21
node_modules/@riotjs/parser/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Riot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

140
node_modules/@riotjs/parser/README.md generated vendored Normal file
View File

@@ -0,0 +1,140 @@
# parser
[![Build Status][travis-image]][travis-url]
[![Code Quality][codeclimate-image]][codeclimate-url]
[![NPM version][npm-version-image]][npm-url]
[![NPM downloads][npm-downloads-image]][npm-url]
[![MIT License][license-image]][license-url]
[![Coverage Status][coverage-image]][coverage-url]
Minimal, loose html parser for Riot tags
### Install
```bash
npm i @riotjs/parser --save
```
The package has two modules:
```js
// Use as: parser(options).parse(code, startPosition)
const parser = require('@riotjs/parser').default
// The enum NodeTypes (a plain JS object) that contains the values of the
// type property of the nodes emited by tagParser (and more).
const nodeTypes = require('@riotjs/parser').nodeTypes
```
ES6 modules export:
```js
import parser, { nodeTypes } from '@riotjs/parser'
```
This parser is a low-level tool that builds a simple array of objects with information about the given html fragment, readed secuencially. It is designed to parse one single tag and not entire html pages, the tag closing the root element ends the parsing.
There are 3 main node types:
* Tags - HTMLElements, including SCRIPT and STYLE elements.
* Comments - Ignored by default.
* Text - Text nodes.
Opening tags can contain attributes. Text and attribute values can contain expressions.
There's no support for untagged JavaScript block.
The value returned by the parser is an object like this:
```js
{
data, // String of the given html fragment with no changes.
output // Array of objects with information about the parsed tags.
}
```
The first element of `output` is the opening tag of the root element.
The parsing stops when the closing tag of the root is found, so the last node have the ending position.
### Commands
* Build: `npm run build`
* Test: `npm t`
* Samples: `npm run samples`
## Tag names
Both, html and Riot tag names must start with a 7 bit letter (`[a-zA-Z]`) followed by zero o more ISO-8859-1 characters, except those in `[\x00-\x2F\x7F-\xA0>/]`.
If the first letter is not found, it becomes simple text.
Any non-recognized character ends the tag name (`'/'` behaves like whitespace).
All the tag names are converted to lower case.
## Openning Tags
Start with a `'<'` followed by a [tag name](#tag-names) or the character `'!'` that signals the start of a [comment](#comments), `DOCTYPE` or `CDATA` declaration (last two are parsed as comments).
Against the html5 specs, tags ending with `'/>'` are preserved as self-closing tags (the builder must handle this).
## Closing tags
They are included in the output, except for void or self-closing tags, and its name include the first slash.
## Attributes
Accepts all characters as the tag names and more.
An equal sign (`'='`) separates the name of the value. If there's no name, this `'='` is the first character of the name (yes). The value can be empty.
One or more slashes (`'/'`) behaves like whitespace. In the name, the slash splits the name generating two attributes, even if the name was quoted.
The first `>` anywhere in the openning tag ends the attribute list, except if this is in a quoted value.
All attribute names are converted to lowercase and the unquoted values are trimmed.
## Comments
Must start with `'<!--'`. The next following `'-->'` or the end of file ends the comment.
Comments in short notation, starting with `'<!'` (without `'--'`), ends at the first `'>'`.
By default, comments are discarted.
## Expressions
Expressions may be contained in attribute values or text nodes.
The default delimiters are `'{'` and `'}'`.
There may be more tan one expression as part of one attribute value or text node, or only one replacing the entire value or node.
When used as the whole attribute value, there's no need to enclose the expression inside quotes, even if the expression contains whitespace.
Single and double quotes can be nested inside the expression.
To emit opening (left) brackets as literal text wherever an opening bracket is expected, the bracket must be prefixed with a backslash (the JS escape char `'\'`).
This character is preserved in the output, but the parser will add a `replace` property for the attribute or node containing the escaped bracket, whose value is the bracket itself.
## Options
* `comments` - Pass `true` to preserve the comments.
* `brackets` - Array of two string with the left/right brackets used to extract expressions.
[travis-image]:https://img.shields.io/travis/riot/parser.svg?style=flat-square
[travis-url]:https://travis-ci.org/riot/parser
[license-image]:http://img.shields.io/badge/license-MIT-000000.svg?style=flat-square
[license-url]:LICENSE.txt
[npm-version-image]:http://img.shields.io/npm/v/@riotjs/parser.svg?style=flat-square
[npm-downloads-image]:http://img.shields.io/npm/dm/@riotjs/parser.svg?style=flat-square
[npm-url]:https://npmjs.org/package/@riotjs/parser
[coverage-image]:https://img.shields.io/coveralls/riot/parser/master.svg?style=flat-square
[coverage-url]:https://coveralls.io/r/riot/parser/?branch=master
[codeclimate-image]:https://api.codeclimate.com/v1/badges/5db4f1c96a43e3736cf0/maintainability
[codeclimate-url]:https://codeclimate.com/github/riot/parser

1787
node_modules/@riotjs/parser/index.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

62
node_modules/@riotjs/parser/package.json generated vendored Normal file
View File

@@ -0,0 +1,62 @@
{
"name": "@riotjs/parser",
"version": "4.3.1",
"description": "The parser for Riot tags",
"main": "./index.js",
"module": "./src/index.js",
"jsnext:main": "./src/index.js",
"license": "MIT",
"engines": {
"node": ">=4.2",
"npm": ">=3.0"
},
"scripts": {
"test": "nyc mocha ./test/index",
"test-debug": "mocha --inspect-brk ./test",
"cov": "nyc report --reporter=text-lcov | coveralls",
"cov-html": "nyc report --reporter=html",
"pretest": "npm run build",
"lint": "eslint src test",
"build": "rollup -c",
"samples": "node ./test/samples.js",
"prepublish": "npm run build"
},
"files": [
"*.js",
"dist",
"src"
],
"keywords": [
"html",
"html5",
"tag",
"parser",
"javascript"
],
"author": {
"name": "aMarCruz",
"email": "amarcruz@yahoo.com",
"url": "https://github.com/aMarCruz"
},
"repository": {
"type": "git",
"url": "https://github.com/riot/parser"
},
"homepage": "https://github.com/riot/parser",
"bugs": "https://github.com/riot/parser/issues",
"readme": "https://github.com/riot/parser/blob/master/README.md",
"devDependencies": {
"chai": "^4.2.0",
"coveralls": "^3.1.0",
"eslint": "^7.6.0",
"eslint-config-riot": "^3.0.0",
"mocha": "^8.1.1",
"nyc": "^15.1.0",
"rollup": "^2.23.0",
"rollup-plugin-node-resolve": "^5.2.0"
},
"dependencies": {
"curri": "^1.0.1",
"dom-nodes": "^1.1.3"
}
}

11
node_modules/@riotjs/parser/rollup.config.js generated vendored Normal file
View File

@@ -0,0 +1,11 @@
import resolve from 'rollup-plugin-node-resolve'
export default {
input: 'src/index.js',
output: {
name: 'parser',
format: 'cjs',
file: './index.js'
},
plugins: [resolve()]
}

16
node_modules/@riotjs/parser/src/constants.js generated vendored Normal file
View File

@@ -0,0 +1,16 @@
export const JAVASCRIPT_OUTPUT_NAME = 'javascript'
export const CSS_OUTPUT_NAME = 'css'
export const TEMPLATE_OUTPUT_NAME = 'template'
// Tag names
export const JAVASCRIPT_TAG = 'script'
export const STYLE_TAG = 'style'
export const TEXTAREA_TAG = 'textarea'
// Boolean attributes
export const IS_RAW = 'isRaw'
export const IS_SELF_CLOSING = 'isSelfClosing'
export const IS_VOID = 'isVoid'
export const IS_BOOLEAN = 'isBoolean'
export const IS_CUSTOM = 'isCustom'
export const IS_SPREAD = 'isSpread'

19
node_modules/@riotjs/parser/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,19 @@
import * as c from './constants'
import * as types from './node-types'
import parser from './parser'
/**
* Expose the internal constants
*/
export const constants = c
/**
* The nodeTypes definition
*/
export const nodeTypes = types
/*
* Factory function to create instances of the parser
*/
export default parser

10
node_modules/@riotjs/parser/src/messages.js generated vendored Normal file
View File

@@ -0,0 +1,10 @@
export const rootTagNotFound = 'Root tag not found.'
export const unclosedTemplateLiteral = 'Unclosed ES6 template literal.'
export const unexpectedEndOfFile = 'Unexpected end of file.'
export const unexpectedNamedTag = 'Unexpected tag <%1>'
export const unclosedComment = 'Unclosed comment.'
export const unclosedNamedBlock = 'Unclosed "%1" block.'
export const duplicatedNamedTag = 'Multiple inline "<%1>" tags are not supported.'
export const expectedAndInsteadSaw = 'Expected "</%1>" and instead saw "<%2>".'
export const unexpectedCharInExpression = 'Unexpected character %1.'
export const unclosedExpression = 'Unclosed expression.'

14
node_modules/@riotjs/parser/src/node-types.js generated vendored Normal file
View File

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

112
node_modules/@riotjs/parser/src/parser.js generated vendored Normal file
View File

@@ -0,0 +1,112 @@
import {ATTR, TAG} from './node-types'
import {rootTagNotFound, unexpectedEndOfFile} from './messages'
import attr from './parsers/attribute'
import curry from 'curri'
import flush from './utils/flush-parser-state'
import panic from './utils/panic'
import tag from './parsers/tag'
import text from './parsers/text'
import treeBuilder from './tree-builder'
/**
* 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.
*/
export default function parser(options, customBuilder) {
const state = curry(createParserState)(options, customBuilder || treeBuilder)
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(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)
}
}

183
node_modules/@riotjs/parser/src/parsers/attribute.js generated vendored Normal file
View File

@@ -0,0 +1,183 @@
import {ATTR, TEXT} from '../node-types'
import {ATTR_START, SPREAD_OPERATOR} from '../regex'
import {IS_BOOLEAN, IS_SELF_CLOSING, IS_SPREAD} from '../constants'
import addToCollection from '../utils/add-to-collection'
import execFromPos from '../utils/exec-from-pos'
import expr from './expression'
import getChunk from '../utils/get-chunk'
import {isBoolAttribute} from 'dom-nodes'
import memoize from '../utils/memoize'
const expressionsContentRe = memoize(brackets => RegExp(`(${brackets[0]}[^${brackets[1]}]*?${brackets[1]})`, 'g'))
const isSpreadAttribute = 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
*/
export default 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(attr.name):
return parseSpreadAttribute(state, attr)
case isExpressionName === true:
return parseExpressionNameAttribute(state, attr)
default:
return parseNomalAttribute(state, attr, quote)
}
}

57
node_modules/@riotjs/parser/src/parsers/comment.js generated vendored Normal file
View File

@@ -0,0 +1,57 @@
import {COMMENT, TEXT} from '../node-types'
import flush from '../utils/flush-parser-state'
import panic from '../utils/panic'
import {unclosedComment} from '../messages'
/**
* 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
*/
export default 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(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
*/
export function pushComment(state, start, end, text) {
state.pos = end
if (state.options.comments === true) {
flush(state)
state.last = {
type: COMMENT,
start,
end,
text
}
}
}

100
node_modules/@riotjs/parser/src/parsers/expression.js generated vendored Normal file
View File

@@ -0,0 +1,100 @@
import escapeStr from '../utils/escape-str'
import exprExtr from '../utils/expr-extr'
import panic from '../utils/panic'
import pushText from '../utils/push-text'
import {unexpectedEndOfFile} from '../messages'
/**
* 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
*/
export default 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(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]
}

49
node_modules/@riotjs/parser/src/parsers/tag.js generated vendored Normal file
View File

@@ -0,0 +1,49 @@
import {ATTR, TEXT} from '../node-types'
import {RE_SCRYLE, TAG_2C, TAG_NAME} from '../regex'
import comment from './comment'
import execFromPos from '../utils/exec-from-pos'
import pushTag from '../utils/push-tag'
import pushText from '../utils/push-text'
/**
* 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
*/
export default 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
}

69
node_modules/@riotjs/parser/src/parsers/text.js generated vendored Normal file
View File

@@ -0,0 +1,69 @@
import {TAG, TEXT} from '../node-types'
import {RE_SCRYLE} from '../regex'
import {TEXTAREA_TAG} from '../constants'
import execFromPos from '../utils/exec-from-pos'
import expr from './expression'
import panic from '../utils/panic'
import pushTag from '../utils/push-tag'
import pushText from '../utils/push-text'
import {unclosedNamedBlock} from '../messages'
/**
* 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
*/
export default 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} 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)
}
}

41
node_modules/@riotjs/parser/src/regex.js generated vendored Normal file
View File

@@ -0,0 +1,41 @@
/**
* Matches the start of valid tags names; used with the first 2 chars after the `'<'`.
* @const
* @private
*/
export 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
*/
export 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
*/
export const ATTR_START = /(\S[^>/=\s]*)(?:\s*=\s*([^>/])?)?/g
/**
* Matches the spread operator
* it will be used for the spread attributes
* @type {RegExp}
*/
export 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
*/
export const RE_SCRYLE = {
script: /<\/script\s*>/gi,
style: /<\/style\s*>/gi,
textarea: /<\/textarea\s*>/gi
}
// Do not touch text content inside this tags
export const RAW_TAGS = /^\/?(?:pre|textarea)$/

252
node_modules/@riotjs/parser/src/tree-builder.js generated vendored Normal file
View File

@@ -0,0 +1,252 @@
/*---------------------------------------------------------------------
* 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.
*/
import {COMMENT, TAG, TEXT} from './node-types'
import {
CSS_OUTPUT_NAME,
IS_RAW,
IS_SELF_CLOSING,
IS_VOID,
JAVASCRIPT_OUTPUT_NAME,
JAVASCRIPT_TAG,
STYLE_TAG,
TEMPLATE_OUTPUT_NAME
} from './constants'
import {RAW_TAGS} from './regex'
import {duplicatedNamedTag} from './messages'
import panic from './utils/panic'
/**
* 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(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)
}
})
export default 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
}
})
}

View File

@@ -0,0 +1,11 @@
/**
* 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
*/
export default function addToCollection(collection = [], item) {
collection.push(item)
return collection
}

7
node_modules/@riotjs/parser/src/utils/escape-str.js generated vendored Normal file
View File

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

11
node_modules/@riotjs/parser/src/utils/exec-from-pos.js generated vendored Normal file
View File

@@ -0,0 +1,11 @@
/**
* 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
*/
export default function execFromPos(re, pos, string) {
re.lastIndex = pos
return re.exec(string)
}

139
node_modules/@riotjs/parser/src/utils/expr-extr.js generated vendored Normal file
View File

@@ -0,0 +1,139 @@
/*
* 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.
*/
import skipES6TL, {$_ES6_BQ} from './skip-es6-tl'
import {unclosedExpression, unexpectedCharInExpression} from '../messages'
import escapeStr from './escape-str'
import panic from './panic'
import skipRegex from './skip-regex'
/**
* @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(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.
*/
export default 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(code, unclosedExpression, end)
}
}

View File

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

12
node_modules/@riotjs/parser/src/utils/format-error.js generated vendored Normal file
View File

@@ -0,0 +1,12 @@
export default 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}`
}

11
node_modules/@riotjs/parser/src/utils/get-chunk.js generated vendored Normal file
View File

@@ -0,0 +1,11 @@
/**
* 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
*/
export default function getChunk(source, start, end) {
return source.slice(start, end)
}

18
node_modules/@riotjs/parser/src/utils/memoize.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
/**
* Memoization function
* @param {Function} fn - function to memoize
* @returns {*} return of the function to memoize
*/
export default 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
}
}

15
node_modules/@riotjs/parser/src/utils/panic.js generated vendored Normal file
View File

@@ -0,0 +1,15 @@
import formatError from './format-error'
/**
* 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
*/
export default function panic(data, msg, pos) {
const message = formatError(data, msg, pos)
throw new Error(message)
}

48
node_modules/@riotjs/parser/src/utils/push-tag.js generated vendored Normal file
View File

@@ -0,0 +1,48 @@
import {
IS_CUSTOM,
IS_VOID
} from '../constants'
import {isCustom, isVoid} from 'dom-nodes'
import {TAG} from '../node-types'
import flush from './flush-parser-state'
/**
* 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
*/
export default 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
}

42
node_modules/@riotjs/parser/src/utils/push-text.js generated vendored Normal file
View File

@@ -0,0 +1,42 @@
import {TEXT} from '../node-types'
import flush from './flush-parser-state'
import getChunk from './get-chunk'
/**
* 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
*/
export default 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
}

33
node_modules/@riotjs/parser/src/utils/skip-es6-tl.js generated vendored Normal file
View File

@@ -0,0 +1,33 @@
import formatError from './format-error'
import {unclosedTemplateLiteral} from '../messages'
export 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)
*/
export default 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)
}

116
node_modules/@riotjs/parser/src/utils/skip-regex.js generated vendored Normal file
View File

@@ -0,0 +1,116 @@
// 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 */
export default 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
}