diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/node_modules/.bin/acorn b/node_modules/.bin/acorn deleted file mode 120000 index cf76760..0000000 --- a/node_modules/.bin/acorn +++ /dev/null @@ -1 +0,0 @@ -../acorn/bin/acorn \ No newline at end of file diff --git a/node_modules/.bin/cssesc b/node_modules/.bin/cssesc deleted file mode 120000 index 487b689..0000000 --- a/node_modules/.bin/cssesc +++ /dev/null @@ -1 +0,0 @@ -../cssesc/bin/cssesc \ No newline at end of file diff --git a/node_modules/.bin/esparse b/node_modules/.bin/esparse deleted file mode 120000 index 7423b18..0000000 --- a/node_modules/.bin/esparse +++ /dev/null @@ -1 +0,0 @@ -../esprima/bin/esparse.js \ No newline at end of file diff --git a/node_modules/.bin/esvalidate b/node_modules/.bin/esvalidate deleted file mode 120000 index 16069ef..0000000 --- a/node_modules/.bin/esvalidate +++ /dev/null @@ -1 +0,0 @@ -../esprima/bin/esvalidate.js \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/CHANGELOG.md b/node_modules/@riotjs/compiler/CHANGELOG.md deleted file mode 100644 index 17d4729..0000000 --- a/node_modules/@riotjs/compiler/CHANGELOG.md +++ /dev/null @@ -1,378 +0,0 @@ -# Compiler Changes - -### v4.3.11 -- Fix https://github.com/riot/compiler/issues/127 -- Fix https://github.com/riot/compiler/issues/126 -- Improve the code maintainability refactoring big files into smaller ones - -### v4.3.10 -- Fix https://github.com/riot/riot/issues/2753 -- Fix https://github.com/riot/riot/issues/2748 -- Update acorn to v7.0.0 - -### v4.3.9 -- Fix https://github.com/riot/compiler/issues/125 -- Fix https://github.com/riot/compiler/issues/124 - -### v4.3.8 -- Fix make sure that the `createExpression` internal function receives always all its arguments - -### v4.3.7 -- Fix https://github.com/riot/compiler/issues/121 - -### v4.3.6 -- Fix https://github.com/riot/compiler/issues/122 -- Fix https://github.com/riot/compiler/pull/118 - -### v4.3.5 -- Fix backslashed unicode css properties - -### v4.3.4 -- Fix escape backslashes in css strings https://github.com/riot/riot/issues/2726 - -### v4.3.3 -- Fix https://github.com/riot/compiler/issues/119 -- Fix https://github.com/riot/riot/issues/2726 - -### v4.3.2 -- Fix void tags will be automatically corrected for example: - ```html - - - - ``` - Will be transfromed to - - ```html - - - - ``` - -### v4.3.1 -- Fix https://github.com/riot/riot/issues/2719 -- Fix https://github.com/riot/riot/issues/2723 - -### v4.3.0 -- Add support for dynamic import - -### v4.2.6 -- Fix expression parts issues https://github.com/riot/riot/issues/2701 - -### v4.2.5 -- Fix https://github.com/riot/riot/issues/2700 replacing the `esprima` parser with `acorn` - -### v4.2.4 -- Fix attributes on custom tags having `if` or `each` directives - -### v4.2.3 -- Update `@riotjs/dom-bindings` using v4.0.0 -- Update npm dependencies - -### v4.2.2 -- Fix [riot#2691](https://github.com/riot/riot/issues/2691) - -### v4.2.1 -- Fix css generation with `@media` queries - -### v4.2.0 -- Add support for `` shortcut expressions -- Fix spread expressions issue [riot/2679](https://github.com/riot/riot/issues/2679) - -### v4.1.1 -- Fix commonjs imports - -### v4.1.0 -- Add support for the slot attribute binding - -### v4.0.4 -- Fix avoid removing selector attributes twice on custom tags - -### v4.0.3 -- Fix attributes handling on custom children nodes [riot#2680](https://github.com/riot/riot/issues/2680) - -### v4.0.2 -- Fix spread operator on each directives [riot#2679](https://github.com/riot/riot/issues/2679) - -### v4.0.1 -- Fix attributes mixed with expressions [riot#2681](https://github.com/riot/riot/issues/2681) - -### v4.0.0 -- Complete rewrite **not backward compatible** -- New output compatible only for Riot.js 4.0.0 -- Add better sourcemaps generation -- Add the `@riotjs/parser` fixing odd issues with regex like [#114](https://github.com/riot/compiler/issues/114) -- Improve the code generation strategy preferring AST to regex parsing -- Remove all the preprocessors from the core in favor of `registerPreprocessor` and `registerPostprocessor` instead - -### v4.0.0-beta.5 -- Fix https://github.com/riot/riot/issues/2669 - -### v4.0.0-beta.3 -- Fix https://github.com/riot/compiler/issues/115 - -### v4.0.0-beta.2 -- Add support for multiple expressions on the same attribute node - -### v4.0.0-beta.1 -- Update rename the `tag` property `exports` - -### v4.0.0-alpha.20 -- Fix handle escaped chars - -### v4.0.0-alpha.19 -- Fix bug in nodes with a single expression - -### v4.0.0-alpha.18 -- Add the `name` key to the tag exports -- Fix self-closed tag bindings - -### v4.0.0-alpha.17 -- Remove unused dev dependencies - -### v4.0.0-alpha.16 -- *Breaking change*: make the compiler API synchronous - -### v4.0.0-alpha.15 -- Fix slots root nodes handling - -### v4.0.0-alpha.14 -- Add sourcemap tests for babel preprocessor -- Update handling of multiple line text expressions, from template literal to array -- Update output format - -### v4.0.0-alpha.13 -- Fix sourcemap for the multiple text expressions -- Check make sure that `slot` tags will not be considered custom tags - -### v4.0.0-alpha.12 -- Fix sourcemap `sourcesContent` property -- Update sourcemap filename - -### v4.0.0-alpha.11 -- Fix sourcemap generation for the `if` and `each` tag bindings - -### v4.0.0-alpha.10 -- Update enhance sourcemaps generation -- Change second arguments for the pre/post processors. The `meta` object will contain info about the current compilation - -### v4.0.0-alpha.9 -- Fix move `recast` into the package dependencies - -### v4.0.0-alpha.8 -- Enhance the source map generation -- Improve performance -- Update npm dependencies - -### v4.0.0-alpha.7 -- Add support for the scoped css -- Update the Tag bindings output to support dynamic tags - -### v4.0.0-alpha.6 -- Fix issue with the object expressions scoping - -### v4.0.0-alpha.5 -- Update the tag bindings API to get the component implementation via function - -### v4.0.0-alpha.4 -- Fix issues related to the member expressions traversal and the scope - -### v4.0.0-alpha.3 -- Fix issue with custom tags and no slots - -### v4.0.0-alpha.2 -- Add support for the spread attributes `` -- Add the `tagName` to the compiler options in runtime -- Fix the options were not passed to the postprocessor - -### v4.0.0-alpha.1 - -- New complete rewrite from scratch -- Change npm name from `riot-compiler` to `@riotjs/compiler` -- First alpha release not backward compatible - -### v3.5.2 -- Fix es6 dynamic imports https://github.com/riot/riot/issues/2641 - -### v3.5.1 -- Fix try importing `@babel/core` first and then fallback to `babel-core` for the `es6` parser - -### v3.5.0 -- Add support for Babel 7 - -### v3.4.0 -- Add inline sourcemap support via `sourcemap='inline'` option - -### v3.3.1 -- Improve the sourcemap generation adding the `sourceContent` key - -### v3.3.0 -- Add initial experimental sourcemaps support via `sourcemap: true` option - -### v3.2.6 -- Fix #105 -- Fix #104 - -### v3.2.5 -- Update dependencies and refactor some internal code avoiding bitwise operators -- Fix coffeescript parser require https://github.com/riot/compiler/pull/102 - -### v3.2.4 -- Fix [riot#2369](https://github.com/riot/riot/issues/2369) : Possible bug involving compilation of tags containing regex. -- Using the `skip-regex` function from npm for sharing bwteen modules (at future). -- Using the `jsSplitter` function for safer replacement of JS code, part of the next compiler. - -### v3.2.3 -- Fixes various issues with literal regexes. - -### v3.1.4 -- Fix avoid the `filename` option for the babel-standalone parser - -### v3.1.3 -- Fix babel in browser runtime parser https://github.com/riot/examples/issues/51 - -### v3.1.2 -- Fix [riot#2210](https://github.com/riot/riot/issues/2210) : Style tag get stripped from riot tag even if it's in a javascript string. -- Updated devDependencies. - -### v3.1.0 -- Adds support for css @apply rule: now ScopedCSS parser can handle it properly - -### v3.0.0 -- Deprecate old `babel` support, now the `es6` parser will use Babel 6 by default -- Change css always scoped by default -- Fix all the `value` attributes using expressions will be output as `riot-value` to [riot#1957](https://github.com/riot/riot/issues/1957) - -### v2.5.5 -- Fix to erroneous version number in the package.json, v2.5.4 was released before. -- Removed unuseful files from the npm package. -- Updated credits in package.json -- Updated devDependencies, skip ESLint in CI test for node v0.12 -- BuGless-hack for [riot#1966](https://github.com/riot/riot/issues/1966) - You can use `<-/>` to signal the end of the html if your html is ending with an expression. - -### v2.5.4 -- Fix #68 : SASS inside Pug template gives Invalid CSS. -- Added parser for [bublé](https://buble.surge.sh) as `buble` in the browser. Option `modules` is `false` in all versions. -- Added parser for [bublé](https://buble.surge.sh) as `buble`. -- Added support for es6 `import` statements. Thanks to @kuashe! - Related to [riot#1715](https://github.com/riot/riot/issues/1715), [riot#1784](https://github.com/riot/riot/issues/1784), and [riot#1864](https://github.com/riot/riot/issues/1864). - -### v2.5.3 -- Fix #73 : resolveModuleSource must be a function - Option removed from the default Babel options. -- Updated node.js to 4.4 in the Travis environment. -- Downgraded ESLint to 2.x for using with node v0.12.x - -### v2.5.2 -- Fix #72: `undefined` is not a function when evaluating `parsers._req`. -- Updated node versions for travis, including v5.x - -### v2.4.1 - -- Add the `pug` parser (it will replace completely `jade` in the next major release) -- Add the possibility to pass custom parsers options directly via the `compiler.compile` method through the `parserOptions: {js: {}, template: {}, style: {}}` key [more info](https://github.com/riot/compiler/issues/64) -- Fix un-escape parser options in html [more info](https://github.com/riot/compiler/issues/63) - -### v2.3.23 -- The parsers are moved to its own directory in the node version. The load is on first use. -- Fix [riot#1325](https://github.com/riot/riot/issues/1325) : Gulp + Browserify + Babelify + type="es6" error. -- Fix [riot#1342](https://github.com/riot/riot/issues/1342), [riot#1636](https://github.com/riot/riot/issues/1636) and request from [dwyl/learn-riot#8](https://github.com/dwyl/learn-riot/issues/8) : Server-Side Rendered Page Fails W3C Check. The new `data-is` attribute is used for scoped styles in addition to `riot-tag` (the later will be removed in compiler v3.x) -- The keyword `defer` in ` or - pushTag(state, `/${name}`, start, end); - break - } - case data[pos] === '<': - state.pos++; - return TAG - default: - expr(state, null, '<', pos); - } - - return TEXT - } - - /** - * Parse the text content depending on the name - * @param {ParserState} state - Parser state - * @param {string} name - one of the tags matched by the RE_SCRYLE regex - * @param {Array} match - result of the regex matching the content of the parsed tag - * @returns {undefined} void function - */ - function parseSpecialTagsContent(state, name, match) { - const { pos } = state; - const start = match.index; - - if (name === TEXTAREA_TAG) { - expr(state, null, match[0], pos); - } else { - pushText(state, pos, start); - } - } - - /*--------------------------------------------------------------------- - * Tree builder for the riot tag parser. - * - * The output has a root property and separate arrays for `html`, `css`, - * and `js` tags. - * - * The root tag is included as first element in the `html` array. - * Script tags marked with "defer" are included in `html` instead `js`. - * - * - Mark SVG tags - * - Mark raw tags - * - Mark void tags - * - Split prefixes from expressions - * - Unescape escaped brackets and escape EOLs and backslashes - * - Compact whitespace (option `compact`) for non-raw tags - * - Create an array `parts` for text nodes and attributes - * - * Throws on unclosed tags or closing tags without start tag. - * Selfclosing and void tags has no nodes[] property. - */ - - /** - * Escape the carriage return and the line feed from a string - * @param {string} string - input string - * @returns {string} output string escaped - */ - function escapeReturn(string) { - return string - .replace(/\r/g, '\\r') - .replace(/\n/g, '\\n') - } - - /** - * 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 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 - } - } - }, - closeTag(store, node) { - const last = store.scryle || store.last; - - last.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; - - if ([JAVASCRIPT_TAG, STYLE_TAG].includes(name)) { - // Only accept one of each - if (store[name]) { - panic$1(this.store.data, duplicatedNamedTag.replace('%1', name), node.start); - } - - store[name] = node; - store.scryle = store[name]; - - } else { - // store.last holds the last tag pushed in the stack and this are - // non-void, non-empty tags, so we are sure the `lastTag` here - // have a `nodes` property. - const lastTag = store.last; - const newNode = node; - - lastTag.nodes.push(newNode); - - if (lastTag[IS_RAW] || RAW_TAGS.test(name)) { - node[IS_RAW] = true; - } - - if (!node[IS_SELF_CLOSING] && !node[IS_VOID]) { - store.stack.push(lastTag); - newNode.nodes = []; - store.last = newNode; - } - } - - if (attrs) { - this.attrs(attrs); - } - }, - attrs(attributes) { - attributes.forEach(attr => { - if (attr.value) { - this.split(attr, attr.value, attr.valueStart, true); - } - }); - }, - pushText(store, node) { - const text = node.text; - const empty = !/\S/.test(text); - const scryle = store.scryle; - if (!scryle) { - // store.last always have a nodes property - const parent = store.last; - - const pack = this.compact && !parent[IS_RAW]; - if (pack && empty) { - return - } - this.split(node, text, node.start, pack); - parent.nodes.push(node); - } else if (!empty) { - scryle.text = node; - } - }, - split(node, source, start, pack) { - const expressions = node.expressions; - const parts = []; - - if (expressions) { - let pos = 0; - - expressions.forEach(expr => { - const text = source.slice(pos, expr.start - start); - const code = expr.text; - parts.push(this.sanitise(node, text, pack), escapeReturn(escapeSlashes(code).trim())); - pos = expr.end - start; - }); - - if (pos < node.end) { - parts.push(this.sanitise(node, source.slice(pos), pack)); - } - } else { - parts[0] = this.sanitise(node, source, pack); - } - - node.parts = parts.filter(p => p); // remove the empty strings - }, - // unescape escaped brackets and split prefixes of expressions - sanitise(node, text, pack) { - let rep = node.unescape; - if (rep) { - let idx = 0; - rep = `\\${rep}`; - while ((idx = text.indexOf(rep, idx)) !== -1) { - text = text.substr(0, idx) + text.substr(idx + 1); - idx++; - } - } - - text = escapeSlashes(text); - - return pack ? cleanSpaces(text) : escapeReturn(text) - } - }); - - function createTreeBuilder(data, options) { - const root = { - type: TAG, - name: '', - start: 0, - end: 0, - nodes: [] - }; - - return Object.assign(Object.create(TREE_BUILDER_STRUCT), { - compact: options.compact !== false, - store: { - last: root, - stack: [], - scryle: null, - root, - style: null, - script: null, - data - } - }) - } - - /** - * Factory for the Parser class, exposing only the `parse` method. - * The export adds the Parser class as property. - * - * @param {Object} options - User Options - * @param {Function} customBuilder - Tree builder factory - * @returns {Function} Public Parser implementation. - */ - function parser$1(options, customBuilder) { - const state = curry(createParserState)(options, customBuilder || createTreeBuilder); - return { - parse: (data) => parse(state(data)) - } - } - - /** - * Create a new state object - * @param {Object} userOptions - parser options - * @param {Function} builder - Tree builder factory - * @param {string} data - data to parse - * @returns {ParserState} it represents the current parser state - */ - function createParserState(userOptions, builder, data) { - const options = Object.assign({ - brackets: ['{', '}'] - }, userOptions); - - return { - options, - regexCache: {}, - pos: 0, - count: -1, - root: null, - last: null, - scryle: null, - builder: builder(data, options), - data - } - } - - /** - * It creates a raw output of pseudo-nodes with one of three different types, - * all of them having a start/end position: - * - * - TAG -- Opening or closing tags - * - TEXT -- Raw text - * - COMMENT -- Comments - * - * @param {ParserState} state - Current parser state - * @returns {ParserResult} Result, contains data and output properties. - */ - function parse(state) { - const { data } = state; - - walk(state); - flush(state); - - if (state.count) { - panic$1(data, state.count > 0 ? unexpectedEndOfFile : rootTagNotFound, state.pos); - } - - return { - data, - output: state.builder.get() - } - } - - /** - * Parser walking recursive function - * @param {ParserState} state - Current parser state - * @param {string} type - current parsing context - * @returns {undefined} void function - */ - function walk(state, type) { - const { data } = state; - // extend the state adding the tree builder instance and the initial data - const length = data.length; - - // The "count" property is set to 1 when the first tag is found. - // This becomes the root and precedent text or comments are discarded. - // So, at the end of the parsing count must be zero. - if (state.pos < length && state.count) { - walk(state, eat(state, type)); - } - } - - /** - * Function to help iterating on the current parser state - * @param {ParserState} state - Current parser state - * @param {string} type - current parsing context - * @returns {string} parsing context - */ - function eat(state, type) { - switch (type) { - case TAG: - return tag(state) - case ATTR: - return attr(state) - default: - return text(state) - } - } - - /** - * The nodeTypes definition - */ - const nodeTypes = types$3; - - // import {IS_BOOLEAN,IS_CUSTOM,IS_RAW,IS_SPREAD,IS_VOID} from '@riotjs/parser/src/constants' - - const BINDING_TYPES = 'bindingTypes'; - const EACH_BINDING_TYPE = 'EACH'; - const IF_BINDING_TYPE = 'IF'; - const TAG_BINDING_TYPE = 'TAG'; - const SLOT_BINDING_TYPE = 'SLOT'; - - - const EXPRESSION_TYPES = 'expressionTypes'; - const ATTRIBUTE_EXPRESSION_TYPE = 'ATTRIBUTE'; - const VALUE_EXPRESSION_TYPE = 'VALUE'; - const TEXT_EXPRESSION_TYPE = 'TEXT'; - const EVENT_EXPRESSION_TYPE = 'EVENT'; - - const TEMPLATE_FN = 'template'; - const SCOPE = 'scope'; - const GET_COMPONENT_FN = 'getComponent'; - - // keys needed to create the DOM bindings - const BINDING_SELECTOR_KEY = 'selector'; - const BINDING_GET_COMPONENT_KEY = 'getComponent'; - const BINDING_TEMPLATE_KEY = 'template'; - const BINDING_TYPE_KEY = 'type'; - const BINDING_REDUNDANT_ATTRIBUTE_KEY = 'redundantAttribute'; - const BINDING_CONDITION_KEY = 'condition'; - const BINDING_ITEM_NAME_KEY = 'itemName'; - const BINDING_GET_KEY_KEY = 'getKey'; - const BINDING_INDEX_NAME_KEY = 'indexName'; - const BINDING_EVALUATE_KEY = 'evaluate'; - const BINDING_NAME_KEY = 'name'; - const BINDING_SLOTS_KEY = 'slots'; - const BINDING_EXPRESSIONS_KEY = 'expressions'; - const BINDING_CHILD_NODE_INDEX_KEY = 'childNodeIndex'; - // slots keys - const BINDING_BINDINGS_KEY = 'bindings'; - const BINDING_ID_KEY = 'id'; - const BINDING_HTML_KEY = 'html'; - const BINDING_ATTRIBUTES_KEY = 'attributes'; - - // DOM directives - const IF_DIRECTIVE = 'if'; - const EACH_DIRECTIVE = 'each'; - const KEY_ATTRIBUTE = 'key'; - const SLOT_ATTRIBUTE = 'slot'; - const NAME_ATTRIBUTE = 'name'; - const IS_DIRECTIVE = 'is'; - - // Misc - const DEFAULT_SLOT_NAME = 'default'; - const TEXT_NODE_EXPRESSION_PLACEHOLDER = ''; - const BINDING_SELECTOR_PREFIX = 'expr'; - const SLOT_TAG_NODE_NAME = 'slot'; - const PROGRESS_TAG_NODE_NAME = 'progress'; - const IS_VOID_NODE = 'isVoid'; - const IS_CUSTOM_NODE = 'isCustom'; - const IS_BOOLEAN_ATTRIBUTE = 'isBoolean'; - const IS_SPREAD_ATTRIBUTE = 'isSpread'; - - /** - * True if the node has not expression set nor bindings directives - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only if it's a static node that doesn't need bindings or expressions - */ - function isStaticNode(node) { - return [ - hasExpressions, - findEachAttribute, - findIfAttribute, - isCustomNode, - isSlotNode - ].every(test => !test(node)) - } - - /** - * Check if a node name is part of the browser or builtin javascript api or it belongs to the current scope - * @param { types.NodePath } path - containing the current node visited - * @returns {boolean} true if it's a global api variable - */ - function isGlobal({ scope, node }) { - return Boolean( - isRaw(node) || - isBuiltinAPI(node) || - isBrowserAPI(node) || - isNewExpression(node) || - isNodeInScope(scope, node), - ) - } - - /** - * Checks if the identifier of a given node exists in a scope - * @param {Scope} scope - scope where to search for the identifier - * @param {types.Node} node - node to search for the identifier - * @returns {boolean} true if the node identifier is defined in the given scope - */ - function isNodeInScope(scope, node) { - const traverse = (isInScope = false) => { - types$1.visit(node, { - visitIdentifier(path) { - if (scope.lookup(getName$1(path.node))) { - isInScope = true; - } - - this.abort(); - } - }); - - return isInScope - }; - - return traverse() - } - - /** - * True if the node has the isCustom attribute set - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if either it's a riot component or a custom element - */ - function isCustomNode(node) { - return !!(node[IS_CUSTOM_NODE] || hasIsAttribute(node)) - } - - /** - * True the node is - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if it's a slot node - */ - function isSlotNode(node) { - return node.name === SLOT_TAG_NODE_NAME - } - - /** - * True if the node has the isVoid attribute set - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if the node is self closing - */ - function isVoidNode(node) { - return !!node[IS_VOID_NODE] - } - - /** - * True if the riot parser did find a tag node - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the tag nodes - */ - function isTagNode(node) { - return node.type === nodeTypes.TAG - } - - /** - * True if the riot parser did find a text node - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the text nodes - */ - function isTextNode(node) { - return node.type === nodeTypes.TEXT - } - - /** - * True if the node parsed is the root one - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the root nodes - */ - function isRootNode(node) { - return node.isRoot - } - - /** - * True if the attribute parsed is of type spread one - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if the attribute node is of type spread - */ - function isSpreadAttribute$1(node) { - return node[IS_SPREAD_ATTRIBUTE] - } - - /** - * True if the node is an attribute and its name is "value" - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for value attribute nodes - */ - function isValueAttribute(node) { - return node.name === 'value' - } - - /** - * True if the DOM node is a progress tag - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true for the progress tags - */ - function isProgressNode(node) { - return node.name === PROGRESS_TAG_NODE_NAME - } - - /** - * True if the node is an attribute and a DOM handler - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for dom listener attribute nodes - */ - const isEventAttribute = (() => { - const EVENT_ATTR_RE = /^on/; - return node => EVENT_ATTR_RE.test(node.name) - })(); - - /** - * True if the node has expressions or expression attributes - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} ditto - */ - function hasExpressions(node) { - return !!( - node.expressions || - // has expression attributes - (getNodeAttributes(node).some(attribute => hasExpressions(attribute))) || - // has child text nodes with expressions - (node.nodes && node.nodes.some(node => isTextNode(node) && hasExpressions(node))) - ) - } - - /** - * True if the node is a directive having its own template - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the IF EACH and TAG bindings - */ - function hasItsOwnTemplate(node) { - return [ - findEachAttribute, - findIfAttribute, - isCustomNode - ].some(test => test(node)) - } - - const hasIfAttribute = compose(Boolean, findIfAttribute); - const hasEachAttribute = compose(Boolean, findEachAttribute); - const hasIsAttribute = compose(Boolean, findIsAttribute); - const hasKeyAttribute = compose(Boolean, findKeyAttribute); - - /** - * Find the attribute node - * @param { string } name - name of the attribute we want to find - * @param { riotParser.nodeTypes.TAG } node - a tag node - * @returns { riotParser.nodeTypes.ATTR } attribute node - */ - function findAttribute(name, node) { - return node.attributes && node.attributes.find(attr => getName$1(attr) === name) - } - - function findIfAttribute(node) { - return findAttribute(IF_DIRECTIVE, node) - } - - function findEachAttribute(node) { - return findAttribute(EACH_DIRECTIVE, node) - } - - function findKeyAttribute(node) { - return findAttribute(KEY_ATTRIBUTE, node) - } - - function findIsAttribute(node) { - return findAttribute(IS_DIRECTIVE, node) - } - - /** - * Find all the node attributes that are not expressions - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} list of all the static attributes - */ - function findStaticAttributes(node) { - return getNodeAttributes(node).filter(attribute => !hasExpressions(attribute)) - } - - /** - * Find all the node attributes that have expressions - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} list of all the dynamic attributes - */ - function findDynamicAttributes(node) { - return getNodeAttributes(node).filter(hasExpressions) - } - - /** - * Unescape the user escaped chars - * @param {string} string - input string - * @param {string} char - probably a '{' or anything the user want's to escape - * @returns {string} cleaned up string - */ - function unescapeChar(string, char) { - return string.replace(RegExp(`\\\\${char}`, 'gm'), char) - } - - const scope$1 = builders.identifier(SCOPE); - const getName$1 = node => node && node.name ? node.name : node; - - /** - * Replace the path scope with a member Expression - * @param { types.NodePath } path - containing the current node visited - * @param { types.Node } property - node we want to prefix with the scope identifier - * @returns {undefined} this is a void function - */ - function replacePathScope(path, property) { - path.replace(builders.memberExpression( - scope$1, - property, - false - )); - } - - /** - * Change the nodes scope adding the `scope` prefix - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean } return false if we want to stop the tree traversal - * @context { types.visit } - */ - function updateNodeScope(path) { - if (!isGlobal(path)) { - replacePathScope(path, path.node); - - return false - } - - this.traverse(path); - } - - /** - * Change the scope of the member expressions - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean } return always false because we want to check only the first node object - */ - function visitMemberExpression(path) { - if (!isGlobal(path) && !isGlobal({ node: path.node.object, scope: path.scope })) { - if (path.value.computed) { - this.traverse(path); - } else if (isBinaryExpression(path.node.object) || path.node.object.computed) { - this.traverse(path.get('object')); - } else if (!path.node.object.callee) { - replacePathScope(path, isThisExpression(path.node.object) ? path.node.property : path.node); - } else { - this.traverse(path.get('object')); - } - } - - return false - } - - - /** - * Objects properties should be handled a bit differently from the Identifier - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean } return false if we want to stop the tree traversal - */ - function visitProperty(path) { - const value = path.node.value; - - if (isIdentifier(value)) { - updateNodeScope(path.get('value')); - } else { - this.traverse(path.get('value')); - } - - return false - } - - /** - * The this expressions should be replaced with the scope - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean|undefined } return false if we want to stop the tree traversal - */ - function visitThisExpression(path) { - path.replace(scope$1); - this.traverse(path); - } - - - /** - * Update the scope of the global nodes - * @param { Object } ast - ast program - * @returns { Object } the ast program with all the global nodes updated - */ - function updateNodesScope(ast) { - const ignorePath = () => false; - - types$1.visit(ast, { - visitIdentifier: updateNodeScope, - visitMemberExpression, - visitProperty, - visitThisExpression, - visitClassExpression: ignorePath - }); - - return ast - } - - /** - * Convert any expression to an AST tree - * @param { Object } expression - expression parsed by the riot parser - * @param { string } sourceFile - original tag file - * @param { string } sourceCode - original tag source code - * @returns { Object } the ast generated - */ - function createASTFromExpression(expression, sourceFile, sourceCode) { - const code = sourceFile ? - addLineOffset(expression.text, sourceCode, expression) : - expression.text; - - return generateAST(`(${code})`, { - sourceFileName: sourceFile - }) - } - - /** - * Create the bindings template property - * @param {Array} args - arguments to pass to the template function - * @returns {ASTNode} a binding template key - */ - function createTemplateProperty(args) { - return simplePropertyNode( - BINDING_TEMPLATE_KEY, - args ? callTemplateFunction(...args) : nullNode() - ) - } - - /** - * Try to get the expression of an attribute node - * @param { RiotParser.Node.Attribute } attribute - riot parser attribute node - * @returns { RiotParser.Node.Expression } attribute expression value - */ - function getAttributeExpression(attribute) { - return attribute.expressions ? attribute.expressions[0] : { - // if no expression was found try to typecast the attribute value - ...attribute, - text: attribute.value - } - } - - /** - * Wrap the ast generated in a function call providing the scope argument - * @param {Object} ast - function body - * @returns {FunctionExpresion} function having the scope argument injected - */ - function wrapASTInFunctionWithScope(ast) { - return builders.functionExpression( - null, - [scope$1], - builders.blockStatement([builders.returnStatement( - ast - )]) - ) - } - - /** - * Convert any parser option to a valid template one - * @param { RiotParser.Node.Expression } expression - expression parsed by the riot parser - * @param { string } sourceFile - original tag file - * @param { string } sourceCode - original tag source code - * @returns { Object } a FunctionExpression object - * - * @example - * toScopedFunction('foo + bar') // scope.foo + scope.bar - * - * @example - * toScopedFunction('foo.baz + bar') // scope.foo.baz + scope.bar - */ - function toScopedFunction(expression, sourceFile, sourceCode) { - return compose( - wrapASTInFunctionWithScope, - transformExpression, - )(expression, sourceFile, sourceCode) - } - - /** - * Transform an expression node updating its global scope - * @param {RiotParser.Node.Expr} expression - riot parser expression node - * @param {string} sourceFile - source file - * @param {string} sourceCode - source code - * @returns {ASTExpression} ast expression generated from the riot parser expression node - */ - function transformExpression(expression, sourceFile, sourceCode) { - return compose( - getExpressionAST, - updateNodesScope, - createASTFromExpression - )(expression, sourceFile, sourceCode) - } - - /** - * Get the parsed AST expression of riot expression node - * @param {AST.Program} sourceAST - raw node parsed - * @returns {AST.Expression} program expression output - */ - function getExpressionAST(sourceAST) { - const astBody = sourceAST.program.body; - - return astBody[0] ? astBody[0].expression : astBody - } - - /** - * Create the template call function - * @param {Array|string|Node.Literal} template - template string - * @param {Array} bindings - template bindings provided as AST nodes - * @returns {Node.CallExpression} template call expression - */ - function callTemplateFunction(template, bindings) { - return builders.callExpression(builders.identifier(TEMPLATE_FN), [ - template ? builders.literal(template) : nullNode(), - bindings ? builders.arrayExpression(bindings) : nullNode() - ]) - } - - /** - * Convert any DOM attribute into a valid DOM selector useful for the querySelector API - * @param { string } attributeName - name of the attribute to query - * @returns { string } the attribute transformed to a query selector - */ - const attributeNameToDOMQuerySelector = attributeName => `[${attributeName}]`; - - /** - * Create the properties to query a DOM node - * @param { string } attributeName - attribute name needed to identify a DOM node - * @returns { Array } array containing the selector properties needed for the binding - */ - function createSelectorProperties(attributeName) { - return attributeName ? [ - simplePropertyNode(BINDING_REDUNDANT_ATTRIBUTE_KEY, builders.literal(attributeName)), - simplePropertyNode(BINDING_SELECTOR_KEY, - compose(builders.literal, attributeNameToDOMQuerySelector)(attributeName) - ) - ] : [] - } - - /** - * Clone the node filtering out the selector attribute from the attributes list - * @param {RiotParser.Node} node - riot parser node - * @param {string} selectorAttribute - name of the selector attribute to filter out - * @returns {RiotParser.Node} the node with the attribute cleaned up - */ - function cloneNodeWithoutSelectorAttribute(node, selectorAttribute) { - return { - ...node, - attributes: getAttributesWithoutSelector(getNodeAttributes(node), selectorAttribute) - } - } - - - /** - * Get the node attributes without the selector one - * @param {Array} attributes - attributes list - * @param {string} selectorAttribute - name of the selector attribute to filter out - * @returns {Array} filtered attributes - */ - function getAttributesWithoutSelector(attributes, selectorAttribute) { - if (selectorAttribute) - return attributes.filter(attribute => attribute.name !== selectorAttribute) - - return attributes - } - - /** - * Clean binding or custom attributes - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} only the attributes that are not bindings or directives - */ - function cleanAttributes(node) { - return getNodeAttributes(node).filter(attribute => ![ - IF_DIRECTIVE, - EACH_DIRECTIVE, - KEY_ATTRIBUTE, - SLOT_ATTRIBUTE, - IS_DIRECTIVE - ].includes(attribute.name)) - } - - /** - * Create a root node proxing only its nodes and attributes - * @param {RiotParser.Node} node - riot parser node - * @returns {RiotParser.Node} root node - */ - function createRootNode(node) { - return { - nodes: getChildrenNodes(node), - isRoot: true, - // root nodes shuold't have directives - attributes: cleanAttributes(node) - } - } - - /** - * Get all the child nodes of a RiotParser.Node - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} all the child nodes found - */ - function getChildrenNodes(node) { - return node && node.nodes ? node.nodes : [] - } - - /** - * Get all the attributes of a riot parser node - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} all the attributes find - */ - function getNodeAttributes(node) { - return node.attributes ? node.attributes : [] - } - /** - * Get the name of a custom node transforming it into an expression node - * @param {RiotParser.Node} node - riot parser node - * @returns {RiotParser.Node.Attr} the node name as expression attribute - */ - function getCustomNodeNameAsExpression(node) { - const isAttribute = findIsAttribute(node); - const toRawString = val => `'${val}'`; - - if (isAttribute) { - return isAttribute.expressions ? isAttribute.expressions[0] : { - ...isAttribute, - text: toRawString(isAttribute.value) - } - } - - return { ...node, text: toRawString(getName$1(node)) } - } - - /** - * Convert all the node static attributes to strings - * @param {RiotParser.Node} node - riot parser node - * @returns {string} all the node static concatenated as string - */ - function staticAttributesToString(node) { - return findStaticAttributes(node) - .map(attribute => attribute[IS_BOOLEAN_ATTRIBUTE] || !attribute.value ? - attribute.name : - `${attribute.name}="${unescapeNode(attribute, 'value').value}"` - ).join(' ') - } - - /** - * Make sure that node escaped chars will be unescaped - * @param {RiotParser.Node} node - riot parser node - * @param {string} key - key property to unescape - * @returns {RiotParser.Node} node with the text property unescaped - */ - function unescapeNode(node, key) { - if (node.unescape) { - return { - ...node, - [key]: unescapeChar(node[key], node.unescape) - } - } - - return node - } - - - /** - * Convert a riot parser opening node into a string - * @param {RiotParser.Node} node - riot parser node - * @returns {string} the node as string - */ - function nodeToString(node) { - const attributes = staticAttributesToString(node); - - switch(true) { - case isTagNode(node): - return `<${node.name}${attributes ? ` ${attributes}` : ''}${isVoidNode(node) ? '/' : ''}>` - case isTextNode(node): - return hasExpressions(node) ? TEXT_NODE_EXPRESSION_PLACEHOLDER : unescapeNode(node, 'text').text - default: - return '' - } - } - - /** - * Close an html node - * @param {RiotParser.Node} node - riot parser node - * @returns {string} the closing tag of the html tag node passed to this function - */ - function closeTag(node) { - return node.name ? `` : '' - } - - /** - * Create a strings array with the `join` call to transform it into a string - * @param {Array} stringsArray - array containing all the strings to concatenate - * @returns {AST.CallExpression} array with a `join` call - */ - function createArrayString(stringsArray) { - return builders.callExpression( - builders.memberExpression( - builders.arrayExpression(stringsArray), - builders.identifier('join'), - false - ), - [builders.literal('')], - ) - } - - /** - * Simple expression bindings might contain multiple expressions like for example: "class="{foo} red {bar}"" - * This helper aims to merge them in a template literal if it's necessary - * @param {RiotParser.Attr} node - riot parser node - * @param {string} sourceFile - original tag file - * @param {string} sourceCode - original tag source code - * @returns { Object } a template literal expression object - */ - function mergeAttributeExpressions(node, sourceFile, sourceCode) { - if (!node.parts || node.parts.length === 1) { - return transformExpression(node.expressions[0], sourceFile, sourceCode) - } - const stringsArray = [ - ...node.parts.reduce((acc, str) => { - const expression = node.expressions.find(e => e.text.trim() === str); - - return [ - ...acc, - expression ? transformExpression(expression, sourceFile, sourceCode) : builders.literal(str) - ] - }, []) - ].filter(expr => !isLiteral(expr) || expr.value); - - - return createArrayString(stringsArray) - } - - /** - * Create a selector that will be used to find the node via dom-bindings - * @param {number} id - temporary variable that will be increased anytime this function will be called - * @returns {string} selector attribute needed to bind a riot expression - */ - const createBindingSelector = (function createSelector(id = 0) { - return () => `${BINDING_SELECTOR_PREFIX}${id++}` - }()); - - /** - * Create an attribute evaluation function - * @param {RiotParser.Attr} sourceNode - riot parser node - * @param {string} sourceFile - original tag file - * @param {string} sourceCode - original tag source code - * @returns { AST.Node } an AST function expression to evaluate the attribute value - */ - function createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) { - return hasExpressions(sourceNode) ? - // dynamic attribute - wrapASTInFunctionWithScope(mergeAttributeExpressions(sourceNode, sourceFile, sourceCode)) : - // static attribute - builders.functionExpression( - null, - [], - builders.blockStatement([ - builders.returnStatement(builders.literal(sourceNode.value || true)) - ]), - ) - } - - /** - * Simple clone deep function, do not use it for classes or recursive objects! - * @param {*} source - possibily an object to clone - * @returns {*} the object we wanted to clone - */ - function cloneDeep(source) { - return JSON.parse(JSON.stringify(source)) - } - - const getEachItemName = expression => isSequenceExpression(expression.left) ? expression.left.expressions[0] : expression.left; - const getEachIndexName = expression => isSequenceExpression(expression.left) ? expression.left.expressions[1] : null; - const getEachValue = expression => expression.right; - const nameToliteral = compose(builders.literal, getName$1); - - const generateEachItemNameKey = expression => simplePropertyNode( - BINDING_ITEM_NAME_KEY, - compose(nameToliteral, getEachItemName)(expression) - ); - - const generateEachIndexNameKey = expression => simplePropertyNode( - BINDING_INDEX_NAME_KEY, - compose(nameToliteral, getEachIndexName)(expression) - ); - - const generateEachEvaluateKey = (expression, eachExpression, sourceFile, sourceCode) => simplePropertyNode( - BINDING_EVALUATE_KEY, - compose( - e => toScopedFunction(e, sourceFile, sourceCode), - e => ({ - ...eachExpression, - text: generateJavascript(e).code - }), - getEachValue - )(expression) - ); - - /** - * Get the each expression properties to create properly the template binding - * @param { DomBinding.Expression } eachExpression - original each expression data - * @param { string } sourceFile - original tag file - * @param { string } sourceCode - original tag source code - * @returns { Array } AST nodes that are needed to build an each binding - */ - function generateEachExpressionProperties(eachExpression, sourceFile, sourceCode) { - const ast = createASTFromExpression(eachExpression, sourceFile, sourceCode); - const body = ast.program.body; - const firstNode = body[0]; - - if (!isExpressionStatement(firstNode)) { - panic(`The each directives supported should be of type "ExpressionStatement",you have provided a "${firstNode.type}"`); - } - - const { expression } = firstNode; - - return [ - generateEachItemNameKey(expression), - generateEachIndexNameKey(expression), - generateEachEvaluateKey(expression, eachExpression, sourceFile, sourceCode) - ] - } - - /** - * Transform a RiotParser.Node.Tag into an each binding - * @param { RiotParser.Node.Tag } sourceNode - tag containing the each attribute - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } an each binding node - */ - function createEachBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - const [ifAttribute, eachAttribute, keyAttribute] = [ - findIfAttribute, - findEachAttribute, - findKeyAttribute - ].map(f => f(sourceNode)); - const attributeOrNull = attribute => attribute ? toScopedFunction(getAttributeExpression(attribute), sourceFile, sourceCode) : nullNode(); - - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(EACH_BINDING_TYPE), - false - ), - ), - simplePropertyNode(BINDING_GET_KEY_KEY, attributeOrNull(keyAttribute)), - simplePropertyNode(BINDING_CONDITION_KEY, attributeOrNull(ifAttribute)), - createTemplateProperty(createNestedBindings(sourceNode, sourceFile, sourceCode, selectorAttribute)), - ...createSelectorProperties(selectorAttribute), - ...compose(generateEachExpressionProperties, getAttributeExpression)(eachAttribute) - ]) - } - - /** - * Transform a RiotParser.Node.Tag into an if binding - * @param { RiotParser.Node.Tag } sourceNode - tag containing the if attribute - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { stiring } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } an if binding node - */ - function createIfBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - const ifAttribute = findIfAttribute(sourceNode); - - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(IF_BINDING_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_EVALUATE_KEY, - toScopedFunction(ifAttribute.expressions[0], sourceFile, sourceCode) - ), - ...createSelectorProperties(selectorAttribute), - createTemplateProperty(createNestedBindings(sourceNode, sourceFile, sourceCode, selectorAttribute)) - ]) - } - - /** - * Create a simple attribute expression - * @param {RiotParser.Node.Attr} sourceNode - the custom tag - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {AST.Node} object containing the expression binding keys - */ - function createAttributeExpression(sourceNode, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(ATTRIBUTE_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode(BINDING_NAME_KEY, isSpreadAttribute$1(sourceNode) ? nullNode() : builders.literal(sourceNode.name)), - simplePropertyNode( - BINDING_EVALUATE_KEY, - createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) - ) - ]) - } - - /** - * Create a simple event expression - * @param {RiotParser.Node.Attr} sourceNode - attribute containing the event handlers - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {AST.Node} object containing the expression binding keys - */ - function createEventExpression(sourceNode, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(EVENT_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode(BINDING_NAME_KEY, builders.literal(sourceNode.name)), - simplePropertyNode( - BINDING_EVALUATE_KEY, - createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) - ) - ]) - } - - /** - * Generate the pure immutable string chunks from a RiotParser.Node.Text - * @param {RiotParser.Node.Text} node - riot parser text node - * @param {string} sourceCode sourceCode - source code - * @returns {Array} array containing the immutable string chunks - */ - function generateLiteralStringChunksFromNode(node, sourceCode) { - return node.expressions.reduce((chunks, expression, index) => { - const start = index ? node.expressions[index - 1].end : node.start; - - chunks.push(sourceCode.substring(start, expression.start)); - - // add the tail to the string - if (index === node.expressions.length - 1) - chunks.push(sourceCode.substring(expression.end, node.end)); - - return chunks - }, []).map(str => node.unescape ? unescapeChar(str, node.unescape) : str) - } - - /** - * Simple bindings might contain multiple expressions like for example: "{foo} and {bar}" - * This helper aims to merge them in a template literal if it's necessary - * @param {RiotParser.Node} node - riot parser node - * @param {string} sourceFile - original tag file - * @param {string} sourceCode - original tag source code - * @returns { Object } a template literal expression object - */ - function mergeNodeExpressions(node, sourceFile, sourceCode) { - if (node.parts.length === 1) - return transformExpression(node.expressions[0], sourceFile, sourceCode) - - const pureStringChunks = generateLiteralStringChunksFromNode(node, sourceCode); - const stringsArray = pureStringChunks.reduce((acc, str, index) => { - const expr = node.expressions[index]; - - return [ - ...acc, - builders.literal(str), - expr ? transformExpression(expr, sourceFile, sourceCode) : nullNode() - ] - }, []) - // filter the empty literal expressions - .filter(expr => !isLiteral(expr) || expr.value); - - return createArrayString(stringsArray) - } - - /** - * Create a text expression - * @param {RiotParser.Node.Text} sourceNode - text node to parse - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {number} childNodeIndex - position of the child text node in its parent children nodes - * @returns {AST.Node} object containing the expression binding keys - */ - function createTextExpression(sourceNode, sourceFile, sourceCode, childNodeIndex) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(TEXT_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_CHILD_NODE_INDEX_KEY, - builders.literal(childNodeIndex) - ), - simplePropertyNode( - BINDING_EVALUATE_KEY, - wrapASTInFunctionWithScope( - mergeNodeExpressions(sourceNode, sourceFile, sourceCode) - ) - ) - ]) - } - - function createValueExpression(sourceNode, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(VALUE_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_EVALUATE_KEY, - createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) - ) - ]) - } - - function createExpression(sourceNode, sourceFile, sourceCode, childNodeIndex, parentNode) { - switch (true) { - case isTextNode(sourceNode): - return createTextExpression(sourceNode, sourceFile, sourceCode, childNodeIndex) - // progress nodes value attributes will be rendered as attributes - // see https://github.com/riot/compiler/issues/122 - case isValueAttribute(sourceNode) && hasValueAttribute(parentNode.name) && !isProgressNode(parentNode): - return createValueExpression(sourceNode, sourceFile, sourceCode) - case isEventAttribute(sourceNode): - return createEventExpression(sourceNode, sourceFile, sourceCode) - default: - return createAttributeExpression(sourceNode, sourceFile, sourceCode) - } - } - - /** - * Create the attribute expressions - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {Array} array containing all the attribute expressions - */ - function createAttributeExpressions(sourceNode, sourceFile, sourceCode) { - return findDynamicAttributes(sourceNode) - .map(attribute => createExpression(attribute, sourceFile, sourceCode, 0, sourceNode)) - } - - /** - * Create the text node expressions - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {Array} array containing all the text node expressions - */ - function createTextNodeExpressions(sourceNode, sourceFile, sourceCode) { - const childrenNodes = getChildrenNodes(sourceNode); - - return childrenNodes - .filter(isTextNode) - .filter(hasExpressions) - .map(node => createExpression( - node, - sourceFile, - sourceCode, - childrenNodes.indexOf(node), - sourceNode - )) - } - - /** - * Add a simple binding to a riot parser node - * @param { RiotParser.Node.Tag } sourceNode - tag containing the if attribute - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } an each binding node - */ - function createSimpleBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - return builders.objectExpression([ - ...createSelectorProperties(selectorAttribute), - simplePropertyNode( - BINDING_EXPRESSIONS_KEY, - builders.arrayExpression([ - ...createTextNodeExpressions(sourceNode, sourceFile, sourceCode), - ...createAttributeExpressions(sourceNode, sourceFile, sourceCode) - ]) - ) - ]) - } - - /** - * Transform a RiotParser.Node.Tag of type slot into a slot binding - * @param { RiotParser.Node.Tag } sourceNode - slot node - * @param { string } selectorAttribute - attribute needed to select the target node - * @returns { AST.Node } a slot binding node - */ - function createSlotBinding(sourceNode, selectorAttribute) { - const slotNameAttribute = findAttribute(NAME_ATTRIBUTE, sourceNode); - const slotName = slotNameAttribute ? slotNameAttribute.value : DEFAULT_SLOT_NAME; - - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(SLOT_BINDING_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_NAME_KEY, - builders.literal(slotName) - ), - ...createSelectorProperties(selectorAttribute) - ]) - } - - /** - * Find the slots in the current component and group them under the same id - * @param {RiotParser.Node.Tag} sourceNode - the custom tag - * @returns {Object} object containing all the slots grouped by name - */ - function groupSlots(sourceNode) { - return getChildrenNodes(sourceNode).reduce((acc, node) => { - const slotAttribute = findSlotAttribute(node); - - if (slotAttribute) { - acc[slotAttribute.value] = node; - } else { - acc.default = createRootNode({ - nodes: [...getChildrenNodes(acc.default), node] - }); - } - - return acc - }, { - default: null - }) - } - - /** - * Create the slot entity to pass to the riot-dom bindings - * @param {string} id - slot id - * @param {RiotParser.Node.Tag} sourceNode - slot root node - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {AST.Node} ast node containing the slot object properties - */ - function buildSlot(id, sourceNode, sourceFile, sourceCode) { - const cloneNode = { - ...sourceNode, - // avoid to render the slot attribute - attributes: getNodeAttributes(sourceNode).filter(attribute => attribute.name !== SLOT_ATTRIBUTE) - }; - const [html, bindings] = build(cloneNode, sourceFile, sourceCode); - - return builders.objectExpression([ - simplePropertyNode(BINDING_ID_KEY, builders.literal(id)), - simplePropertyNode(BINDING_HTML_KEY, builders.literal(html)), - simplePropertyNode(BINDING_BINDINGS_KEY, builders.arrayExpression(bindings)) - ]) - } - - /** - * Create the AST array containing the slots - * @param { RiotParser.Node.Tag } sourceNode - the custom tag - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns {AST.ArrayExpression} array containing the attributes to bind - */ - function createSlotsArray(sourceNode, sourceFile, sourceCode) { - return builders.arrayExpression([ - ...compose( - slots => slots.map(([key, value]) => buildSlot(key, value, sourceFile, sourceCode)), - slots => slots.filter(([,value]) => value), - Object.entries, - groupSlots - )(sourceNode) - ]) - } - - /** - * Create the AST array containing the attributes to bind to this node - * @param { RiotParser.Node.Tag } sourceNode - the custom tag - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns {AST.ArrayExpression} array containing the slot objects - */ - function createBindingAttributes(sourceNode, selectorAttribute, sourceFile, sourceCode) { - return builders.arrayExpression([ - ...compose( - attributes => attributes.map(attribute => createExpression(attribute, sourceFile, sourceCode, 0, sourceNode)), - attributes => getAttributesWithoutSelector(attributes, selectorAttribute), // eslint-disable-line - cleanAttributes - )(sourceNode) - ]) - } - - /** - * Find the slot attribute if it exists - * @param {RiotParser.Node.Tag} sourceNode - the custom tag - * @returns {RiotParser.Node.Attr|undefined} the slot attribute found - */ - function findSlotAttribute(sourceNode) { - return getNodeAttributes(sourceNode).find(attribute => attribute.name === SLOT_ATTRIBUTE) - } - - /** - * Transform a RiotParser.Node.Tag into a tag binding - * @param { RiotParser.Node.Tag } sourceNode - the custom tag - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } tag binding node - */ - function createTagBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(TAG_BINDING_TYPE), - false - ), - ), - simplePropertyNode(BINDING_GET_COMPONENT_KEY, builders.identifier(GET_COMPONENT_FN)), - simplePropertyNode( - BINDING_EVALUATE_KEY, - toScopedFunction(getCustomNodeNameAsExpression(sourceNode), sourceFile, sourceCode) - ), - simplePropertyNode(BINDING_SLOTS_KEY, createSlotsArray(sourceNode, sourceFile, sourceCode)), - simplePropertyNode( - BINDING_ATTRIBUTES_KEY, - createBindingAttributes(sourceNode, selectorAttribute, sourceFile, sourceCode) - ), - ...createSelectorProperties(selectorAttribute) - ]) - } - - const BuildingState = Object.freeze({ - html: [], - bindings: [], - parent: null - }); - - /** - * Nodes having bindings should be cloned and new selector properties should be added to them - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} bindingsSelector - temporary string to identify the current node - * @returns {RiotParser.Node} the original node parsed having the new binding selector attribute - */ - function createBindingsTag(sourceNode, bindingsSelector) { - if (!bindingsSelector) return sourceNode - - return { - ...sourceNode, - // inject the selector bindings into the node attributes - attributes: [{ - name: bindingsSelector, - value: bindingsSelector - }, ...getNodeAttributes(sourceNode)] - } - } - - /** - * Create a generic dynamic node (text or tag) and generate its bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and bindings for the current node - */ - function createDynamicNode(sourceNode, sourceFile, sourceCode, state) { - switch (true) { - case isTextNode(sourceNode): - // text nodes will not have any bindings - return [nodeToString(sourceNode), []] - default: - return createTagWithBindings(sourceNode, sourceFile, sourceCode) - } - } - - /** - * Create only a dynamic tag node with generating a custom selector and its bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and bindings for the current node - */ - function createTagWithBindings(sourceNode, sourceFile, sourceCode) { - const bindingsSelector = isRootNode(sourceNode) ? null : createBindingSelector(); - const cloneNode = createBindingsTag(sourceNode, bindingsSelector); - const tagOpeningHTML = nodeToString(cloneNode); - - switch(true) { - // EACH bindings have prio 1 - case hasEachAttribute(cloneNode): - return [tagOpeningHTML, [createEachBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - // IF bindings have prio 2 - case hasIfAttribute(cloneNode): - return [tagOpeningHTML, [createIfBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - // TAG bindings have prio 3 - case isCustomNode(cloneNode): - return [tagOpeningHTML, [createTagBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - // slot tag - case isSlotNode(cloneNode): - return [tagOpeningHTML, [createSlotBinding(cloneNode, bindingsSelector)]] - // this node has expressions bound to it - default: - return [tagOpeningHTML, [createSimpleBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - } - } - - /** - * Parse a node trying to extract its template and bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and bindings for the current node - */ - function parseNode(sourceNode, sourceFile, sourceCode, state) { - // static nodes have no bindings - if (isStaticNode(sourceNode)) return [nodeToString(sourceNode), []] - return createDynamicNode(sourceNode, sourceFile, sourceCode) - } - - /** - * Create the tag binding - * @param { RiotParser.Node.Tag } sourceNode - tag containing the each attribute - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @param { string } selector - binding selector - * @returns { Array } array with only the tag binding AST - */ - function createNestedBindings(sourceNode, sourceFile, sourceCode, selector) { - const mightBeARiotComponent = isCustomNode(sourceNode); - - return mightBeARiotComponent ? [null, [ - createTagBinding( - cloneNodeWithoutSelectorAttribute(sourceNode, selector), - null, - sourceFile, - sourceCode - )] - ] : build(createRootNode(sourceNode), sourceFile, sourceCode) - } - - /** - * Build the template and the bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and the dom bindings - */ - function build( - sourceNode, - sourceFile, - sourceCode, - state - ) { - if (!sourceNode) panic('Something went wrong with your tag DOM parsing, your tag template can\'t be created'); - - const [nodeHTML, nodeBindings] = parseNode(sourceNode, sourceFile, sourceCode); - const childrenNodes = getChildrenNodes(sourceNode); - const currentState = { ...cloneDeep(BuildingState), ...state }; - - // mutate the original arrays - currentState.html.push(...nodeHTML); - currentState.bindings.push(...nodeBindings); - - // do recursion if - // this tag has children and it has no special directives bound to it - if (childrenNodes.length && !hasItsOwnTemplate(sourceNode)) { - childrenNodes.forEach(node => build(node, sourceFile, sourceCode, { parent: sourceNode, ...currentState })); - } - - // close the tag if it's not a void one - if (isTagNode(sourceNode) && !isVoidNode(sourceNode)) { - currentState.html.push(closeTag(sourceNode)); - } - - return [ - currentState.html.join(''), - currentState.bindings - ] - } - - const templateFunctionArguments = [ - TEMPLATE_FN, - EXPRESSION_TYPES, - BINDING_TYPES, - GET_COMPONENT_FN - ].map(builders.identifier); - - /** - * Create the content of the template function - * @param { RiotParser.Node } sourceNode - node generated by the riot compiler - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns {AST.BlockStatement} the content of the template function - */ - function createTemplateFunctionContent(sourceNode, sourceFile, sourceCode) { - return builders.blockStatement([ - builders.returnStatement( - callTemplateFunction( - ...build( - createRootNode(sourceNode), - sourceFile, - sourceCode - ) - ) - ) - ]) - } - - /** - * Extend the AST adding the new template property containing our template call to render the component - * @param { Object } ast - current output ast - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @param { RiotParser.Node } sourceNode - node generated by the riot compiler - * @returns { Object } the output ast having the "template" key - */ - function extendTemplateProperty(ast, sourceFile, sourceCode, sourceNode) { - types$1.visit(ast, { - visitProperty(path) { - if (path.value.key.value === TAG_TEMPLATE_PROPERTY) { - path.value.value = builders.functionExpression( - null, - templateFunctionArguments, - createTemplateFunctionContent(sourceNode, sourceFile, sourceCode) - ); - - return false - } - - this.traverse(path); - } - }); - - return ast - } - - /** - * Generate the component template logic - * @param { RiotParser.Node } sourceNode - node generated by the riot compiler - * @param { string } source - original component source code - * @param { Object } meta - compilation meta information - * @param { AST } ast - current AST output - * @returns { AST } the AST generated - */ - function template(sourceNode, source, meta, ast) { - const { options } = meta; - return extendTemplateProperty(ast, options.file, source, sourceNode) - } - - const DEFAULT_OPTIONS = { - template: 'default', - file: '[unknown-source-file]', - scopedCss: true - }; - - /** - * Create the initial AST - * @param {string} tagName - the name of the component we have compiled - * @returns { AST } the initial AST - * - * @example - * // the output represents the following string in AST - */ - function createInitialInput({tagName}) { - /* - generates - export default { - ${TAG_CSS_PROPERTY}: null, - ${TAG_LOGIC_PROPERTY}: null, - ${TAG_TEMPLATE_PROPERTY}: null - } - */ - return builders.program([ - builders.exportDefaultDeclaration( - builders.objectExpression([ - simplePropertyNode(TAG_CSS_PROPERTY, nullNode()), - simplePropertyNode(TAG_LOGIC_PROPERTY, nullNode()), - simplePropertyNode(TAG_TEMPLATE_PROPERTY, nullNode()), - simplePropertyNode(TAG_NAME_PROPERTY, builders.literal(tagName)) - ]) - )] - ) - } - - /** - * Make sure the input sourcemap is valid otherwise we ignore it - * @param {SourceMapGenerator} map - preprocessor source map - * @returns {Object} sourcemap as json or nothing - */ - function normaliseInputSourceMap(map) { - const inputSourceMap = sourcemapAsJSON(map); - return isEmptySourcemap(inputSourceMap) ? null : inputSourceMap - } - - /** - * Override the sourcemap content making sure it will always contain the tag source code - * @param {Object} map - sourcemap as json - * @param {string} source - component source code - * @returns {Object} original source map with the "sourcesContent" property overriden - */ - function overrideSourcemapContent(map, source) { - return { - ...map, - sourcesContent: [source] - } - } - - /** - * Create the compilation meta object - * @param { string } source - source code of the tag we will need to compile - * @param { string } options - compiling options - * @returns {Object} meta object - */ - function createMeta(source, options) { - return { - tagName: null, - fragments: null, - options: { - ...DEFAULT_OPTIONS, - ...options - }, - source - } - } - - /** - * Generate the output code source together with the sourcemap - * @param { string } source - source code of the tag we will need to compile - * @param { string } opts - compiling options - * @returns { Output } object containing output code and source map - */ - function compile(source, opts = {}) { - const meta = createMeta(source, opts); - const {options} = meta; - const { code, map } = execute$1('template', options.template, meta, source); - const { template: template$1, css: css$1, javascript: javascript$1 } = parser$1(options).parse(code).output; - - // extend the meta object with the result of the parsing - Object.assign(meta, { - tagName: template$1.name, - fragments: { template: template$1, css: css$1, javascript: javascript$1 } - }); - - return compose( - result => ({ ...result, meta }), - result => execute(result, meta), - result => ({ - ...result, - map: overrideSourcemapContent(result.map, source) - }), - ast => meta.ast = ast && generateJavascript(ast, { - sourceMapName: `${options.file}.map`, - inputSourceMap: normaliseInputSourceMap(map) - }), - hookGenerator(template, template$1, code, meta), - hookGenerator(javascript, javascript$1, code, meta), - hookGenerator(css, css$1, code, meta), - )(createInitialInput(meta)) - } - - /** - * Prepare the riot parser node transformers - * @param { Function } transformer - transformer function - * @param { Object } sourceNode - riot parser node - * @param { string } source - component source code - * @param { Object } meta - compilation meta information - * @returns { Promise } object containing output code and source map - */ - function hookGenerator(transformer, sourceNode, source, meta) { - if ( - // filter missing nodes - !sourceNode || - // filter nodes without children - (sourceNode.nodes && !sourceNode.nodes.length) || - // filter empty javascript and css nodes - (!sourceNode.nodes && !sourceNode.text)) { - return result => result - } - - return curry(transformer)(sourceNode, source, meta) - } - - // This function can be used to register new preprocessors - // a preprocessor can target either only the css or javascript nodes - // or the complete tag source file ('template') - const registerPreprocessor = register$1; - - // This function can allow you to register postprocessors that will parse the output code - // here we can run prettifiers, eslint fixes... - const registerPostprocessor = register; - - exports.compile = compile; - exports.createInitialInput = createInitialInput; - exports.registerPostprocessor = registerPostprocessor; - exports.registerPreprocessor = registerPreprocessor; - - Object.defineProperty(exports, '__esModule', { value: true }); - -})); diff --git a/node_modules/@riotjs/compiler/dist/index.esm.js b/node_modules/@riotjs/compiler/dist/index.esm.js deleted file mode 100644 index fc58b59..0000000 --- a/node_modules/@riotjs/compiler/dist/index.esm.js +++ /dev/null @@ -1,2157 +0,0 @@ -/* Riot Compiler WIP, @license MIT */ -import { types as types$1, print, parse } from 'recast'; -import { composeSourceMaps } from 'recast/lib/util'; -import { SourceMapGenerator } from 'source-map'; -import compose from 'cumpa'; -import cssEscape from 'cssesc'; -import curry from 'curri'; -import { Parser } from 'acorn'; -import globalScope from 'globals'; -import riotParser, { nodeTypes } from '@riotjs/parser'; -import { hasValueAttribute } from 'dom-nodes'; - -const TAG_LOGIC_PROPERTY = 'exports'; -const TAG_CSS_PROPERTY = 'css'; -const TAG_TEMPLATE_PROPERTY = 'template'; -const TAG_NAME_PROPERTY = 'name'; - -const types = types$1; -const builders = types$1.builders; -const namedTypes = types$1.namedTypes; - -function nullNode() { - return builders.literal(null) -} - -function simplePropertyNode(key, value) { - return builders.property('init', builders.literal(key), value, false) -} - -/** - * Return a source map as JSON, it it has not the toJSON method it means it can - * be used right the way - * @param { SourceMapGenerator|Object } map - a sourcemap generator or simply an json object - * @returns { Object } the source map as JSON - */ -function sourcemapAsJSON(map) { - if (map && map.toJSON) return map.toJSON() - return map -} - -/** - * Detect node js environements - * @returns { boolean } true if the runtime is node - */ -function isNode() { - return typeof process !== 'undefined' -} - -/** - * Compose two sourcemaps - * @param { SourceMapGenerator } formerMap - original sourcemap - * @param { SourceMapGenerator } latterMap - target sourcemap - * @returns { Object } sourcemap json - */ -function composeSourcemaps(formerMap, latterMap) { - if ( - isNode() && - formerMap && latterMap && latterMap.mappings - ) { - return composeSourceMaps(sourcemapAsJSON(formerMap), sourcemapAsJSON(latterMap)) - } else if (isNode() && formerMap) { - return sourcemapAsJSON(formerMap) - } - - return {} -} - -/** - * Create a new sourcemap generator - * @param { Object } options - sourcemap options - * @returns { SourceMapGenerator } SourceMapGenerator instance - */ -function createSourcemap(options) { - return new SourceMapGenerator(options) -} - -const Output = Object.freeze({ - code: '', - ast: [], - meta: {}, - map: null -}); - -/** - * Create the right output data result of a parsing - * @param { Object } data - output data - * @param { string } data.code - code generated - * @param { AST } data.ast - ast representing the code - * @param { SourceMapGenerator } data.map - source map generated along with the code - * @param { Object } meta - compilation meta infomration - * @returns { Output } output container object - */ -function createOutput(data, meta) { - const output = { - ...Output, - ...data, - meta - }; - - if (!output.map && meta && meta.options && meta.options.file) - return { - ...output, - map: createSourcemap({ file: meta.options.file }) - } - - return output -} - -/** - * Transform the source code received via a compiler function - * @param { Function } compiler - function needed to generate the output code - * @param { Object } meta - compilation meta information - * @param { string } source - source code - * @returns { Output } output - the result of the compiler - */ -function transform(compiler, meta, source) { - const result = (compiler ? compiler(source, meta) : { code: source }); - return createOutput(result, meta) -} - -/** - * Throw an error with a descriptive message - * @param { string } message - error message - * @returns { undefined } hoppla.. at this point the program should stop working - */ -function panic(message) { - throw new Error(message) -} - -const postprocessors = new Set(); - -/** - * Register a postprocessor that will be used after the parsing and compilation of the riot tags - * @param { Function } postprocessor - transformer that will receive the output code ans sourcemap - * @returns { Set } the postprocessors collection - */ -function register(postprocessor) { - if (postprocessors.has(postprocessor)) { - panic(`This postprocessor "${postprocessor.name || postprocessor.toString()}" was already registered`); - } - - postprocessors.add(postprocessor); - - return postprocessors -} - -/** - * Exec all the postprocessors in sequence combining the sourcemaps generated - * @param { Output } compilerOutput - output generated by the compiler - * @param { Object } meta - compiling meta information - * @returns { Output } object containing output code and source map - */ -function execute(compilerOutput, meta) { - return Array.from(postprocessors).reduce(function(acc, postprocessor) { - const { code, map } = acc; - const output = postprocessor(code, meta); - - return { - code: output.code, - map: composeSourcemaps(map, output.map) - } - }, createOutput(compilerOutput, meta)) -} - -/** - * Parsers that can be registered by users to preparse components fragments - * @type { Object } - */ -const preprocessors = Object.freeze({ - javascript: new Map(), - css: new Map(), - template: new Map().set('default', code => ({ code })) -}); - -// throw a processor type error -function preprocessorTypeError(type) { - panic(`No preprocessor of type "${type}" was found, please make sure to use one of these: 'javascript', 'css' or 'template'`); -} - -// throw an error if the preprocessor was not registered -function preprocessorNameNotFoundError(name) { - panic(`No preprocessor named "${name}" was found, are you sure you have registered it?'`); -} - -/** - * Register a custom preprocessor - * @param { string } type - preprocessor type either 'js', 'css' or 'template' - * @param { string } name - unique preprocessor id - * @param { Function } preprocessor - preprocessor function - * @returns { Map } - the preprocessors map - */ -function register$1(type, name, preprocessor) { - if (!type) panic('Please define the type of preprocessor you want to register \'javascript\', \'css\' or \'template\''); - if (!name) panic('Please define a name for your preprocessor'); - if (!preprocessor) panic('Please provide a preprocessor function'); - if (!preprocessors[type]) preprocessorTypeError(type); - if (preprocessors[type].has(name)) panic(`The preprocessor ${name} was already registered before`); - - preprocessors[type].set(name, preprocessor); - - return preprocessors -} - -/** - * Exec the compilation of a preprocessor - * @param { string } type - preprocessor type either 'js', 'css' or 'template' - * @param { string } name - unique preprocessor id - * @param { Object } meta - preprocessor meta information - * @param { string } source - source code - * @returns { Output } object containing a sourcemap and a code string - */ -function execute$1(type, name, meta, source) { - if (!preprocessors[type]) preprocessorTypeError(type); - if (!preprocessors[type].has(name)) preprocessorNameNotFoundError(name); - - return transform(preprocessors[type].get(name), meta, source) -} - -const ATTRIBUTE_TYPE_NAME = 'type'; - -/** - * Get the type attribute from a node generated by the riot parser - * @param { Object} sourceNode - riot parser node - * @returns { string|null } a valid type to identify the preprocessor to use or nothing - */ -function getPreprocessorTypeByAttribute(sourceNode) { - const typeAttribute = sourceNode.attributes ? - sourceNode.attributes.find(attribute => attribute.name === ATTRIBUTE_TYPE_NAME) : - null; - - return typeAttribute ? normalize(typeAttribute.value) : null -} - - -/** - * Remove the noise in case a user has defined the preprocessor type='text/scss' - * @param { string } value - input string - * @returns { string } normalized string - */ -function normalize(value) { - return value.replace('text/', '') -} - -/** - * Preprocess a riot parser node - * @param { string } preprocessorType - either css, js - * @param { string } preprocessorName - preprocessor id - * @param { Object } meta - compilation meta information - * @param { RiotParser.nodeTypes } node - css node detected by the parser - * @returns { Output } code and sourcemap generated by the preprocessor - */ -function preprocess(preprocessorType, preprocessorName, meta, node) { - const code = node.text; - - return (preprocessorName ? - execute$1(preprocessorType, preprocessorName, meta, code) : - { code } - ) -} - -/** - * Matches valid, multiline JavaScript comments in almost all its forms. - * @const {RegExp} - * @static - */ -const R_MLCOMMS = /\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//g; - -/** - * Source for creating regexes matching valid quoted, single-line JavaScript strings. - * It recognizes escape characters, including nested quotes and line continuation. - * @const {string} - */ -const S_LINESTR = /"[^"\n\\]*(?:\\[\S\s][^"\n\\]*)*"|'[^'\n\\]*(?:\\[\S\s][^'\n\\]*)*'/.source; - -/** - * Matches CSS selectors, excluding those beginning with '@' and quoted strings. - * @const {RegExp} - */ - -const CSS_SELECTOR = RegExp(`([{}]|^)[; ]*((?:[^@ ;{}][^{}]*)?[^@ ;{}:] ?)(?={)|${S_LINESTR}`, 'g'); - -/** - * Parses styles enclosed in a "scoped" tag - * The "css" string is received without comments or surrounding spaces. - * - * @param {string} tag - Tag name of the root element - * @param {string} css - The CSS code - * @returns {string} CSS with the styles scoped to the root element - */ -function scopedCSS(tag, css) { - const host = ':host'; - const selectorsBlacklist = ['from', 'to']; - - return css.replace(CSS_SELECTOR, function(m, p1, p2) { - // skip quoted strings - if (!p2) return m - - // we have a selector list, parse each individually - p2 = p2.replace(/[^,]+/g, function(sel) { - const s = sel.trim(); - - // skip selectors already using the tag name - if (s.indexOf(tag) === 0) { - return sel - } - - // skips the keywords and percents of css animations - if (!s || selectorsBlacklist.indexOf(s) > -1 || s.slice(-1) === '%') { - return sel - } - - // replace the `:host` pseudo-selector, where it is, with the root tag name; - // if `:host` was not included, add the tag name as prefix, and mirror all - // `[data-is]` - if (s.indexOf(host) < 0) { - return `${tag} ${s},[is="${tag}"] ${s}` - } else { - return `${s.replace(host, tag)},${ - s.replace(host, `[is="${tag}"]`)}` - } - }); - - // add the danling bracket char and return the processed selector list - return p1 ? `${p1} ${p2}` : p2 - }) -} - -/** - * Remove comments, compact and trim whitespace - * @param { string } code - compiled css code - * @returns { string } css code normalized - */ -function compactCss(code) { - return code.replace(R_MLCOMMS, '').replace(/\s+/g, ' ').trim() -} - -const escapeBackslashes = s => s.replace(/\\/g, '\\\\'); -const escapeIdentifier = identifier => escapeBackslashes(cssEscape(identifier, { - isIdentifier: true -})); - -/** - * Generate the component css - * @param { Object } sourceNode - node generated by the riot compiler - * @param { string } source - original component source code - * @param { Object } meta - compilation meta information - * @param { AST } ast - current AST output - * @returns { AST } the AST generated - */ -function css(sourceNode, source, meta, ast) { - const preprocessorName = getPreprocessorTypeByAttribute(sourceNode); - const { options } = meta; - const preprocessorOutput = preprocess('css', preprocessorName, meta, sourceNode.text); - const normalizedCssCode = compactCss(preprocessorOutput.code); - const escapedCssIdentifier = escapeIdentifier(meta.tagName); - - const cssCode = (options.scopedCss ? - scopedCSS(escapedCssIdentifier, escapeBackslashes(normalizedCssCode)) : - escapeBackslashes(normalizedCssCode) - ).trim(); - - types.visit(ast, { - visitProperty(path) { - if (path.value.key.value === TAG_CSS_PROPERTY) { - path.value.value = builders.templateLiteral( - [builders.templateElement({ raw: cssCode, cooked: '' }, false)], - [] - ); - - return false - } - - this.traverse(path); - } - }); - - return ast -} - -/** - * Generate the javascript from an ast source - * @param {AST} ast - ast object - * @param {Object} options - printer options - * @returns {Object} code + map - */ -function generateJavascript(ast, options) { - return print(ast, { - ...options, - tabWidth: 2, - quote: 'single' - }) -} - -/** - * True if the sourcemap has no mappings, it is empty - * @param {Object} map - sourcemap json - * @returns {boolean} true if empty - */ -function isEmptySourcemap(map) { - return !map || !map.mappings || !map.mappings.length -} - -const LINES_RE = /\r\n?|\n/g; - -/** - * Split a string into a rows array generated from its EOL matches - * @param { string } string [description] - * @returns { Array } array containing all the string rows - */ -function splitStringByEOL(string) { - return string.split(LINES_RE) -} - -/** - * Get the line and the column of a source text based on its position in the string - * @param { string } string - target string - * @param { number } position - target position - * @returns { Object } object containing the source text line and column - */ -function getLineAndColumnByPosition(string, position) { - const lines = splitStringByEOL(string.slice(0, position)); - - return { - line: lines.length, - column: lines[lines.length - 1].length - } -} - -/** - * Add the offset to the code that must be parsed in order to generate properly the sourcemaps - * @param {string} input - input string - * @param {string} source - original source code - * @param {RiotParser.Node} node - node that we are going to transform - * @return {string} the input string with the offset properly set - */ -function addLineOffset(input, source, node) { - const {column, line} = getLineAndColumnByPosition(source, node.start); - return `${'\n'.repeat(line - 1)}${' '.repeat(column + 1)}${input}` -} - -/** - * Parse a js source to generate the AST - * @param {string} source - javascript source - * @param {Object} options - parser options - * @returns {AST} AST tree - */ -function generateAST(source, options) { - return parse(source, { - parser: { - parse(source, opts) { - return Parser.parse(source, { - ...opts, - ecmaVersion: 2020 - }) - } - }, - ...options - }) -} - -const browserAPIs = Object.keys(globalScope.browser); -const builtinAPIs = Object.keys(globalScope.builtin); - -const isIdentifier = namedTypes.Identifier.check.bind(namedTypes.Identifier); -const isLiteral = namedTypes.Literal.check.bind(namedTypes.Literal); -const isExpressionStatement = namedTypes.ExpressionStatement.check.bind(namedTypes.ExpressionStatement); -const isObjectExpression = namedTypes.ObjectExpression.check.bind(namedTypes.ObjectExpression); -const isThisExpression = namedTypes.ThisExpression.check.bind(namedTypes.ThisExpression); -const isNewExpression = namedTypes.NewExpression.check.bind(namedTypes.NewExpression); -const isSequenceExpression = namedTypes.SequenceExpression.check.bind(namedTypes.SequenceExpression); -const isBinaryExpression = namedTypes.BinaryExpression.check.bind(namedTypes.BinaryExpression); -const isExportDefaultStatement = namedTypes.ExportDefaultDeclaration.check.bind(namedTypes.ExportDefaultDeclaration); - -const isBrowserAPI = ({name}) => browserAPIs.includes(name); -const isBuiltinAPI = ({name}) => builtinAPIs.includes(name); -const isRaw = (node) => node && node.raw; // eslint-disable-line - -/** - * Find the export default statement - * @param { Array } body - tree structure containing the program code - * @returns { Object } node containing only the code of the export default statement - */ -function findExportDefaultStatement(body) { - return body.find(isExportDefaultStatement) -} - -/** - * Find all the code in an ast program except for the export default statements - * @param { Array } body - tree structure containing the program code - * @returns { Array } array containing all the program code except the export default expressions - */ -function filterNonExportDefaultStatements(body) { - return body.filter(node => !isExportDefaultStatement(node)) -} - -/** - * Get the body of the AST structure - * @param { Object } ast - ast object generated by recast - * @returns { Array } array containing the program code - */ -function getProgramBody(ast) { - return ast.body || ast.program.body -} - -/** - * Extend the AST adding the new tag method containing our tag sourcecode - * @param { Object } ast - current output ast - * @param { Object } exportDefaultNode - tag export default node - * @returns { Object } the output ast having the "tag" key extended with the content of the export default - */ -function extendTagProperty(ast, exportDefaultNode) { - types.visit(ast, { - visitProperty(path) { - if (path.value.key.value === TAG_LOGIC_PROPERTY) { - path.value.value = exportDefaultNode.declaration; - return false - } - - this.traverse(path); - } - }); - - return ast -} - -/** - * Generate the component javascript logic - * @param { Object } sourceNode - node generated by the riot compiler - * @param { string } source - original component source code - * @param { Object } meta - compilation meta information - * @param { AST } ast - current AST output - * @returns { AST } the AST generated - */ -function javascript(sourceNode, source, meta, ast) { - const preprocessorName = getPreprocessorTypeByAttribute(sourceNode); - const javascriptNode = addLineOffset(sourceNode.text.text, source, sourceNode); - const { options } = meta; - const preprocessorOutput = preprocess('javascript', preprocessorName, meta, { - ...sourceNode, - text: javascriptNode - }); - const inputSourceMap = sourcemapAsJSON(preprocessorOutput.map); - const generatedAst = generateAST(preprocessorOutput.code, { - sourceFileName: options.file, - inputSourceMap: isEmptySourcemap(inputSourceMap) ? null : inputSourceMap - }); - const generatedAstBody = getProgramBody(generatedAst); - const bodyWithoutExportDefault = filterNonExportDefaultStatements(generatedAstBody); - const exportDefaultNode = findExportDefaultStatement(generatedAstBody); - const outputBody = getProgramBody(ast); - - // add to the ast the "private" javascript content of our tag script node - outputBody.unshift(...bodyWithoutExportDefault); - - // convert the export default adding its content to the "tag" property exported - if (exportDefaultNode) extendTagProperty(ast, exportDefaultNode); - - return ast -} - -// import {IS_BOOLEAN,IS_CUSTOM,IS_RAW,IS_SPREAD,IS_VOID} from '@riotjs/parser/src/constants' - -const BINDING_TYPES = 'bindingTypes'; -const EACH_BINDING_TYPE = 'EACH'; -const IF_BINDING_TYPE = 'IF'; -const TAG_BINDING_TYPE = 'TAG'; -const SLOT_BINDING_TYPE = 'SLOT'; - - -const EXPRESSION_TYPES = 'expressionTypes'; -const ATTRIBUTE_EXPRESSION_TYPE = 'ATTRIBUTE'; -const VALUE_EXPRESSION_TYPE = 'VALUE'; -const TEXT_EXPRESSION_TYPE = 'TEXT'; -const EVENT_EXPRESSION_TYPE = 'EVENT'; - -const TEMPLATE_FN = 'template'; -const SCOPE = 'scope'; -const GET_COMPONENT_FN = 'getComponent'; - -// keys needed to create the DOM bindings -const BINDING_SELECTOR_KEY = 'selector'; -const BINDING_GET_COMPONENT_KEY = 'getComponent'; -const BINDING_TEMPLATE_KEY = 'template'; -const BINDING_TYPE_KEY = 'type'; -const BINDING_REDUNDANT_ATTRIBUTE_KEY = 'redundantAttribute'; -const BINDING_CONDITION_KEY = 'condition'; -const BINDING_ITEM_NAME_KEY = 'itemName'; -const BINDING_GET_KEY_KEY = 'getKey'; -const BINDING_INDEX_NAME_KEY = 'indexName'; -const BINDING_EVALUATE_KEY = 'evaluate'; -const BINDING_NAME_KEY = 'name'; -const BINDING_SLOTS_KEY = 'slots'; -const BINDING_EXPRESSIONS_KEY = 'expressions'; -const BINDING_CHILD_NODE_INDEX_KEY = 'childNodeIndex'; -// slots keys -const BINDING_BINDINGS_KEY = 'bindings'; -const BINDING_ID_KEY = 'id'; -const BINDING_HTML_KEY = 'html'; -const BINDING_ATTRIBUTES_KEY = 'attributes'; - -// DOM directives -const IF_DIRECTIVE = 'if'; -const EACH_DIRECTIVE = 'each'; -const KEY_ATTRIBUTE = 'key'; -const SLOT_ATTRIBUTE = 'slot'; -const NAME_ATTRIBUTE = 'name'; -const IS_DIRECTIVE = 'is'; - -// Misc -const DEFAULT_SLOT_NAME = 'default'; -const TEXT_NODE_EXPRESSION_PLACEHOLDER = ''; -const BINDING_SELECTOR_PREFIX = 'expr'; -const SLOT_TAG_NODE_NAME = 'slot'; -const PROGRESS_TAG_NODE_NAME = 'progress'; -const IS_VOID_NODE = 'isVoid'; -const IS_CUSTOM_NODE = 'isCustom'; -const IS_BOOLEAN_ATTRIBUTE = 'isBoolean'; -const IS_SPREAD_ATTRIBUTE = 'isSpread'; - -/** - * True if the node has not expression set nor bindings directives - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only if it's a static node that doesn't need bindings or expressions - */ -function isStaticNode(node) { - return [ - hasExpressions, - findEachAttribute, - findIfAttribute, - isCustomNode, - isSlotNode - ].every(test => !test(node)) -} - -/** - * Check if a node name is part of the browser or builtin javascript api or it belongs to the current scope - * @param { types.NodePath } path - containing the current node visited - * @returns {boolean} true if it's a global api variable - */ -function isGlobal({ scope, node }) { - return Boolean( - isRaw(node) || - isBuiltinAPI(node) || - isBrowserAPI(node) || - isNewExpression(node) || - isNodeInScope(scope, node), - ) -} - -/** - * Checks if the identifier of a given node exists in a scope - * @param {Scope} scope - scope where to search for the identifier - * @param {types.Node} node - node to search for the identifier - * @returns {boolean} true if the node identifier is defined in the given scope - */ -function isNodeInScope(scope, node) { - const traverse = (isInScope = false) => { - types.visit(node, { - visitIdentifier(path) { - if (scope.lookup(getName(path.node))) { - isInScope = true; - } - - this.abort(); - } - }); - - return isInScope - }; - - return traverse() -} - -/** - * True if the node has the isCustom attribute set - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if either it's a riot component or a custom element - */ -function isCustomNode(node) { - return !!(node[IS_CUSTOM_NODE] || hasIsAttribute(node)) -} - -/** - * True the node is - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if it's a slot node - */ -function isSlotNode(node) { - return node.name === SLOT_TAG_NODE_NAME -} - -/** - * True if the node has the isVoid attribute set - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if the node is self closing - */ -function isVoidNode(node) { - return !!node[IS_VOID_NODE] -} - -/** - * True if the riot parser did find a tag node - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the tag nodes - */ -function isTagNode(node) { - return node.type === nodeTypes.TAG -} - -/** - * True if the riot parser did find a text node - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the text nodes - */ -function isTextNode(node) { - return node.type === nodeTypes.TEXT -} - -/** - * True if the node parsed is the root one - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the root nodes - */ -function isRootNode(node) { - return node.isRoot -} - -/** - * True if the attribute parsed is of type spread one - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if the attribute node is of type spread - */ -function isSpreadAttribute(node) { - return node[IS_SPREAD_ATTRIBUTE] -} - -/** - * True if the node is an attribute and its name is "value" - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for value attribute nodes - */ -function isValueAttribute(node) { - return node.name === 'value' -} - -/** - * True if the DOM node is a progress tag - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true for the progress tags - */ -function isProgressNode(node) { - return node.name === PROGRESS_TAG_NODE_NAME -} - -/** - * True if the node is an attribute and a DOM handler - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for dom listener attribute nodes - */ -const isEventAttribute = (() => { - const EVENT_ATTR_RE = /^on/; - return node => EVENT_ATTR_RE.test(node.name) -})(); - -/** - * True if the node has expressions or expression attributes - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} ditto - */ -function hasExpressions(node) { - return !!( - node.expressions || - // has expression attributes - (getNodeAttributes(node).some(attribute => hasExpressions(attribute))) || - // has child text nodes with expressions - (node.nodes && node.nodes.some(node => isTextNode(node) && hasExpressions(node))) - ) -} - -/** - * True if the node is a directive having its own template - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the IF EACH and TAG bindings - */ -function hasItsOwnTemplate(node) { - return [ - findEachAttribute, - findIfAttribute, - isCustomNode - ].some(test => test(node)) -} - -const hasIfAttribute = compose(Boolean, findIfAttribute); -const hasEachAttribute = compose(Boolean, findEachAttribute); -const hasIsAttribute = compose(Boolean, findIsAttribute); -const hasKeyAttribute = compose(Boolean, findKeyAttribute); - -/** - * Find the attribute node - * @param { string } name - name of the attribute we want to find - * @param { riotParser.nodeTypes.TAG } node - a tag node - * @returns { riotParser.nodeTypes.ATTR } attribute node - */ -function findAttribute(name, node) { - return node.attributes && node.attributes.find(attr => getName(attr) === name) -} - -function findIfAttribute(node) { - return findAttribute(IF_DIRECTIVE, node) -} - -function findEachAttribute(node) { - return findAttribute(EACH_DIRECTIVE, node) -} - -function findKeyAttribute(node) { - return findAttribute(KEY_ATTRIBUTE, node) -} - -function findIsAttribute(node) { - return findAttribute(IS_DIRECTIVE, node) -} - -/** - * Find all the node attributes that are not expressions - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} list of all the static attributes - */ -function findStaticAttributes(node) { - return getNodeAttributes(node).filter(attribute => !hasExpressions(attribute)) -} - -/** - * Find all the node attributes that have expressions - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} list of all the dynamic attributes - */ -function findDynamicAttributes(node) { - return getNodeAttributes(node).filter(hasExpressions) -} - -/** - * Unescape the user escaped chars - * @param {string} string - input string - * @param {string} char - probably a '{' or anything the user want's to escape - * @returns {string} cleaned up string - */ -function unescapeChar(string, char) { - return string.replace(RegExp(`\\\\${char}`, 'gm'), char) -} - -const scope = builders.identifier(SCOPE); -const getName = node => node && node.name ? node.name : node; - -/** - * Replace the path scope with a member Expression - * @param { types.NodePath } path - containing the current node visited - * @param { types.Node } property - node we want to prefix with the scope identifier - * @returns {undefined} this is a void function - */ -function replacePathScope(path, property) { - path.replace(builders.memberExpression( - scope, - property, - false - )); -} - -/** - * Change the nodes scope adding the `scope` prefix - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean } return false if we want to stop the tree traversal - * @context { types.visit } - */ -function updateNodeScope(path) { - if (!isGlobal(path)) { - replacePathScope(path, path.node); - - return false - } - - this.traverse(path); -} - -/** - * Change the scope of the member expressions - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean } return always false because we want to check only the first node object - */ -function visitMemberExpression(path) { - if (!isGlobal(path) && !isGlobal({ node: path.node.object, scope: path.scope })) { - if (path.value.computed) { - this.traverse(path); - } else if (isBinaryExpression(path.node.object) || path.node.object.computed) { - this.traverse(path.get('object')); - } else if (!path.node.object.callee) { - replacePathScope(path, isThisExpression(path.node.object) ? path.node.property : path.node); - } else { - this.traverse(path.get('object')); - } - } - - return false -} - - -/** - * Objects properties should be handled a bit differently from the Identifier - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean } return false if we want to stop the tree traversal - */ -function visitProperty(path) { - const value = path.node.value; - - if (isIdentifier(value)) { - updateNodeScope(path.get('value')); - } else { - this.traverse(path.get('value')); - } - - return false -} - -/** - * The this expressions should be replaced with the scope - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean|undefined } return false if we want to stop the tree traversal - */ -function visitThisExpression(path) { - path.replace(scope); - this.traverse(path); -} - - -/** - * Update the scope of the global nodes - * @param { Object } ast - ast program - * @returns { Object } the ast program with all the global nodes updated - */ -function updateNodesScope(ast) { - const ignorePath = () => false; - - types.visit(ast, { - visitIdentifier: updateNodeScope, - visitMemberExpression, - visitProperty, - visitThisExpression, - visitClassExpression: ignorePath - }); - - return ast -} - -/** - * Convert any expression to an AST tree - * @param { Object } expression - expression parsed by the riot parser - * @param { string } sourceFile - original tag file - * @param { string } sourceCode - original tag source code - * @returns { Object } the ast generated - */ -function createASTFromExpression(expression, sourceFile, sourceCode) { - const code = sourceFile ? - addLineOffset(expression.text, sourceCode, expression) : - expression.text; - - return generateAST(`(${code})`, { - sourceFileName: sourceFile - }) -} - -/** - * Create the bindings template property - * @param {Array} args - arguments to pass to the template function - * @returns {ASTNode} a binding template key - */ -function createTemplateProperty(args) { - return simplePropertyNode( - BINDING_TEMPLATE_KEY, - args ? callTemplateFunction(...args) : nullNode() - ) -} - -/** - * Try to get the expression of an attribute node - * @param { RiotParser.Node.Attribute } attribute - riot parser attribute node - * @returns { RiotParser.Node.Expression } attribute expression value - */ -function getAttributeExpression(attribute) { - return attribute.expressions ? attribute.expressions[0] : { - // if no expression was found try to typecast the attribute value - ...attribute, - text: attribute.value - } -} - -/** - * Wrap the ast generated in a function call providing the scope argument - * @param {Object} ast - function body - * @returns {FunctionExpresion} function having the scope argument injected - */ -function wrapASTInFunctionWithScope(ast) { - return builders.functionExpression( - null, - [scope], - builders.blockStatement([builders.returnStatement( - ast - )]) - ) -} - -/** - * Convert any parser option to a valid template one - * @param { RiotParser.Node.Expression } expression - expression parsed by the riot parser - * @param { string } sourceFile - original tag file - * @param { string } sourceCode - original tag source code - * @returns { Object } a FunctionExpression object - * - * @example - * toScopedFunction('foo + bar') // scope.foo + scope.bar - * - * @example - * toScopedFunction('foo.baz + bar') // scope.foo.baz + scope.bar - */ -function toScopedFunction(expression, sourceFile, sourceCode) { - return compose( - wrapASTInFunctionWithScope, - transformExpression, - )(expression, sourceFile, sourceCode) -} - -/** - * Transform an expression node updating its global scope - * @param {RiotParser.Node.Expr} expression - riot parser expression node - * @param {string} sourceFile - source file - * @param {string} sourceCode - source code - * @returns {ASTExpression} ast expression generated from the riot parser expression node - */ -function transformExpression(expression, sourceFile, sourceCode) { - return compose( - getExpressionAST, - updateNodesScope, - createASTFromExpression - )(expression, sourceFile, sourceCode) -} - -/** - * Get the parsed AST expression of riot expression node - * @param {AST.Program} sourceAST - raw node parsed - * @returns {AST.Expression} program expression output - */ -function getExpressionAST(sourceAST) { - const astBody = sourceAST.program.body; - - return astBody[0] ? astBody[0].expression : astBody -} - -/** - * Create the template call function - * @param {Array|string|Node.Literal} template - template string - * @param {Array} bindings - template bindings provided as AST nodes - * @returns {Node.CallExpression} template call expression - */ -function callTemplateFunction(template, bindings) { - return builders.callExpression(builders.identifier(TEMPLATE_FN), [ - template ? builders.literal(template) : nullNode(), - bindings ? builders.arrayExpression(bindings) : nullNode() - ]) -} - -/** - * Convert any DOM attribute into a valid DOM selector useful for the querySelector API - * @param { string } attributeName - name of the attribute to query - * @returns { string } the attribute transformed to a query selector - */ -const attributeNameToDOMQuerySelector = attributeName => `[${attributeName}]`; - -/** - * Create the properties to query a DOM node - * @param { string } attributeName - attribute name needed to identify a DOM node - * @returns { Array } array containing the selector properties needed for the binding - */ -function createSelectorProperties(attributeName) { - return attributeName ? [ - simplePropertyNode(BINDING_REDUNDANT_ATTRIBUTE_KEY, builders.literal(attributeName)), - simplePropertyNode(BINDING_SELECTOR_KEY, - compose(builders.literal, attributeNameToDOMQuerySelector)(attributeName) - ) - ] : [] -} - -/** - * Clone the node filtering out the selector attribute from the attributes list - * @param {RiotParser.Node} node - riot parser node - * @param {string} selectorAttribute - name of the selector attribute to filter out - * @returns {RiotParser.Node} the node with the attribute cleaned up - */ -function cloneNodeWithoutSelectorAttribute(node, selectorAttribute) { - return { - ...node, - attributes: getAttributesWithoutSelector(getNodeAttributes(node), selectorAttribute) - } -} - - -/** - * Get the node attributes without the selector one - * @param {Array} attributes - attributes list - * @param {string} selectorAttribute - name of the selector attribute to filter out - * @returns {Array} filtered attributes - */ -function getAttributesWithoutSelector(attributes, selectorAttribute) { - if (selectorAttribute) - return attributes.filter(attribute => attribute.name !== selectorAttribute) - - return attributes -} - -/** - * Clean binding or custom attributes - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} only the attributes that are not bindings or directives - */ -function cleanAttributes(node) { - return getNodeAttributes(node).filter(attribute => ![ - IF_DIRECTIVE, - EACH_DIRECTIVE, - KEY_ATTRIBUTE, - SLOT_ATTRIBUTE, - IS_DIRECTIVE - ].includes(attribute.name)) -} - -/** - * Create a root node proxing only its nodes and attributes - * @param {RiotParser.Node} node - riot parser node - * @returns {RiotParser.Node} root node - */ -function createRootNode(node) { - return { - nodes: getChildrenNodes(node), - isRoot: true, - // root nodes shuold't have directives - attributes: cleanAttributes(node) - } -} - -/** - * Get all the child nodes of a RiotParser.Node - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} all the child nodes found - */ -function getChildrenNodes(node) { - return node && node.nodes ? node.nodes : [] -} - -/** - * Get all the attributes of a riot parser node - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} all the attributes find - */ -function getNodeAttributes(node) { - return node.attributes ? node.attributes : [] -} -/** - * Get the name of a custom node transforming it into an expression node - * @param {RiotParser.Node} node - riot parser node - * @returns {RiotParser.Node.Attr} the node name as expression attribute - */ -function getCustomNodeNameAsExpression(node) { - const isAttribute = findIsAttribute(node); - const toRawString = val => `'${val}'`; - - if (isAttribute) { - return isAttribute.expressions ? isAttribute.expressions[0] : { - ...isAttribute, - text: toRawString(isAttribute.value) - } - } - - return { ...node, text: toRawString(getName(node)) } -} - -/** - * Convert all the node static attributes to strings - * @param {RiotParser.Node} node - riot parser node - * @returns {string} all the node static concatenated as string - */ -function staticAttributesToString(node) { - return findStaticAttributes(node) - .map(attribute => attribute[IS_BOOLEAN_ATTRIBUTE] || !attribute.value ? - attribute.name : - `${attribute.name}="${unescapeNode(attribute, 'value').value}"` - ).join(' ') -} - -/** - * Make sure that node escaped chars will be unescaped - * @param {RiotParser.Node} node - riot parser node - * @param {string} key - key property to unescape - * @returns {RiotParser.Node} node with the text property unescaped - */ -function unescapeNode(node, key) { - if (node.unescape) { - return { - ...node, - [key]: unescapeChar(node[key], node.unescape) - } - } - - return node -} - - -/** - * Convert a riot parser opening node into a string - * @param {RiotParser.Node} node - riot parser node - * @returns {string} the node as string - */ -function nodeToString(node) { - const attributes = staticAttributesToString(node); - - switch(true) { - case isTagNode(node): - return `<${node.name}${attributes ? ` ${attributes}` : ''}${isVoidNode(node) ? '/' : ''}>` - case isTextNode(node): - return hasExpressions(node) ? TEXT_NODE_EXPRESSION_PLACEHOLDER : unescapeNode(node, 'text').text - default: - return '' - } -} - -/** - * Close an html node - * @param {RiotParser.Node} node - riot parser node - * @returns {string} the closing tag of the html tag node passed to this function - */ -function closeTag(node) { - return node.name ? `` : '' -} - -/** - * Create a strings array with the `join` call to transform it into a string - * @param {Array} stringsArray - array containing all the strings to concatenate - * @returns {AST.CallExpression} array with a `join` call - */ -function createArrayString(stringsArray) { - return builders.callExpression( - builders.memberExpression( - builders.arrayExpression(stringsArray), - builders.identifier('join'), - false - ), - [builders.literal('')], - ) -} - -/** - * Simple expression bindings might contain multiple expressions like for example: "class="{foo} red {bar}"" - * This helper aims to merge them in a template literal if it's necessary - * @param {RiotParser.Attr} node - riot parser node - * @param {string} sourceFile - original tag file - * @param {string} sourceCode - original tag source code - * @returns { Object } a template literal expression object - */ -function mergeAttributeExpressions(node, sourceFile, sourceCode) { - if (!node.parts || node.parts.length === 1) { - return transformExpression(node.expressions[0], sourceFile, sourceCode) - } - const stringsArray = [ - ...node.parts.reduce((acc, str) => { - const expression = node.expressions.find(e => e.text.trim() === str); - - return [ - ...acc, - expression ? transformExpression(expression, sourceFile, sourceCode) : builders.literal(str) - ] - }, []) - ].filter(expr => !isLiteral(expr) || expr.value); - - - return createArrayString(stringsArray) -} - -/** - * Create a selector that will be used to find the node via dom-bindings - * @param {number} id - temporary variable that will be increased anytime this function will be called - * @returns {string} selector attribute needed to bind a riot expression - */ -const createBindingSelector = (function createSelector(id = 0) { - return () => `${BINDING_SELECTOR_PREFIX}${id++}` -}()); - -/** - * Create an attribute evaluation function - * @param {RiotParser.Attr} sourceNode - riot parser node - * @param {string} sourceFile - original tag file - * @param {string} sourceCode - original tag source code - * @returns { AST.Node } an AST function expression to evaluate the attribute value - */ -function createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) { - return hasExpressions(sourceNode) ? - // dynamic attribute - wrapASTInFunctionWithScope(mergeAttributeExpressions(sourceNode, sourceFile, sourceCode)) : - // static attribute - builders.functionExpression( - null, - [], - builders.blockStatement([ - builders.returnStatement(builders.literal(sourceNode.value || true)) - ]), - ) -} - -/** - * Simple clone deep function, do not use it for classes or recursive objects! - * @param {*} source - possibily an object to clone - * @returns {*} the object we wanted to clone - */ -function cloneDeep(source) { - return JSON.parse(JSON.stringify(source)) -} - -const getEachItemName = expression => isSequenceExpression(expression.left) ? expression.left.expressions[0] : expression.left; -const getEachIndexName = expression => isSequenceExpression(expression.left) ? expression.left.expressions[1] : null; -const getEachValue = expression => expression.right; -const nameToliteral = compose(builders.literal, getName); - -const generateEachItemNameKey = expression => simplePropertyNode( - BINDING_ITEM_NAME_KEY, - compose(nameToliteral, getEachItemName)(expression) -); - -const generateEachIndexNameKey = expression => simplePropertyNode( - BINDING_INDEX_NAME_KEY, - compose(nameToliteral, getEachIndexName)(expression) -); - -const generateEachEvaluateKey = (expression, eachExpression, sourceFile, sourceCode) => simplePropertyNode( - BINDING_EVALUATE_KEY, - compose( - e => toScopedFunction(e, sourceFile, sourceCode), - e => ({ - ...eachExpression, - text: generateJavascript(e).code - }), - getEachValue - )(expression) -); - -/** - * Get the each expression properties to create properly the template binding - * @param { DomBinding.Expression } eachExpression - original each expression data - * @param { string } sourceFile - original tag file - * @param { string } sourceCode - original tag source code - * @returns { Array } AST nodes that are needed to build an each binding - */ -function generateEachExpressionProperties(eachExpression, sourceFile, sourceCode) { - const ast = createASTFromExpression(eachExpression, sourceFile, sourceCode); - const body = ast.program.body; - const firstNode = body[0]; - - if (!isExpressionStatement(firstNode)) { - panic(`The each directives supported should be of type "ExpressionStatement",you have provided a "${firstNode.type}"`); - } - - const { expression } = firstNode; - - return [ - generateEachItemNameKey(expression), - generateEachIndexNameKey(expression), - generateEachEvaluateKey(expression, eachExpression, sourceFile, sourceCode) - ] -} - -/** - * Transform a RiotParser.Node.Tag into an each binding - * @param { RiotParser.Node.Tag } sourceNode - tag containing the each attribute - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } an each binding node - */ -function createEachBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - const [ifAttribute, eachAttribute, keyAttribute] = [ - findIfAttribute, - findEachAttribute, - findKeyAttribute - ].map(f => f(sourceNode)); - const attributeOrNull = attribute => attribute ? toScopedFunction(getAttributeExpression(attribute), sourceFile, sourceCode) : nullNode(); - - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(EACH_BINDING_TYPE), - false - ), - ), - simplePropertyNode(BINDING_GET_KEY_KEY, attributeOrNull(keyAttribute)), - simplePropertyNode(BINDING_CONDITION_KEY, attributeOrNull(ifAttribute)), - createTemplateProperty(createNestedBindings(sourceNode, sourceFile, sourceCode, selectorAttribute)), - ...createSelectorProperties(selectorAttribute), - ...compose(generateEachExpressionProperties, getAttributeExpression)(eachAttribute) - ]) -} - -/** - * Transform a RiotParser.Node.Tag into an if binding - * @param { RiotParser.Node.Tag } sourceNode - tag containing the if attribute - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { stiring } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } an if binding node - */ -function createIfBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - const ifAttribute = findIfAttribute(sourceNode); - - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(IF_BINDING_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_EVALUATE_KEY, - toScopedFunction(ifAttribute.expressions[0], sourceFile, sourceCode) - ), - ...createSelectorProperties(selectorAttribute), - createTemplateProperty(createNestedBindings(sourceNode, sourceFile, sourceCode, selectorAttribute)) - ]) -} - -/** - * Create a simple attribute expression - * @param {RiotParser.Node.Attr} sourceNode - the custom tag - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {AST.Node} object containing the expression binding keys - */ -function createAttributeExpression(sourceNode, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(ATTRIBUTE_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode(BINDING_NAME_KEY, isSpreadAttribute(sourceNode) ? nullNode() : builders.literal(sourceNode.name)), - simplePropertyNode( - BINDING_EVALUATE_KEY, - createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) - ) - ]) -} - -/** - * Create a simple event expression - * @param {RiotParser.Node.Attr} sourceNode - attribute containing the event handlers - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {AST.Node} object containing the expression binding keys - */ -function createEventExpression(sourceNode, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(EVENT_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode(BINDING_NAME_KEY, builders.literal(sourceNode.name)), - simplePropertyNode( - BINDING_EVALUATE_KEY, - createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) - ) - ]) -} - -/** - * Generate the pure immutable string chunks from a RiotParser.Node.Text - * @param {RiotParser.Node.Text} node - riot parser text node - * @param {string} sourceCode sourceCode - source code - * @returns {Array} array containing the immutable string chunks - */ -function generateLiteralStringChunksFromNode(node, sourceCode) { - return node.expressions.reduce((chunks, expression, index) => { - const start = index ? node.expressions[index - 1].end : node.start; - - chunks.push(sourceCode.substring(start, expression.start)); - - // add the tail to the string - if (index === node.expressions.length - 1) - chunks.push(sourceCode.substring(expression.end, node.end)); - - return chunks - }, []).map(str => node.unescape ? unescapeChar(str, node.unescape) : str) -} - -/** - * Simple bindings might contain multiple expressions like for example: "{foo} and {bar}" - * This helper aims to merge them in a template literal if it's necessary - * @param {RiotParser.Node} node - riot parser node - * @param {string} sourceFile - original tag file - * @param {string} sourceCode - original tag source code - * @returns { Object } a template literal expression object - */ -function mergeNodeExpressions(node, sourceFile, sourceCode) { - if (node.parts.length === 1) - return transformExpression(node.expressions[0], sourceFile, sourceCode) - - const pureStringChunks = generateLiteralStringChunksFromNode(node, sourceCode); - const stringsArray = pureStringChunks.reduce((acc, str, index) => { - const expr = node.expressions[index]; - - return [ - ...acc, - builders.literal(str), - expr ? transformExpression(expr, sourceFile, sourceCode) : nullNode() - ] - }, []) - // filter the empty literal expressions - .filter(expr => !isLiteral(expr) || expr.value); - - return createArrayString(stringsArray) -} - -/** - * Create a text expression - * @param {RiotParser.Node.Text} sourceNode - text node to parse - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {number} childNodeIndex - position of the child text node in its parent children nodes - * @returns {AST.Node} object containing the expression binding keys - */ -function createTextExpression(sourceNode, sourceFile, sourceCode, childNodeIndex) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(TEXT_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_CHILD_NODE_INDEX_KEY, - builders.literal(childNodeIndex) - ), - simplePropertyNode( - BINDING_EVALUATE_KEY, - wrapASTInFunctionWithScope( - mergeNodeExpressions(sourceNode, sourceFile, sourceCode) - ) - ) - ]) -} - -function createValueExpression(sourceNode, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(VALUE_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_EVALUATE_KEY, - createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) - ) - ]) -} - -function createExpression(sourceNode, sourceFile, sourceCode, childNodeIndex, parentNode) { - switch (true) { - case isTextNode(sourceNode): - return createTextExpression(sourceNode, sourceFile, sourceCode, childNodeIndex) - // progress nodes value attributes will be rendered as attributes - // see https://github.com/riot/compiler/issues/122 - case isValueAttribute(sourceNode) && hasValueAttribute(parentNode.name) && !isProgressNode(parentNode): - return createValueExpression(sourceNode, sourceFile, sourceCode) - case isEventAttribute(sourceNode): - return createEventExpression(sourceNode, sourceFile, sourceCode) - default: - return createAttributeExpression(sourceNode, sourceFile, sourceCode) - } -} - -/** - * Create the attribute expressions - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {Array} array containing all the attribute expressions - */ -function createAttributeExpressions(sourceNode, sourceFile, sourceCode) { - return findDynamicAttributes(sourceNode) - .map(attribute => createExpression(attribute, sourceFile, sourceCode, 0, sourceNode)) -} - -/** - * Create the text node expressions - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {Array} array containing all the text node expressions - */ -function createTextNodeExpressions(sourceNode, sourceFile, sourceCode) { - const childrenNodes = getChildrenNodes(sourceNode); - - return childrenNodes - .filter(isTextNode) - .filter(hasExpressions) - .map(node => createExpression( - node, - sourceFile, - sourceCode, - childrenNodes.indexOf(node), - sourceNode - )) -} - -/** - * Add a simple binding to a riot parser node - * @param { RiotParser.Node.Tag } sourceNode - tag containing the if attribute - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } an each binding node - */ -function createSimpleBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - return builders.objectExpression([ - ...createSelectorProperties(selectorAttribute), - simplePropertyNode( - BINDING_EXPRESSIONS_KEY, - builders.arrayExpression([ - ...createTextNodeExpressions(sourceNode, sourceFile, sourceCode), - ...createAttributeExpressions(sourceNode, sourceFile, sourceCode) - ]) - ) - ]) -} - -/** - * Transform a RiotParser.Node.Tag of type slot into a slot binding - * @param { RiotParser.Node.Tag } sourceNode - slot node - * @param { string } selectorAttribute - attribute needed to select the target node - * @returns { AST.Node } a slot binding node - */ -function createSlotBinding(sourceNode, selectorAttribute) { - const slotNameAttribute = findAttribute(NAME_ATTRIBUTE, sourceNode); - const slotName = slotNameAttribute ? slotNameAttribute.value : DEFAULT_SLOT_NAME; - - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(SLOT_BINDING_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_NAME_KEY, - builders.literal(slotName) - ), - ...createSelectorProperties(selectorAttribute) - ]) -} - -/** - * Find the slots in the current component and group them under the same id - * @param {RiotParser.Node.Tag} sourceNode - the custom tag - * @returns {Object} object containing all the slots grouped by name - */ -function groupSlots(sourceNode) { - return getChildrenNodes(sourceNode).reduce((acc, node) => { - const slotAttribute = findSlotAttribute(node); - - if (slotAttribute) { - acc[slotAttribute.value] = node; - } else { - acc.default = createRootNode({ - nodes: [...getChildrenNodes(acc.default), node] - }); - } - - return acc - }, { - default: null - }) -} - -/** - * Create the slot entity to pass to the riot-dom bindings - * @param {string} id - slot id - * @param {RiotParser.Node.Tag} sourceNode - slot root node - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {AST.Node} ast node containing the slot object properties - */ -function buildSlot(id, sourceNode, sourceFile, sourceCode) { - const cloneNode = { - ...sourceNode, - // avoid to render the slot attribute - attributes: getNodeAttributes(sourceNode).filter(attribute => attribute.name !== SLOT_ATTRIBUTE) - }; - const [html, bindings] = build(cloneNode, sourceFile, sourceCode); - - return builders.objectExpression([ - simplePropertyNode(BINDING_ID_KEY, builders.literal(id)), - simplePropertyNode(BINDING_HTML_KEY, builders.literal(html)), - simplePropertyNode(BINDING_BINDINGS_KEY, builders.arrayExpression(bindings)) - ]) -} - -/** - * Create the AST array containing the slots - * @param { RiotParser.Node.Tag } sourceNode - the custom tag - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns {AST.ArrayExpression} array containing the attributes to bind - */ -function createSlotsArray(sourceNode, sourceFile, sourceCode) { - return builders.arrayExpression([ - ...compose( - slots => slots.map(([key, value]) => buildSlot(key, value, sourceFile, sourceCode)), - slots => slots.filter(([,value]) => value), - Object.entries, - groupSlots - )(sourceNode) - ]) -} - -/** - * Create the AST array containing the attributes to bind to this node - * @param { RiotParser.Node.Tag } sourceNode - the custom tag - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns {AST.ArrayExpression} array containing the slot objects - */ -function createBindingAttributes(sourceNode, selectorAttribute, sourceFile, sourceCode) { - return builders.arrayExpression([ - ...compose( - attributes => attributes.map(attribute => createExpression(attribute, sourceFile, sourceCode, 0, sourceNode)), - attributes => getAttributesWithoutSelector(attributes, selectorAttribute), // eslint-disable-line - cleanAttributes - )(sourceNode) - ]) -} - -/** - * Find the slot attribute if it exists - * @param {RiotParser.Node.Tag} sourceNode - the custom tag - * @returns {RiotParser.Node.Attr|undefined} the slot attribute found - */ -function findSlotAttribute(sourceNode) { - return getNodeAttributes(sourceNode).find(attribute => attribute.name === SLOT_ATTRIBUTE) -} - -/** - * Transform a RiotParser.Node.Tag into a tag binding - * @param { RiotParser.Node.Tag } sourceNode - the custom tag - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } tag binding node - */ -function createTagBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(TAG_BINDING_TYPE), - false - ), - ), - simplePropertyNode(BINDING_GET_COMPONENT_KEY, builders.identifier(GET_COMPONENT_FN)), - simplePropertyNode( - BINDING_EVALUATE_KEY, - toScopedFunction(getCustomNodeNameAsExpression(sourceNode), sourceFile, sourceCode) - ), - simplePropertyNode(BINDING_SLOTS_KEY, createSlotsArray(sourceNode, sourceFile, sourceCode)), - simplePropertyNode( - BINDING_ATTRIBUTES_KEY, - createBindingAttributes(sourceNode, selectorAttribute, sourceFile, sourceCode) - ), - ...createSelectorProperties(selectorAttribute) - ]) -} - -const BuildingState = Object.freeze({ - html: [], - bindings: [], - parent: null -}); - -/** - * Nodes having bindings should be cloned and new selector properties should be added to them - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} bindingsSelector - temporary string to identify the current node - * @returns {RiotParser.Node} the original node parsed having the new binding selector attribute - */ -function createBindingsTag(sourceNode, bindingsSelector) { - if (!bindingsSelector) return sourceNode - - return { - ...sourceNode, - // inject the selector bindings into the node attributes - attributes: [{ - name: bindingsSelector, - value: bindingsSelector - }, ...getNodeAttributes(sourceNode)] - } -} - -/** - * Create a generic dynamic node (text or tag) and generate its bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and bindings for the current node - */ -function createDynamicNode(sourceNode, sourceFile, sourceCode, state) { - switch (true) { - case isTextNode(sourceNode): - // text nodes will not have any bindings - return [nodeToString(sourceNode), []] - default: - return createTagWithBindings(sourceNode, sourceFile, sourceCode) - } -} - -/** - * Create only a dynamic tag node with generating a custom selector and its bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and bindings for the current node - */ -function createTagWithBindings(sourceNode, sourceFile, sourceCode) { - const bindingsSelector = isRootNode(sourceNode) ? null : createBindingSelector(); - const cloneNode = createBindingsTag(sourceNode, bindingsSelector); - const tagOpeningHTML = nodeToString(cloneNode); - - switch(true) { - // EACH bindings have prio 1 - case hasEachAttribute(cloneNode): - return [tagOpeningHTML, [createEachBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - // IF bindings have prio 2 - case hasIfAttribute(cloneNode): - return [tagOpeningHTML, [createIfBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - // TAG bindings have prio 3 - case isCustomNode(cloneNode): - return [tagOpeningHTML, [createTagBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - // slot tag - case isSlotNode(cloneNode): - return [tagOpeningHTML, [createSlotBinding(cloneNode, bindingsSelector)]] - // this node has expressions bound to it - default: - return [tagOpeningHTML, [createSimpleBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - } -} - -/** - * Parse a node trying to extract its template and bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and bindings for the current node - */ -function parseNode(sourceNode, sourceFile, sourceCode, state) { - // static nodes have no bindings - if (isStaticNode(sourceNode)) return [nodeToString(sourceNode), []] - return createDynamicNode(sourceNode, sourceFile, sourceCode) -} - -/** - * Create the tag binding - * @param { RiotParser.Node.Tag } sourceNode - tag containing the each attribute - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @param { string } selector - binding selector - * @returns { Array } array with only the tag binding AST - */ -function createNestedBindings(sourceNode, sourceFile, sourceCode, selector) { - const mightBeARiotComponent = isCustomNode(sourceNode); - - return mightBeARiotComponent ? [null, [ - createTagBinding( - cloneNodeWithoutSelectorAttribute(sourceNode, selector), - null, - sourceFile, - sourceCode - )] - ] : build(createRootNode(sourceNode), sourceFile, sourceCode) -} - -/** - * Build the template and the bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and the dom bindings - */ -function build( - sourceNode, - sourceFile, - sourceCode, - state -) { - if (!sourceNode) panic('Something went wrong with your tag DOM parsing, your tag template can\'t be created'); - - const [nodeHTML, nodeBindings] = parseNode(sourceNode, sourceFile, sourceCode); - const childrenNodes = getChildrenNodes(sourceNode); - const currentState = { ...cloneDeep(BuildingState), ...state }; - - // mutate the original arrays - currentState.html.push(...nodeHTML); - currentState.bindings.push(...nodeBindings); - - // do recursion if - // this tag has children and it has no special directives bound to it - if (childrenNodes.length && !hasItsOwnTemplate(sourceNode)) { - childrenNodes.forEach(node => build(node, sourceFile, sourceCode, { parent: sourceNode, ...currentState })); - } - - // close the tag if it's not a void one - if (isTagNode(sourceNode) && !isVoidNode(sourceNode)) { - currentState.html.push(closeTag(sourceNode)); - } - - return [ - currentState.html.join(''), - currentState.bindings - ] -} - -const templateFunctionArguments = [ - TEMPLATE_FN, - EXPRESSION_TYPES, - BINDING_TYPES, - GET_COMPONENT_FN -].map(builders.identifier); - -/** - * Create the content of the template function - * @param { RiotParser.Node } sourceNode - node generated by the riot compiler - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns {AST.BlockStatement} the content of the template function - */ -function createTemplateFunctionContent(sourceNode, sourceFile, sourceCode) { - return builders.blockStatement([ - builders.returnStatement( - callTemplateFunction( - ...build( - createRootNode(sourceNode), - sourceFile, - sourceCode - ) - ) - ) - ]) -} - -/** - * Extend the AST adding the new template property containing our template call to render the component - * @param { Object } ast - current output ast - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @param { RiotParser.Node } sourceNode - node generated by the riot compiler - * @returns { Object } the output ast having the "template" key - */ -function extendTemplateProperty(ast, sourceFile, sourceCode, sourceNode) { - types.visit(ast, { - visitProperty(path) { - if (path.value.key.value === TAG_TEMPLATE_PROPERTY) { - path.value.value = builders.functionExpression( - null, - templateFunctionArguments, - createTemplateFunctionContent(sourceNode, sourceFile, sourceCode) - ); - - return false - } - - this.traverse(path); - } - }); - - return ast -} - -/** - * Generate the component template logic - * @param { RiotParser.Node } sourceNode - node generated by the riot compiler - * @param { string } source - original component source code - * @param { Object } meta - compilation meta information - * @param { AST } ast - current AST output - * @returns { AST } the AST generated - */ -function template(sourceNode, source, meta, ast) { - const { options } = meta; - return extendTemplateProperty(ast, options.file, source, sourceNode) -} - -const DEFAULT_OPTIONS = { - template: 'default', - file: '[unknown-source-file]', - scopedCss: true -}; - -/** - * Create the initial AST - * @param {string} tagName - the name of the component we have compiled - * @returns { AST } the initial AST - * - * @example - * // the output represents the following string in AST - */ -function createInitialInput({tagName}) { - /* - generates - export default { - ${TAG_CSS_PROPERTY}: null, - ${TAG_LOGIC_PROPERTY}: null, - ${TAG_TEMPLATE_PROPERTY}: null - } - */ - return builders.program([ - builders.exportDefaultDeclaration( - builders.objectExpression([ - simplePropertyNode(TAG_CSS_PROPERTY, nullNode()), - simplePropertyNode(TAG_LOGIC_PROPERTY, nullNode()), - simplePropertyNode(TAG_TEMPLATE_PROPERTY, nullNode()), - simplePropertyNode(TAG_NAME_PROPERTY, builders.literal(tagName)) - ]) - )] - ) -} - -/** - * Make sure the input sourcemap is valid otherwise we ignore it - * @param {SourceMapGenerator} map - preprocessor source map - * @returns {Object} sourcemap as json or nothing - */ -function normaliseInputSourceMap(map) { - const inputSourceMap = sourcemapAsJSON(map); - return isEmptySourcemap(inputSourceMap) ? null : inputSourceMap -} - -/** - * Override the sourcemap content making sure it will always contain the tag source code - * @param {Object} map - sourcemap as json - * @param {string} source - component source code - * @returns {Object} original source map with the "sourcesContent" property overriden - */ -function overrideSourcemapContent(map, source) { - return { - ...map, - sourcesContent: [source] - } -} - -/** - * Create the compilation meta object - * @param { string } source - source code of the tag we will need to compile - * @param { string } options - compiling options - * @returns {Object} meta object - */ -function createMeta(source, options) { - return { - tagName: null, - fragments: null, - options: { - ...DEFAULT_OPTIONS, - ...options - }, - source - } -} - -/** - * Generate the output code source together with the sourcemap - * @param { string } source - source code of the tag we will need to compile - * @param { string } opts - compiling options - * @returns { Output } object containing output code and source map - */ -function compile(source, opts = {}) { - const meta = createMeta(source, opts); - const {options} = meta; - const { code, map } = execute$1('template', options.template, meta, source); - const { template: template$1, css: css$1, javascript: javascript$1 } = riotParser(options).parse(code).output; - - // extend the meta object with the result of the parsing - Object.assign(meta, { - tagName: template$1.name, - fragments: { template: template$1, css: css$1, javascript: javascript$1 } - }); - - return compose( - result => ({ ...result, meta }), - result => execute(result, meta), - result => ({ - ...result, - map: overrideSourcemapContent(result.map, source) - }), - ast => meta.ast = ast && generateJavascript(ast, { - sourceMapName: `${options.file}.map`, - inputSourceMap: normaliseInputSourceMap(map) - }), - hookGenerator(template, template$1, code, meta), - hookGenerator(javascript, javascript$1, code, meta), - hookGenerator(css, css$1, code, meta), - )(createInitialInput(meta)) -} - -/** - * Prepare the riot parser node transformers - * @param { Function } transformer - transformer function - * @param { Object } sourceNode - riot parser node - * @param { string } source - component source code - * @param { Object } meta - compilation meta information - * @returns { Promise } object containing output code and source map - */ -function hookGenerator(transformer, sourceNode, source, meta) { - if ( - // filter missing nodes - !sourceNode || - // filter nodes without children - (sourceNode.nodes && !sourceNode.nodes.length) || - // filter empty javascript and css nodes - (!sourceNode.nodes && !sourceNode.text)) { - return result => result - } - - return curry(transformer)(sourceNode, source, meta) -} - -// This function can be used to register new preprocessors -// a preprocessor can target either only the css or javascript nodes -// or the complete tag source file ('template') -const registerPreprocessor = register$1; - -// This function can allow you to register postprocessors that will parse the output code -// here we can run prettifiers, eslint fixes... -const registerPostprocessor = register; - -export { compile, createInitialInput, registerPostprocessor, registerPreprocessor }; diff --git a/node_modules/@riotjs/compiler/dist/index.js b/node_modules/@riotjs/compiler/dist/index.js deleted file mode 100644 index 12bcdbd..0000000 --- a/node_modules/@riotjs/compiler/dist/index.js +++ /dev/null @@ -1,2167 +0,0 @@ -/* Riot Compiler WIP, @license MIT */ -'use strict'; - -Object.defineProperty(exports, '__esModule', { value: true }); - -function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } - -var recast = require('recast'); -var util = require('recast/lib/util'); -var sourceMap = require('source-map'); -var compose = _interopDefault(require('cumpa')); -var cssEscape = _interopDefault(require('cssesc')); -var curry = _interopDefault(require('curri')); -var acorn = require('acorn'); -var globalScope = _interopDefault(require('globals')); -var riotParser = require('@riotjs/parser'); -var riotParser__default = _interopDefault(riotParser); -var domNodes = require('dom-nodes'); - -const TAG_LOGIC_PROPERTY = 'exports'; -const TAG_CSS_PROPERTY = 'css'; -const TAG_TEMPLATE_PROPERTY = 'template'; -const TAG_NAME_PROPERTY = 'name'; - -const types = recast.types; -const builders = recast.types.builders; -const namedTypes = recast.types.namedTypes; - -function nullNode() { - return builders.literal(null) -} - -function simplePropertyNode(key, value) { - return builders.property('init', builders.literal(key), value, false) -} - -/** - * Return a source map as JSON, it it has not the toJSON method it means it can - * be used right the way - * @param { SourceMapGenerator|Object } map - a sourcemap generator or simply an json object - * @returns { Object } the source map as JSON - */ -function sourcemapAsJSON(map) { - if (map && map.toJSON) return map.toJSON() - return map -} - -/** - * Detect node js environements - * @returns { boolean } true if the runtime is node - */ -function isNode() { - return typeof process !== 'undefined' -} - -/** - * Compose two sourcemaps - * @param { SourceMapGenerator } formerMap - original sourcemap - * @param { SourceMapGenerator } latterMap - target sourcemap - * @returns { Object } sourcemap json - */ -function composeSourcemaps(formerMap, latterMap) { - if ( - isNode() && - formerMap && latterMap && latterMap.mappings - ) { - return util.composeSourceMaps(sourcemapAsJSON(formerMap), sourcemapAsJSON(latterMap)) - } else if (isNode() && formerMap) { - return sourcemapAsJSON(formerMap) - } - - return {} -} - -/** - * Create a new sourcemap generator - * @param { Object } options - sourcemap options - * @returns { SourceMapGenerator } SourceMapGenerator instance - */ -function createSourcemap(options) { - return new sourceMap.SourceMapGenerator(options) -} - -const Output = Object.freeze({ - code: '', - ast: [], - meta: {}, - map: null -}); - -/** - * Create the right output data result of a parsing - * @param { Object } data - output data - * @param { string } data.code - code generated - * @param { AST } data.ast - ast representing the code - * @param { SourceMapGenerator } data.map - source map generated along with the code - * @param { Object } meta - compilation meta infomration - * @returns { Output } output container object - */ -function createOutput(data, meta) { - const output = { - ...Output, - ...data, - meta - }; - - if (!output.map && meta && meta.options && meta.options.file) - return { - ...output, - map: createSourcemap({ file: meta.options.file }) - } - - return output -} - -/** - * Transform the source code received via a compiler function - * @param { Function } compiler - function needed to generate the output code - * @param { Object } meta - compilation meta information - * @param { string } source - source code - * @returns { Output } output - the result of the compiler - */ -function transform(compiler, meta, source) { - const result = (compiler ? compiler(source, meta) : { code: source }); - return createOutput(result, meta) -} - -/** - * Throw an error with a descriptive message - * @param { string } message - error message - * @returns { undefined } hoppla.. at this point the program should stop working - */ -function panic(message) { - throw new Error(message) -} - -const postprocessors = new Set(); - -/** - * Register a postprocessor that will be used after the parsing and compilation of the riot tags - * @param { Function } postprocessor - transformer that will receive the output code ans sourcemap - * @returns { Set } the postprocessors collection - */ -function register(postprocessor) { - if (postprocessors.has(postprocessor)) { - panic(`This postprocessor "${postprocessor.name || postprocessor.toString()}" was already registered`); - } - - postprocessors.add(postprocessor); - - return postprocessors -} - -/** - * Exec all the postprocessors in sequence combining the sourcemaps generated - * @param { Output } compilerOutput - output generated by the compiler - * @param { Object } meta - compiling meta information - * @returns { Output } object containing output code and source map - */ -function execute(compilerOutput, meta) { - return Array.from(postprocessors).reduce(function(acc, postprocessor) { - const { code, map } = acc; - const output = postprocessor(code, meta); - - return { - code: output.code, - map: composeSourcemaps(map, output.map) - } - }, createOutput(compilerOutput, meta)) -} - -/** - * Parsers that can be registered by users to preparse components fragments - * @type { Object } - */ -const preprocessors = Object.freeze({ - javascript: new Map(), - css: new Map(), - template: new Map().set('default', code => ({ code })) -}); - -// throw a processor type error -function preprocessorTypeError(type) { - panic(`No preprocessor of type "${type}" was found, please make sure to use one of these: 'javascript', 'css' or 'template'`); -} - -// throw an error if the preprocessor was not registered -function preprocessorNameNotFoundError(name) { - panic(`No preprocessor named "${name}" was found, are you sure you have registered it?'`); -} - -/** - * Register a custom preprocessor - * @param { string } type - preprocessor type either 'js', 'css' or 'template' - * @param { string } name - unique preprocessor id - * @param { Function } preprocessor - preprocessor function - * @returns { Map } - the preprocessors map - */ -function register$1(type, name, preprocessor) { - if (!type) panic('Please define the type of preprocessor you want to register \'javascript\', \'css\' or \'template\''); - if (!name) panic('Please define a name for your preprocessor'); - if (!preprocessor) panic('Please provide a preprocessor function'); - if (!preprocessors[type]) preprocessorTypeError(type); - if (preprocessors[type].has(name)) panic(`The preprocessor ${name} was already registered before`); - - preprocessors[type].set(name, preprocessor); - - return preprocessors -} - -/** - * Exec the compilation of a preprocessor - * @param { string } type - preprocessor type either 'js', 'css' or 'template' - * @param { string } name - unique preprocessor id - * @param { Object } meta - preprocessor meta information - * @param { string } source - source code - * @returns { Output } object containing a sourcemap and a code string - */ -function execute$1(type, name, meta, source) { - if (!preprocessors[type]) preprocessorTypeError(type); - if (!preprocessors[type].has(name)) preprocessorNameNotFoundError(name); - - return transform(preprocessors[type].get(name), meta, source) -} - -const ATTRIBUTE_TYPE_NAME = 'type'; - -/** - * Get the type attribute from a node generated by the riot parser - * @param { Object} sourceNode - riot parser node - * @returns { string|null } a valid type to identify the preprocessor to use or nothing - */ -function getPreprocessorTypeByAttribute(sourceNode) { - const typeAttribute = sourceNode.attributes ? - sourceNode.attributes.find(attribute => attribute.name === ATTRIBUTE_TYPE_NAME) : - null; - - return typeAttribute ? normalize(typeAttribute.value) : null -} - - -/** - * Remove the noise in case a user has defined the preprocessor type='text/scss' - * @param { string } value - input string - * @returns { string } normalized string - */ -function normalize(value) { - return value.replace('text/', '') -} - -/** - * Preprocess a riot parser node - * @param { string } preprocessorType - either css, js - * @param { string } preprocessorName - preprocessor id - * @param { Object } meta - compilation meta information - * @param { RiotParser.nodeTypes } node - css node detected by the parser - * @returns { Output } code and sourcemap generated by the preprocessor - */ -function preprocess(preprocessorType, preprocessorName, meta, node) { - const code = node.text; - - return (preprocessorName ? - execute$1(preprocessorType, preprocessorName, meta, code) : - { code } - ) -} - -/** - * Matches valid, multiline JavaScript comments in almost all its forms. - * @const {RegExp} - * @static - */ -const R_MLCOMMS = /\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//g; - -/** - * Source for creating regexes matching valid quoted, single-line JavaScript strings. - * It recognizes escape characters, including nested quotes and line continuation. - * @const {string} - */ -const S_LINESTR = /"[^"\n\\]*(?:\\[\S\s][^"\n\\]*)*"|'[^'\n\\]*(?:\\[\S\s][^'\n\\]*)*'/.source; - -/** - * Matches CSS selectors, excluding those beginning with '@' and quoted strings. - * @const {RegExp} - */ - -const CSS_SELECTOR = RegExp(`([{}]|^)[; ]*((?:[^@ ;{}][^{}]*)?[^@ ;{}:] ?)(?={)|${S_LINESTR}`, 'g'); - -/** - * Parses styles enclosed in a "scoped" tag - * The "css" string is received without comments or surrounding spaces. - * - * @param {string} tag - Tag name of the root element - * @param {string} css - The CSS code - * @returns {string} CSS with the styles scoped to the root element - */ -function scopedCSS(tag, css) { - const host = ':host'; - const selectorsBlacklist = ['from', 'to']; - - return css.replace(CSS_SELECTOR, function(m, p1, p2) { - // skip quoted strings - if (!p2) return m - - // we have a selector list, parse each individually - p2 = p2.replace(/[^,]+/g, function(sel) { - const s = sel.trim(); - - // skip selectors already using the tag name - if (s.indexOf(tag) === 0) { - return sel - } - - // skips the keywords and percents of css animations - if (!s || selectorsBlacklist.indexOf(s) > -1 || s.slice(-1) === '%') { - return sel - } - - // replace the `:host` pseudo-selector, where it is, with the root tag name; - // if `:host` was not included, add the tag name as prefix, and mirror all - // `[data-is]` - if (s.indexOf(host) < 0) { - return `${tag} ${s},[is="${tag}"] ${s}` - } else { - return `${s.replace(host, tag)},${ - s.replace(host, `[is="${tag}"]`)}` - } - }); - - // add the danling bracket char and return the processed selector list - return p1 ? `${p1} ${p2}` : p2 - }) -} - -/** - * Remove comments, compact and trim whitespace - * @param { string } code - compiled css code - * @returns { string } css code normalized - */ -function compactCss(code) { - return code.replace(R_MLCOMMS, '').replace(/\s+/g, ' ').trim() -} - -const escapeBackslashes = s => s.replace(/\\/g, '\\\\'); -const escapeIdentifier = identifier => escapeBackslashes(cssEscape(identifier, { - isIdentifier: true -})); - -/** - * Generate the component css - * @param { Object } sourceNode - node generated by the riot compiler - * @param { string } source - original component source code - * @param { Object } meta - compilation meta information - * @param { AST } ast - current AST output - * @returns { AST } the AST generated - */ -function css(sourceNode, source, meta, ast) { - const preprocessorName = getPreprocessorTypeByAttribute(sourceNode); - const { options } = meta; - const preprocessorOutput = preprocess('css', preprocessorName, meta, sourceNode.text); - const normalizedCssCode = compactCss(preprocessorOutput.code); - const escapedCssIdentifier = escapeIdentifier(meta.tagName); - - const cssCode = (options.scopedCss ? - scopedCSS(escapedCssIdentifier, escapeBackslashes(normalizedCssCode)) : - escapeBackslashes(normalizedCssCode) - ).trim(); - - types.visit(ast, { - visitProperty(path) { - if (path.value.key.value === TAG_CSS_PROPERTY) { - path.value.value = builders.templateLiteral( - [builders.templateElement({ raw: cssCode, cooked: '' }, false)], - [] - ); - - return false - } - - this.traverse(path); - } - }); - - return ast -} - -/** - * Generate the javascript from an ast source - * @param {AST} ast - ast object - * @param {Object} options - printer options - * @returns {Object} code + map - */ -function generateJavascript(ast, options) { - return recast.print(ast, { - ...options, - tabWidth: 2, - quote: 'single' - }) -} - -/** - * True if the sourcemap has no mappings, it is empty - * @param {Object} map - sourcemap json - * @returns {boolean} true if empty - */ -function isEmptySourcemap(map) { - return !map || !map.mappings || !map.mappings.length -} - -const LINES_RE = /\r\n?|\n/g; - -/** - * Split a string into a rows array generated from its EOL matches - * @param { string } string [description] - * @returns { Array } array containing all the string rows - */ -function splitStringByEOL(string) { - return string.split(LINES_RE) -} - -/** - * Get the line and the column of a source text based on its position in the string - * @param { string } string - target string - * @param { number } position - target position - * @returns { Object } object containing the source text line and column - */ -function getLineAndColumnByPosition(string, position) { - const lines = splitStringByEOL(string.slice(0, position)); - - return { - line: lines.length, - column: lines[lines.length - 1].length - } -} - -/** - * Add the offset to the code that must be parsed in order to generate properly the sourcemaps - * @param {string} input - input string - * @param {string} source - original source code - * @param {RiotParser.Node} node - node that we are going to transform - * @return {string} the input string with the offset properly set - */ -function addLineOffset(input, source, node) { - const {column, line} = getLineAndColumnByPosition(source, node.start); - return `${'\n'.repeat(line - 1)}${' '.repeat(column + 1)}${input}` -} - -/** - * Parse a js source to generate the AST - * @param {string} source - javascript source - * @param {Object} options - parser options - * @returns {AST} AST tree - */ -function generateAST(source, options) { - return recast.parse(source, { - parser: { - parse(source, opts) { - return acorn.Parser.parse(source, { - ...opts, - ecmaVersion: 2020 - }) - } - }, - ...options - }) -} - -const browserAPIs = Object.keys(globalScope.browser); -const builtinAPIs = Object.keys(globalScope.builtin); - -const isIdentifier = namedTypes.Identifier.check.bind(namedTypes.Identifier); -const isLiteral = namedTypes.Literal.check.bind(namedTypes.Literal); -const isExpressionStatement = namedTypes.ExpressionStatement.check.bind(namedTypes.ExpressionStatement); -const isObjectExpression = namedTypes.ObjectExpression.check.bind(namedTypes.ObjectExpression); -const isThisExpression = namedTypes.ThisExpression.check.bind(namedTypes.ThisExpression); -const isNewExpression = namedTypes.NewExpression.check.bind(namedTypes.NewExpression); -const isSequenceExpression = namedTypes.SequenceExpression.check.bind(namedTypes.SequenceExpression); -const isBinaryExpression = namedTypes.BinaryExpression.check.bind(namedTypes.BinaryExpression); -const isExportDefaultStatement = namedTypes.ExportDefaultDeclaration.check.bind(namedTypes.ExportDefaultDeclaration); - -const isBrowserAPI = ({name}) => browserAPIs.includes(name); -const isBuiltinAPI = ({name}) => builtinAPIs.includes(name); -const isRaw = (node) => node && node.raw; // eslint-disable-line - -/** - * Find the export default statement - * @param { Array } body - tree structure containing the program code - * @returns { Object } node containing only the code of the export default statement - */ -function findExportDefaultStatement(body) { - return body.find(isExportDefaultStatement) -} - -/** - * Find all the code in an ast program except for the export default statements - * @param { Array } body - tree structure containing the program code - * @returns { Array } array containing all the program code except the export default expressions - */ -function filterNonExportDefaultStatements(body) { - return body.filter(node => !isExportDefaultStatement(node)) -} - -/** - * Get the body of the AST structure - * @param { Object } ast - ast object generated by recast - * @returns { Array } array containing the program code - */ -function getProgramBody(ast) { - return ast.body || ast.program.body -} - -/** - * Extend the AST adding the new tag method containing our tag sourcecode - * @param { Object } ast - current output ast - * @param { Object } exportDefaultNode - tag export default node - * @returns { Object } the output ast having the "tag" key extended with the content of the export default - */ -function extendTagProperty(ast, exportDefaultNode) { - types.visit(ast, { - visitProperty(path) { - if (path.value.key.value === TAG_LOGIC_PROPERTY) { - path.value.value = exportDefaultNode.declaration; - return false - } - - this.traverse(path); - } - }); - - return ast -} - -/** - * Generate the component javascript logic - * @param { Object } sourceNode - node generated by the riot compiler - * @param { string } source - original component source code - * @param { Object } meta - compilation meta information - * @param { AST } ast - current AST output - * @returns { AST } the AST generated - */ -function javascript(sourceNode, source, meta, ast) { - const preprocessorName = getPreprocessorTypeByAttribute(sourceNode); - const javascriptNode = addLineOffset(sourceNode.text.text, source, sourceNode); - const { options } = meta; - const preprocessorOutput = preprocess('javascript', preprocessorName, meta, { - ...sourceNode, - text: javascriptNode - }); - const inputSourceMap = sourcemapAsJSON(preprocessorOutput.map); - const generatedAst = generateAST(preprocessorOutput.code, { - sourceFileName: options.file, - inputSourceMap: isEmptySourcemap(inputSourceMap) ? null : inputSourceMap - }); - const generatedAstBody = getProgramBody(generatedAst); - const bodyWithoutExportDefault = filterNonExportDefaultStatements(generatedAstBody); - const exportDefaultNode = findExportDefaultStatement(generatedAstBody); - const outputBody = getProgramBody(ast); - - // add to the ast the "private" javascript content of our tag script node - outputBody.unshift(...bodyWithoutExportDefault); - - // convert the export default adding its content to the "tag" property exported - if (exportDefaultNode) extendTagProperty(ast, exportDefaultNode); - - return ast -} - -// import {IS_BOOLEAN,IS_CUSTOM,IS_RAW,IS_SPREAD,IS_VOID} from '@riotjs/parser/src/constants' - -const BINDING_TYPES = 'bindingTypes'; -const EACH_BINDING_TYPE = 'EACH'; -const IF_BINDING_TYPE = 'IF'; -const TAG_BINDING_TYPE = 'TAG'; -const SLOT_BINDING_TYPE = 'SLOT'; - - -const EXPRESSION_TYPES = 'expressionTypes'; -const ATTRIBUTE_EXPRESSION_TYPE = 'ATTRIBUTE'; -const VALUE_EXPRESSION_TYPE = 'VALUE'; -const TEXT_EXPRESSION_TYPE = 'TEXT'; -const EVENT_EXPRESSION_TYPE = 'EVENT'; - -const TEMPLATE_FN = 'template'; -const SCOPE = 'scope'; -const GET_COMPONENT_FN = 'getComponent'; - -// keys needed to create the DOM bindings -const BINDING_SELECTOR_KEY = 'selector'; -const BINDING_GET_COMPONENT_KEY = 'getComponent'; -const BINDING_TEMPLATE_KEY = 'template'; -const BINDING_TYPE_KEY = 'type'; -const BINDING_REDUNDANT_ATTRIBUTE_KEY = 'redundantAttribute'; -const BINDING_CONDITION_KEY = 'condition'; -const BINDING_ITEM_NAME_KEY = 'itemName'; -const BINDING_GET_KEY_KEY = 'getKey'; -const BINDING_INDEX_NAME_KEY = 'indexName'; -const BINDING_EVALUATE_KEY = 'evaluate'; -const BINDING_NAME_KEY = 'name'; -const BINDING_SLOTS_KEY = 'slots'; -const BINDING_EXPRESSIONS_KEY = 'expressions'; -const BINDING_CHILD_NODE_INDEX_KEY = 'childNodeIndex'; -// slots keys -const BINDING_BINDINGS_KEY = 'bindings'; -const BINDING_ID_KEY = 'id'; -const BINDING_HTML_KEY = 'html'; -const BINDING_ATTRIBUTES_KEY = 'attributes'; - -// DOM directives -const IF_DIRECTIVE = 'if'; -const EACH_DIRECTIVE = 'each'; -const KEY_ATTRIBUTE = 'key'; -const SLOT_ATTRIBUTE = 'slot'; -const NAME_ATTRIBUTE = 'name'; -const IS_DIRECTIVE = 'is'; - -// Misc -const DEFAULT_SLOT_NAME = 'default'; -const TEXT_NODE_EXPRESSION_PLACEHOLDER = ''; -const BINDING_SELECTOR_PREFIX = 'expr'; -const SLOT_TAG_NODE_NAME = 'slot'; -const PROGRESS_TAG_NODE_NAME = 'progress'; -const IS_VOID_NODE = 'isVoid'; -const IS_CUSTOM_NODE = 'isCustom'; -const IS_BOOLEAN_ATTRIBUTE = 'isBoolean'; -const IS_SPREAD_ATTRIBUTE = 'isSpread'; - -/** - * True if the node has not expression set nor bindings directives - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only if it's a static node that doesn't need bindings or expressions - */ -function isStaticNode(node) { - return [ - hasExpressions, - findEachAttribute, - findIfAttribute, - isCustomNode, - isSlotNode - ].every(test => !test(node)) -} - -/** - * Check if a node name is part of the browser or builtin javascript api or it belongs to the current scope - * @param { types.NodePath } path - containing the current node visited - * @returns {boolean} true if it's a global api variable - */ -function isGlobal({ scope, node }) { - return Boolean( - isRaw(node) || - isBuiltinAPI(node) || - isBrowserAPI(node) || - isNewExpression(node) || - isNodeInScope(scope, node), - ) -} - -/** - * Checks if the identifier of a given node exists in a scope - * @param {Scope} scope - scope where to search for the identifier - * @param {types.Node} node - node to search for the identifier - * @returns {boolean} true if the node identifier is defined in the given scope - */ -function isNodeInScope(scope, node) { - const traverse = (isInScope = false) => { - types.visit(node, { - visitIdentifier(path) { - if (scope.lookup(getName(path.node))) { - isInScope = true; - } - - this.abort(); - } - }); - - return isInScope - }; - - return traverse() -} - -/** - * True if the node has the isCustom attribute set - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if either it's a riot component or a custom element - */ -function isCustomNode(node) { - return !!(node[IS_CUSTOM_NODE] || hasIsAttribute(node)) -} - -/** - * True the node is - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if it's a slot node - */ -function isSlotNode(node) { - return node.name === SLOT_TAG_NODE_NAME -} - -/** - * True if the node has the isVoid attribute set - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if the node is self closing - */ -function isVoidNode(node) { - return !!node[IS_VOID_NODE] -} - -/** - * True if the riot parser did find a tag node - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the tag nodes - */ -function isTagNode(node) { - return node.type === riotParser.nodeTypes.TAG -} - -/** - * True if the riot parser did find a text node - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the text nodes - */ -function isTextNode(node) { - return node.type === riotParser.nodeTypes.TEXT -} - -/** - * True if the node parsed is the root one - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the root nodes - */ -function isRootNode(node) { - return node.isRoot -} - -/** - * True if the attribute parsed is of type spread one - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if the attribute node is of type spread - */ -function isSpreadAttribute(node) { - return node[IS_SPREAD_ATTRIBUTE] -} - -/** - * True if the node is an attribute and its name is "value" - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for value attribute nodes - */ -function isValueAttribute(node) { - return node.name === 'value' -} - -/** - * True if the DOM node is a progress tag - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true for the progress tags - */ -function isProgressNode(node) { - return node.name === PROGRESS_TAG_NODE_NAME -} - -/** - * True if the node is an attribute and a DOM handler - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for dom listener attribute nodes - */ -const isEventAttribute = (() => { - const EVENT_ATTR_RE = /^on/; - return node => EVENT_ATTR_RE.test(node.name) -})(); - -/** - * True if the node has expressions or expression attributes - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} ditto - */ -function hasExpressions(node) { - return !!( - node.expressions || - // has expression attributes - (getNodeAttributes(node).some(attribute => hasExpressions(attribute))) || - // has child text nodes with expressions - (node.nodes && node.nodes.some(node => isTextNode(node) && hasExpressions(node))) - ) -} - -/** - * True if the node is a directive having its own template - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the IF EACH and TAG bindings - */ -function hasItsOwnTemplate(node) { - return [ - findEachAttribute, - findIfAttribute, - isCustomNode - ].some(test => test(node)) -} - -const hasIfAttribute = compose(Boolean, findIfAttribute); -const hasEachAttribute = compose(Boolean, findEachAttribute); -const hasIsAttribute = compose(Boolean, findIsAttribute); -const hasKeyAttribute = compose(Boolean, findKeyAttribute); - -/** - * Find the attribute node - * @param { string } name - name of the attribute we want to find - * @param { riotParser.nodeTypes.TAG } node - a tag node - * @returns { riotParser.nodeTypes.ATTR } attribute node - */ -function findAttribute(name, node) { - return node.attributes && node.attributes.find(attr => getName(attr) === name) -} - -function findIfAttribute(node) { - return findAttribute(IF_DIRECTIVE, node) -} - -function findEachAttribute(node) { - return findAttribute(EACH_DIRECTIVE, node) -} - -function findKeyAttribute(node) { - return findAttribute(KEY_ATTRIBUTE, node) -} - -function findIsAttribute(node) { - return findAttribute(IS_DIRECTIVE, node) -} - -/** - * Find all the node attributes that are not expressions - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} list of all the static attributes - */ -function findStaticAttributes(node) { - return getNodeAttributes(node).filter(attribute => !hasExpressions(attribute)) -} - -/** - * Find all the node attributes that have expressions - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} list of all the dynamic attributes - */ -function findDynamicAttributes(node) { - return getNodeAttributes(node).filter(hasExpressions) -} - -/** - * Unescape the user escaped chars - * @param {string} string - input string - * @param {string} char - probably a '{' or anything the user want's to escape - * @returns {string} cleaned up string - */ -function unescapeChar(string, char) { - return string.replace(RegExp(`\\\\${char}`, 'gm'), char) -} - -const scope = builders.identifier(SCOPE); -const getName = node => node && node.name ? node.name : node; - -/** - * Replace the path scope with a member Expression - * @param { types.NodePath } path - containing the current node visited - * @param { types.Node } property - node we want to prefix with the scope identifier - * @returns {undefined} this is a void function - */ -function replacePathScope(path, property) { - path.replace(builders.memberExpression( - scope, - property, - false - )); -} - -/** - * Change the nodes scope adding the `scope` prefix - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean } return false if we want to stop the tree traversal - * @context { types.visit } - */ -function updateNodeScope(path) { - if (!isGlobal(path)) { - replacePathScope(path, path.node); - - return false - } - - this.traverse(path); -} - -/** - * Change the scope of the member expressions - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean } return always false because we want to check only the first node object - */ -function visitMemberExpression(path) { - if (!isGlobal(path) && !isGlobal({ node: path.node.object, scope: path.scope })) { - if (path.value.computed) { - this.traverse(path); - } else if (isBinaryExpression(path.node.object) || path.node.object.computed) { - this.traverse(path.get('object')); - } else if (!path.node.object.callee) { - replacePathScope(path, isThisExpression(path.node.object) ? path.node.property : path.node); - } else { - this.traverse(path.get('object')); - } - } - - return false -} - - -/** - * Objects properties should be handled a bit differently from the Identifier - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean } return false if we want to stop the tree traversal - */ -function visitProperty(path) { - const value = path.node.value; - - if (isIdentifier(value)) { - updateNodeScope(path.get('value')); - } else { - this.traverse(path.get('value')); - } - - return false -} - -/** - * The this expressions should be replaced with the scope - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean|undefined } return false if we want to stop the tree traversal - */ -function visitThisExpression(path) { - path.replace(scope); - this.traverse(path); -} - - -/** - * Update the scope of the global nodes - * @param { Object } ast - ast program - * @returns { Object } the ast program with all the global nodes updated - */ -function updateNodesScope(ast) { - const ignorePath = () => false; - - types.visit(ast, { - visitIdentifier: updateNodeScope, - visitMemberExpression, - visitProperty, - visitThisExpression, - visitClassExpression: ignorePath - }); - - return ast -} - -/** - * Convert any expression to an AST tree - * @param { Object } expression - expression parsed by the riot parser - * @param { string } sourceFile - original tag file - * @param { string } sourceCode - original tag source code - * @returns { Object } the ast generated - */ -function createASTFromExpression(expression, sourceFile, sourceCode) { - const code = sourceFile ? - addLineOffset(expression.text, sourceCode, expression) : - expression.text; - - return generateAST(`(${code})`, { - sourceFileName: sourceFile - }) -} - -/** - * Create the bindings template property - * @param {Array} args - arguments to pass to the template function - * @returns {ASTNode} a binding template key - */ -function createTemplateProperty(args) { - return simplePropertyNode( - BINDING_TEMPLATE_KEY, - args ? callTemplateFunction(...args) : nullNode() - ) -} - -/** - * Try to get the expression of an attribute node - * @param { RiotParser.Node.Attribute } attribute - riot parser attribute node - * @returns { RiotParser.Node.Expression } attribute expression value - */ -function getAttributeExpression(attribute) { - return attribute.expressions ? attribute.expressions[0] : { - // if no expression was found try to typecast the attribute value - ...attribute, - text: attribute.value - } -} - -/** - * Wrap the ast generated in a function call providing the scope argument - * @param {Object} ast - function body - * @returns {FunctionExpresion} function having the scope argument injected - */ -function wrapASTInFunctionWithScope(ast) { - return builders.functionExpression( - null, - [scope], - builders.blockStatement([builders.returnStatement( - ast - )]) - ) -} - -/** - * Convert any parser option to a valid template one - * @param { RiotParser.Node.Expression } expression - expression parsed by the riot parser - * @param { string } sourceFile - original tag file - * @param { string } sourceCode - original tag source code - * @returns { Object } a FunctionExpression object - * - * @example - * toScopedFunction('foo + bar') // scope.foo + scope.bar - * - * @example - * toScopedFunction('foo.baz + bar') // scope.foo.baz + scope.bar - */ -function toScopedFunction(expression, sourceFile, sourceCode) { - return compose( - wrapASTInFunctionWithScope, - transformExpression, - )(expression, sourceFile, sourceCode) -} - -/** - * Transform an expression node updating its global scope - * @param {RiotParser.Node.Expr} expression - riot parser expression node - * @param {string} sourceFile - source file - * @param {string} sourceCode - source code - * @returns {ASTExpression} ast expression generated from the riot parser expression node - */ -function transformExpression(expression, sourceFile, sourceCode) { - return compose( - getExpressionAST, - updateNodesScope, - createASTFromExpression - )(expression, sourceFile, sourceCode) -} - -/** - * Get the parsed AST expression of riot expression node - * @param {AST.Program} sourceAST - raw node parsed - * @returns {AST.Expression} program expression output - */ -function getExpressionAST(sourceAST) { - const astBody = sourceAST.program.body; - - return astBody[0] ? astBody[0].expression : astBody -} - -/** - * Create the template call function - * @param {Array|string|Node.Literal} template - template string - * @param {Array} bindings - template bindings provided as AST nodes - * @returns {Node.CallExpression} template call expression - */ -function callTemplateFunction(template, bindings) { - return builders.callExpression(builders.identifier(TEMPLATE_FN), [ - template ? builders.literal(template) : nullNode(), - bindings ? builders.arrayExpression(bindings) : nullNode() - ]) -} - -/** - * Convert any DOM attribute into a valid DOM selector useful for the querySelector API - * @param { string } attributeName - name of the attribute to query - * @returns { string } the attribute transformed to a query selector - */ -const attributeNameToDOMQuerySelector = attributeName => `[${attributeName}]`; - -/** - * Create the properties to query a DOM node - * @param { string } attributeName - attribute name needed to identify a DOM node - * @returns { Array } array containing the selector properties needed for the binding - */ -function createSelectorProperties(attributeName) { - return attributeName ? [ - simplePropertyNode(BINDING_REDUNDANT_ATTRIBUTE_KEY, builders.literal(attributeName)), - simplePropertyNode(BINDING_SELECTOR_KEY, - compose(builders.literal, attributeNameToDOMQuerySelector)(attributeName) - ) - ] : [] -} - -/** - * Clone the node filtering out the selector attribute from the attributes list - * @param {RiotParser.Node} node - riot parser node - * @param {string} selectorAttribute - name of the selector attribute to filter out - * @returns {RiotParser.Node} the node with the attribute cleaned up - */ -function cloneNodeWithoutSelectorAttribute(node, selectorAttribute) { - return { - ...node, - attributes: getAttributesWithoutSelector(getNodeAttributes(node), selectorAttribute) - } -} - - -/** - * Get the node attributes without the selector one - * @param {Array} attributes - attributes list - * @param {string} selectorAttribute - name of the selector attribute to filter out - * @returns {Array} filtered attributes - */ -function getAttributesWithoutSelector(attributes, selectorAttribute) { - if (selectorAttribute) - return attributes.filter(attribute => attribute.name !== selectorAttribute) - - return attributes -} - -/** - * Clean binding or custom attributes - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} only the attributes that are not bindings or directives - */ -function cleanAttributes(node) { - return getNodeAttributes(node).filter(attribute => ![ - IF_DIRECTIVE, - EACH_DIRECTIVE, - KEY_ATTRIBUTE, - SLOT_ATTRIBUTE, - IS_DIRECTIVE - ].includes(attribute.name)) -} - -/** - * Create a root node proxing only its nodes and attributes - * @param {RiotParser.Node} node - riot parser node - * @returns {RiotParser.Node} root node - */ -function createRootNode(node) { - return { - nodes: getChildrenNodes(node), - isRoot: true, - // root nodes shuold't have directives - attributes: cleanAttributes(node) - } -} - -/** - * Get all the child nodes of a RiotParser.Node - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} all the child nodes found - */ -function getChildrenNodes(node) { - return node && node.nodes ? node.nodes : [] -} - -/** - * Get all the attributes of a riot parser node - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} all the attributes find - */ -function getNodeAttributes(node) { - return node.attributes ? node.attributes : [] -} -/** - * Get the name of a custom node transforming it into an expression node - * @param {RiotParser.Node} node - riot parser node - * @returns {RiotParser.Node.Attr} the node name as expression attribute - */ -function getCustomNodeNameAsExpression(node) { - const isAttribute = findIsAttribute(node); - const toRawString = val => `'${val}'`; - - if (isAttribute) { - return isAttribute.expressions ? isAttribute.expressions[0] : { - ...isAttribute, - text: toRawString(isAttribute.value) - } - } - - return { ...node, text: toRawString(getName(node)) } -} - -/** - * Convert all the node static attributes to strings - * @param {RiotParser.Node} node - riot parser node - * @returns {string} all the node static concatenated as string - */ -function staticAttributesToString(node) { - return findStaticAttributes(node) - .map(attribute => attribute[IS_BOOLEAN_ATTRIBUTE] || !attribute.value ? - attribute.name : - `${attribute.name}="${unescapeNode(attribute, 'value').value}"` - ).join(' ') -} - -/** - * Make sure that node escaped chars will be unescaped - * @param {RiotParser.Node} node - riot parser node - * @param {string} key - key property to unescape - * @returns {RiotParser.Node} node with the text property unescaped - */ -function unescapeNode(node, key) { - if (node.unescape) { - return { - ...node, - [key]: unescapeChar(node[key], node.unescape) - } - } - - return node -} - - -/** - * Convert a riot parser opening node into a string - * @param {RiotParser.Node} node - riot parser node - * @returns {string} the node as string - */ -function nodeToString(node) { - const attributes = staticAttributesToString(node); - - switch(true) { - case isTagNode(node): - return `<${node.name}${attributes ? ` ${attributes}` : ''}${isVoidNode(node) ? '/' : ''}>` - case isTextNode(node): - return hasExpressions(node) ? TEXT_NODE_EXPRESSION_PLACEHOLDER : unescapeNode(node, 'text').text - default: - return '' - } -} - -/** - * Close an html node - * @param {RiotParser.Node} node - riot parser node - * @returns {string} the closing tag of the html tag node passed to this function - */ -function closeTag(node) { - return node.name ? `` : '' -} - -/** - * Create a strings array with the `join` call to transform it into a string - * @param {Array} stringsArray - array containing all the strings to concatenate - * @returns {AST.CallExpression} array with a `join` call - */ -function createArrayString(stringsArray) { - return builders.callExpression( - builders.memberExpression( - builders.arrayExpression(stringsArray), - builders.identifier('join'), - false - ), - [builders.literal('')], - ) -} - -/** - * Simple expression bindings might contain multiple expressions like for example: "class="{foo} red {bar}"" - * This helper aims to merge them in a template literal if it's necessary - * @param {RiotParser.Attr} node - riot parser node - * @param {string} sourceFile - original tag file - * @param {string} sourceCode - original tag source code - * @returns { Object } a template literal expression object - */ -function mergeAttributeExpressions(node, sourceFile, sourceCode) { - if (!node.parts || node.parts.length === 1) { - return transformExpression(node.expressions[0], sourceFile, sourceCode) - } - const stringsArray = [ - ...node.parts.reduce((acc, str) => { - const expression = node.expressions.find(e => e.text.trim() === str); - - return [ - ...acc, - expression ? transformExpression(expression, sourceFile, sourceCode) : builders.literal(str) - ] - }, []) - ].filter(expr => !isLiteral(expr) || expr.value); - - - return createArrayString(stringsArray) -} - -/** - * Create a selector that will be used to find the node via dom-bindings - * @param {number} id - temporary variable that will be increased anytime this function will be called - * @returns {string} selector attribute needed to bind a riot expression - */ -const createBindingSelector = (function createSelector(id = 0) { - return () => `${BINDING_SELECTOR_PREFIX}${id++}` -}()); - -/** - * Create an attribute evaluation function - * @param {RiotParser.Attr} sourceNode - riot parser node - * @param {string} sourceFile - original tag file - * @param {string} sourceCode - original tag source code - * @returns { AST.Node } an AST function expression to evaluate the attribute value - */ -function createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) { - return hasExpressions(sourceNode) ? - // dynamic attribute - wrapASTInFunctionWithScope(mergeAttributeExpressions(sourceNode, sourceFile, sourceCode)) : - // static attribute - builders.functionExpression( - null, - [], - builders.blockStatement([ - builders.returnStatement(builders.literal(sourceNode.value || true)) - ]), - ) -} - -/** - * Simple clone deep function, do not use it for classes or recursive objects! - * @param {*} source - possibily an object to clone - * @returns {*} the object we wanted to clone - */ -function cloneDeep(source) { - return JSON.parse(JSON.stringify(source)) -} - -const getEachItemName = expression => isSequenceExpression(expression.left) ? expression.left.expressions[0] : expression.left; -const getEachIndexName = expression => isSequenceExpression(expression.left) ? expression.left.expressions[1] : null; -const getEachValue = expression => expression.right; -const nameToliteral = compose(builders.literal, getName); - -const generateEachItemNameKey = expression => simplePropertyNode( - BINDING_ITEM_NAME_KEY, - compose(nameToliteral, getEachItemName)(expression) -); - -const generateEachIndexNameKey = expression => simplePropertyNode( - BINDING_INDEX_NAME_KEY, - compose(nameToliteral, getEachIndexName)(expression) -); - -const generateEachEvaluateKey = (expression, eachExpression, sourceFile, sourceCode) => simplePropertyNode( - BINDING_EVALUATE_KEY, - compose( - e => toScopedFunction(e, sourceFile, sourceCode), - e => ({ - ...eachExpression, - text: generateJavascript(e).code - }), - getEachValue - )(expression) -); - -/** - * Get the each expression properties to create properly the template binding - * @param { DomBinding.Expression } eachExpression - original each expression data - * @param { string } sourceFile - original tag file - * @param { string } sourceCode - original tag source code - * @returns { Array } AST nodes that are needed to build an each binding - */ -function generateEachExpressionProperties(eachExpression, sourceFile, sourceCode) { - const ast = createASTFromExpression(eachExpression, sourceFile, sourceCode); - const body = ast.program.body; - const firstNode = body[0]; - - if (!isExpressionStatement(firstNode)) { - panic(`The each directives supported should be of type "ExpressionStatement",you have provided a "${firstNode.type}"`); - } - - const { expression } = firstNode; - - return [ - generateEachItemNameKey(expression), - generateEachIndexNameKey(expression), - generateEachEvaluateKey(expression, eachExpression, sourceFile, sourceCode) - ] -} - -/** - * Transform a RiotParser.Node.Tag into an each binding - * @param { RiotParser.Node.Tag } sourceNode - tag containing the each attribute - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } an each binding node - */ -function createEachBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - const [ifAttribute, eachAttribute, keyAttribute] = [ - findIfAttribute, - findEachAttribute, - findKeyAttribute - ].map(f => f(sourceNode)); - const attributeOrNull = attribute => attribute ? toScopedFunction(getAttributeExpression(attribute), sourceFile, sourceCode) : nullNode(); - - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(EACH_BINDING_TYPE), - false - ), - ), - simplePropertyNode(BINDING_GET_KEY_KEY, attributeOrNull(keyAttribute)), - simplePropertyNode(BINDING_CONDITION_KEY, attributeOrNull(ifAttribute)), - createTemplateProperty(createNestedBindings(sourceNode, sourceFile, sourceCode, selectorAttribute)), - ...createSelectorProperties(selectorAttribute), - ...compose(generateEachExpressionProperties, getAttributeExpression)(eachAttribute) - ]) -} - -/** - * Transform a RiotParser.Node.Tag into an if binding - * @param { RiotParser.Node.Tag } sourceNode - tag containing the if attribute - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { stiring } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } an if binding node - */ -function createIfBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - const ifAttribute = findIfAttribute(sourceNode); - - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(IF_BINDING_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_EVALUATE_KEY, - toScopedFunction(ifAttribute.expressions[0], sourceFile, sourceCode) - ), - ...createSelectorProperties(selectorAttribute), - createTemplateProperty(createNestedBindings(sourceNode, sourceFile, sourceCode, selectorAttribute)) - ]) -} - -/** - * Create a simple attribute expression - * @param {RiotParser.Node.Attr} sourceNode - the custom tag - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {AST.Node} object containing the expression binding keys - */ -function createAttributeExpression(sourceNode, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(ATTRIBUTE_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode(BINDING_NAME_KEY, isSpreadAttribute(sourceNode) ? nullNode() : builders.literal(sourceNode.name)), - simplePropertyNode( - BINDING_EVALUATE_KEY, - createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) - ) - ]) -} - -/** - * Create a simple event expression - * @param {RiotParser.Node.Attr} sourceNode - attribute containing the event handlers - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {AST.Node} object containing the expression binding keys - */ -function createEventExpression(sourceNode, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(EVENT_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode(BINDING_NAME_KEY, builders.literal(sourceNode.name)), - simplePropertyNode( - BINDING_EVALUATE_KEY, - createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) - ) - ]) -} - -/** - * Generate the pure immutable string chunks from a RiotParser.Node.Text - * @param {RiotParser.Node.Text} node - riot parser text node - * @param {string} sourceCode sourceCode - source code - * @returns {Array} array containing the immutable string chunks - */ -function generateLiteralStringChunksFromNode(node, sourceCode) { - return node.expressions.reduce((chunks, expression, index) => { - const start = index ? node.expressions[index - 1].end : node.start; - - chunks.push(sourceCode.substring(start, expression.start)); - - // add the tail to the string - if (index === node.expressions.length - 1) - chunks.push(sourceCode.substring(expression.end, node.end)); - - return chunks - }, []).map(str => node.unescape ? unescapeChar(str, node.unescape) : str) -} - -/** - * Simple bindings might contain multiple expressions like for example: "{foo} and {bar}" - * This helper aims to merge them in a template literal if it's necessary - * @param {RiotParser.Node} node - riot parser node - * @param {string} sourceFile - original tag file - * @param {string} sourceCode - original tag source code - * @returns { Object } a template literal expression object - */ -function mergeNodeExpressions(node, sourceFile, sourceCode) { - if (node.parts.length === 1) - return transformExpression(node.expressions[0], sourceFile, sourceCode) - - const pureStringChunks = generateLiteralStringChunksFromNode(node, sourceCode); - const stringsArray = pureStringChunks.reduce((acc, str, index) => { - const expr = node.expressions[index]; - - return [ - ...acc, - builders.literal(str), - expr ? transformExpression(expr, sourceFile, sourceCode) : nullNode() - ] - }, []) - // filter the empty literal expressions - .filter(expr => !isLiteral(expr) || expr.value); - - return createArrayString(stringsArray) -} - -/** - * Create a text expression - * @param {RiotParser.Node.Text} sourceNode - text node to parse - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {number} childNodeIndex - position of the child text node in its parent children nodes - * @returns {AST.Node} object containing the expression binding keys - */ -function createTextExpression(sourceNode, sourceFile, sourceCode, childNodeIndex) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(TEXT_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_CHILD_NODE_INDEX_KEY, - builders.literal(childNodeIndex) - ), - simplePropertyNode( - BINDING_EVALUATE_KEY, - wrapASTInFunctionWithScope( - mergeNodeExpressions(sourceNode, sourceFile, sourceCode) - ) - ) - ]) -} - -function createValueExpression(sourceNode, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(VALUE_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_EVALUATE_KEY, - createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) - ) - ]) -} - -function createExpression(sourceNode, sourceFile, sourceCode, childNodeIndex, parentNode) { - switch (true) { - case isTextNode(sourceNode): - return createTextExpression(sourceNode, sourceFile, sourceCode, childNodeIndex) - // progress nodes value attributes will be rendered as attributes - // see https://github.com/riot/compiler/issues/122 - case isValueAttribute(sourceNode) && domNodes.hasValueAttribute(parentNode.name) && !isProgressNode(parentNode): - return createValueExpression(sourceNode, sourceFile, sourceCode) - case isEventAttribute(sourceNode): - return createEventExpression(sourceNode, sourceFile, sourceCode) - default: - return createAttributeExpression(sourceNode, sourceFile, sourceCode) - } -} - -/** - * Create the attribute expressions - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {Array} array containing all the attribute expressions - */ -function createAttributeExpressions(sourceNode, sourceFile, sourceCode) { - return findDynamicAttributes(sourceNode) - .map(attribute => createExpression(attribute, sourceFile, sourceCode, 0, sourceNode)) -} - -/** - * Create the text node expressions - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {Array} array containing all the text node expressions - */ -function createTextNodeExpressions(sourceNode, sourceFile, sourceCode) { - const childrenNodes = getChildrenNodes(sourceNode); - - return childrenNodes - .filter(isTextNode) - .filter(hasExpressions) - .map(node => createExpression( - node, - sourceFile, - sourceCode, - childrenNodes.indexOf(node), - sourceNode - )) -} - -/** - * Add a simple binding to a riot parser node - * @param { RiotParser.Node.Tag } sourceNode - tag containing the if attribute - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } an each binding node - */ -function createSimpleBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - return builders.objectExpression([ - ...createSelectorProperties(selectorAttribute), - simplePropertyNode( - BINDING_EXPRESSIONS_KEY, - builders.arrayExpression([ - ...createTextNodeExpressions(sourceNode, sourceFile, sourceCode), - ...createAttributeExpressions(sourceNode, sourceFile, sourceCode) - ]) - ) - ]) -} - -/** - * Transform a RiotParser.Node.Tag of type slot into a slot binding - * @param { RiotParser.Node.Tag } sourceNode - slot node - * @param { string } selectorAttribute - attribute needed to select the target node - * @returns { AST.Node } a slot binding node - */ -function createSlotBinding(sourceNode, selectorAttribute) { - const slotNameAttribute = findAttribute(NAME_ATTRIBUTE, sourceNode); - const slotName = slotNameAttribute ? slotNameAttribute.value : DEFAULT_SLOT_NAME; - - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(SLOT_BINDING_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_NAME_KEY, - builders.literal(slotName) - ), - ...createSelectorProperties(selectorAttribute) - ]) -} - -/** - * Find the slots in the current component and group them under the same id - * @param {RiotParser.Node.Tag} sourceNode - the custom tag - * @returns {Object} object containing all the slots grouped by name - */ -function groupSlots(sourceNode) { - return getChildrenNodes(sourceNode).reduce((acc, node) => { - const slotAttribute = findSlotAttribute(node); - - if (slotAttribute) { - acc[slotAttribute.value] = node; - } else { - acc.default = createRootNode({ - nodes: [...getChildrenNodes(acc.default), node] - }); - } - - return acc - }, { - default: null - }) -} - -/** - * Create the slot entity to pass to the riot-dom bindings - * @param {string} id - slot id - * @param {RiotParser.Node.Tag} sourceNode - slot root node - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {AST.Node} ast node containing the slot object properties - */ -function buildSlot(id, sourceNode, sourceFile, sourceCode) { - const cloneNode = { - ...sourceNode, - // avoid to render the slot attribute - attributes: getNodeAttributes(sourceNode).filter(attribute => attribute.name !== SLOT_ATTRIBUTE) - }; - const [html, bindings] = build(cloneNode, sourceFile, sourceCode); - - return builders.objectExpression([ - simplePropertyNode(BINDING_ID_KEY, builders.literal(id)), - simplePropertyNode(BINDING_HTML_KEY, builders.literal(html)), - simplePropertyNode(BINDING_BINDINGS_KEY, builders.arrayExpression(bindings)) - ]) -} - -/** - * Create the AST array containing the slots - * @param { RiotParser.Node.Tag } sourceNode - the custom tag - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns {AST.ArrayExpression} array containing the attributes to bind - */ -function createSlotsArray(sourceNode, sourceFile, sourceCode) { - return builders.arrayExpression([ - ...compose( - slots => slots.map(([key, value]) => buildSlot(key, value, sourceFile, sourceCode)), - slots => slots.filter(([,value]) => value), - Object.entries, - groupSlots - )(sourceNode) - ]) -} - -/** - * Create the AST array containing the attributes to bind to this node - * @param { RiotParser.Node.Tag } sourceNode - the custom tag - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns {AST.ArrayExpression} array containing the slot objects - */ -function createBindingAttributes(sourceNode, selectorAttribute, sourceFile, sourceCode) { - return builders.arrayExpression([ - ...compose( - attributes => attributes.map(attribute => createExpression(attribute, sourceFile, sourceCode, 0, sourceNode)), - attributes => getAttributesWithoutSelector(attributes, selectorAttribute), // eslint-disable-line - cleanAttributes - )(sourceNode) - ]) -} - -/** - * Find the slot attribute if it exists - * @param {RiotParser.Node.Tag} sourceNode - the custom tag - * @returns {RiotParser.Node.Attr|undefined} the slot attribute found - */ -function findSlotAttribute(sourceNode) { - return getNodeAttributes(sourceNode).find(attribute => attribute.name === SLOT_ATTRIBUTE) -} - -/** - * Transform a RiotParser.Node.Tag into a tag binding - * @param { RiotParser.Node.Tag } sourceNode - the custom tag - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } tag binding node - */ -function createTagBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(TAG_BINDING_TYPE), - false - ), - ), - simplePropertyNode(BINDING_GET_COMPONENT_KEY, builders.identifier(GET_COMPONENT_FN)), - simplePropertyNode( - BINDING_EVALUATE_KEY, - toScopedFunction(getCustomNodeNameAsExpression(sourceNode), sourceFile, sourceCode) - ), - simplePropertyNode(BINDING_SLOTS_KEY, createSlotsArray(sourceNode, sourceFile, sourceCode)), - simplePropertyNode( - BINDING_ATTRIBUTES_KEY, - createBindingAttributes(sourceNode, selectorAttribute, sourceFile, sourceCode) - ), - ...createSelectorProperties(selectorAttribute) - ]) -} - -const BuildingState = Object.freeze({ - html: [], - bindings: [], - parent: null -}); - -/** - * Nodes having bindings should be cloned and new selector properties should be added to them - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} bindingsSelector - temporary string to identify the current node - * @returns {RiotParser.Node} the original node parsed having the new binding selector attribute - */ -function createBindingsTag(sourceNode, bindingsSelector) { - if (!bindingsSelector) return sourceNode - - return { - ...sourceNode, - // inject the selector bindings into the node attributes - attributes: [{ - name: bindingsSelector, - value: bindingsSelector - }, ...getNodeAttributes(sourceNode)] - } -} - -/** - * Create a generic dynamic node (text or tag) and generate its bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and bindings for the current node - */ -function createDynamicNode(sourceNode, sourceFile, sourceCode, state) { - switch (true) { - case isTextNode(sourceNode): - // text nodes will not have any bindings - return [nodeToString(sourceNode), []] - default: - return createTagWithBindings(sourceNode, sourceFile, sourceCode) - } -} - -/** - * Create only a dynamic tag node with generating a custom selector and its bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and bindings for the current node - */ -function createTagWithBindings(sourceNode, sourceFile, sourceCode) { - const bindingsSelector = isRootNode(sourceNode) ? null : createBindingSelector(); - const cloneNode = createBindingsTag(sourceNode, bindingsSelector); - const tagOpeningHTML = nodeToString(cloneNode); - - switch(true) { - // EACH bindings have prio 1 - case hasEachAttribute(cloneNode): - return [tagOpeningHTML, [createEachBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - // IF bindings have prio 2 - case hasIfAttribute(cloneNode): - return [tagOpeningHTML, [createIfBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - // TAG bindings have prio 3 - case isCustomNode(cloneNode): - return [tagOpeningHTML, [createTagBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - // slot tag - case isSlotNode(cloneNode): - return [tagOpeningHTML, [createSlotBinding(cloneNode, bindingsSelector)]] - // this node has expressions bound to it - default: - return [tagOpeningHTML, [createSimpleBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - } -} - -/** - * Parse a node trying to extract its template and bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and bindings for the current node - */ -function parseNode(sourceNode, sourceFile, sourceCode, state) { - // static nodes have no bindings - if (isStaticNode(sourceNode)) return [nodeToString(sourceNode), []] - return createDynamicNode(sourceNode, sourceFile, sourceCode) -} - -/** - * Create the tag binding - * @param { RiotParser.Node.Tag } sourceNode - tag containing the each attribute - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @param { string } selector - binding selector - * @returns { Array } array with only the tag binding AST - */ -function createNestedBindings(sourceNode, sourceFile, sourceCode, selector) { - const mightBeARiotComponent = isCustomNode(sourceNode); - - return mightBeARiotComponent ? [null, [ - createTagBinding( - cloneNodeWithoutSelectorAttribute(sourceNode, selector), - null, - sourceFile, - sourceCode - )] - ] : build(createRootNode(sourceNode), sourceFile, sourceCode) -} - -/** - * Build the template and the bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and the dom bindings - */ -function build( - sourceNode, - sourceFile, - sourceCode, - state -) { - if (!sourceNode) panic('Something went wrong with your tag DOM parsing, your tag template can\'t be created'); - - const [nodeHTML, nodeBindings] = parseNode(sourceNode, sourceFile, sourceCode); - const childrenNodes = getChildrenNodes(sourceNode); - const currentState = { ...cloneDeep(BuildingState), ...state }; - - // mutate the original arrays - currentState.html.push(...nodeHTML); - currentState.bindings.push(...nodeBindings); - - // do recursion if - // this tag has children and it has no special directives bound to it - if (childrenNodes.length && !hasItsOwnTemplate(sourceNode)) { - childrenNodes.forEach(node => build(node, sourceFile, sourceCode, { parent: sourceNode, ...currentState })); - } - - // close the tag if it's not a void one - if (isTagNode(sourceNode) && !isVoidNode(sourceNode)) { - currentState.html.push(closeTag(sourceNode)); - } - - return [ - currentState.html.join(''), - currentState.bindings - ] -} - -const templateFunctionArguments = [ - TEMPLATE_FN, - EXPRESSION_TYPES, - BINDING_TYPES, - GET_COMPONENT_FN -].map(builders.identifier); - -/** - * Create the content of the template function - * @param { RiotParser.Node } sourceNode - node generated by the riot compiler - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns {AST.BlockStatement} the content of the template function - */ -function createTemplateFunctionContent(sourceNode, sourceFile, sourceCode) { - return builders.blockStatement([ - builders.returnStatement( - callTemplateFunction( - ...build( - createRootNode(sourceNode), - sourceFile, - sourceCode - ) - ) - ) - ]) -} - -/** - * Extend the AST adding the new template property containing our template call to render the component - * @param { Object } ast - current output ast - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @param { RiotParser.Node } sourceNode - node generated by the riot compiler - * @returns { Object } the output ast having the "template" key - */ -function extendTemplateProperty(ast, sourceFile, sourceCode, sourceNode) { - types.visit(ast, { - visitProperty(path) { - if (path.value.key.value === TAG_TEMPLATE_PROPERTY) { - path.value.value = builders.functionExpression( - null, - templateFunctionArguments, - createTemplateFunctionContent(sourceNode, sourceFile, sourceCode) - ); - - return false - } - - this.traverse(path); - } - }); - - return ast -} - -/** - * Generate the component template logic - * @param { RiotParser.Node } sourceNode - node generated by the riot compiler - * @param { string } source - original component source code - * @param { Object } meta - compilation meta information - * @param { AST } ast - current AST output - * @returns { AST } the AST generated - */ -function template(sourceNode, source, meta, ast) { - const { options } = meta; - return extendTemplateProperty(ast, options.file, source, sourceNode) -} - -const DEFAULT_OPTIONS = { - template: 'default', - file: '[unknown-source-file]', - scopedCss: true -}; - -/** - * Create the initial AST - * @param {string} tagName - the name of the component we have compiled - * @returns { AST } the initial AST - * - * @example - * // the output represents the following string in AST - */ -function createInitialInput({tagName}) { - /* - generates - export default { - ${TAG_CSS_PROPERTY}: null, - ${TAG_LOGIC_PROPERTY}: null, - ${TAG_TEMPLATE_PROPERTY}: null - } - */ - return builders.program([ - builders.exportDefaultDeclaration( - builders.objectExpression([ - simplePropertyNode(TAG_CSS_PROPERTY, nullNode()), - simplePropertyNode(TAG_LOGIC_PROPERTY, nullNode()), - simplePropertyNode(TAG_TEMPLATE_PROPERTY, nullNode()), - simplePropertyNode(TAG_NAME_PROPERTY, builders.literal(tagName)) - ]) - )] - ) -} - -/** - * Make sure the input sourcemap is valid otherwise we ignore it - * @param {SourceMapGenerator} map - preprocessor source map - * @returns {Object} sourcemap as json or nothing - */ -function normaliseInputSourceMap(map) { - const inputSourceMap = sourcemapAsJSON(map); - return isEmptySourcemap(inputSourceMap) ? null : inputSourceMap -} - -/** - * Override the sourcemap content making sure it will always contain the tag source code - * @param {Object} map - sourcemap as json - * @param {string} source - component source code - * @returns {Object} original source map with the "sourcesContent" property overriden - */ -function overrideSourcemapContent(map, source) { - return { - ...map, - sourcesContent: [source] - } -} - -/** - * Create the compilation meta object - * @param { string } source - source code of the tag we will need to compile - * @param { string } options - compiling options - * @returns {Object} meta object - */ -function createMeta(source, options) { - return { - tagName: null, - fragments: null, - options: { - ...DEFAULT_OPTIONS, - ...options - }, - source - } -} - -/** - * Generate the output code source together with the sourcemap - * @param { string } source - source code of the tag we will need to compile - * @param { string } opts - compiling options - * @returns { Output } object containing output code and source map - */ -function compile(source, opts = {}) { - const meta = createMeta(source, opts); - const {options} = meta; - const { code, map } = execute$1('template', options.template, meta, source); - const { template: template$1, css: css$1, javascript: javascript$1 } = riotParser__default(options).parse(code).output; - - // extend the meta object with the result of the parsing - Object.assign(meta, { - tagName: template$1.name, - fragments: { template: template$1, css: css$1, javascript: javascript$1 } - }); - - return compose( - result => ({ ...result, meta }), - result => execute(result, meta), - result => ({ - ...result, - map: overrideSourcemapContent(result.map, source) - }), - ast => meta.ast = ast && generateJavascript(ast, { - sourceMapName: `${options.file}.map`, - inputSourceMap: normaliseInputSourceMap(map) - }), - hookGenerator(template, template$1, code, meta), - hookGenerator(javascript, javascript$1, code, meta), - hookGenerator(css, css$1, code, meta), - )(createInitialInput(meta)) -} - -/** - * Prepare the riot parser node transformers - * @param { Function } transformer - transformer function - * @param { Object } sourceNode - riot parser node - * @param { string } source - component source code - * @param { Object } meta - compilation meta information - * @returns { Promise } object containing output code and source map - */ -function hookGenerator(transformer, sourceNode, source, meta) { - if ( - // filter missing nodes - !sourceNode || - // filter nodes without children - (sourceNode.nodes && !sourceNode.nodes.length) || - // filter empty javascript and css nodes - (!sourceNode.nodes && !sourceNode.text)) { - return result => result - } - - return curry(transformer)(sourceNode, source, meta) -} - -// This function can be used to register new preprocessors -// a preprocessor can target either only the css or javascript nodes -// or the complete tag source file ('template') -const registerPreprocessor = register$1; - -// This function can allow you to register postprocessors that will parse the output code -// here we can run prettifiers, eslint fixes... -const registerPostprocessor = register; - -exports.compile = compile; -exports.createInitialInput = createInitialInput; -exports.registerPostprocessor = registerPostprocessor; -exports.registerPreprocessor = registerPreprocessor; diff --git a/node_modules/@riotjs/compiler/package.json b/node_modules/@riotjs/compiler/package.json deleted file mode 100644 index 6fbe992..0000000 --- a/node_modules/@riotjs/compiler/package.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "_from": "@riotjs/compiler@^4.3.11", - "_id": "@riotjs/compiler@4.3.11", - "_inBundle": false, - "_integrity": "sha512-3TpOuoiXWSLGvcvZRfhJLdpRpwJihmT+J+NB2nWXA5/8+23x1soUoPmBtRi9Jo2xLInsh3J1/q5tQ8LjXLc2eQ==", - "_location": "/@riotjs/compiler", - "_phantomChildren": {}, - "_requested": { - "type": "range", - "registry": true, - "raw": "@riotjs/compiler@^4.3.11", - "name": "@riotjs/compiler", - "escapedName": "@riotjs%2fcompiler", - "scope": "@riotjs", - "rawSpec": "^4.3.11", - "saveSpec": null, - "fetchSpec": "^4.3.11" - }, - "_requiredBy": [ - "/riot" - ], - "_resolved": "https://registry.npmjs.org/@riotjs/compiler/-/compiler-4.3.11.tgz", - "_shasum": "b068216c19092d524dc0a7fbce1572a23830607b", - "_spec": "@riotjs/compiler@^4.3.11", - "_where": "/home/herrhase/Workspace/tentakelfabrik/tiny-components/tiny-one-page/node_modules/riot", - "author": { - "name": "Gianluca Guarini", - "email": "gianluca.guarini@gmail.com", - "url": "http://gianlucaguarini.com" - }, - "bugs": { - "url": "https://github.com/riot/compiler/issues" - }, - "bundleDependencies": false, - "dependencies": { - "@riotjs/dom-bindings": "^4.2.5", - "@riotjs/parser": "^4.0.3", - "acorn": "^7.0.0", - "cssesc": "^3.0.0", - "cumpa": "^1.0.1", - "curri": "^1.0.1", - "dom-nodes": "^1.1.3", - "globals": "^12.0.0", - "recast": "^0.18.2", - "source-map": "^0.7.3" - }, - "deprecated": false, - "description": "Compiler for riot .tag files", - "devDependencies": { - "chai": "^4.2.0", - "coveralls": "^3.0.6", - "eslint": "^6.2.1", - "eslint-config-riot": "^3.0.0", - "esm": "^3.2.25", - "mocha": "^6.2.0", - "nyc": "^14.1.1", - "rollup": "^1.20.1", - "rollup-plugin-alias": "^2.0.0", - "rollup-plugin-commonjs": "^10.0.2", - "rollup-plugin-json": "^4.0.0", - "rollup-plugin-node-builtins": "^2.1.2", - "rollup-plugin-node-resolve": "^5.2.0", - "shelljs": "^0.8.3" - }, - "files": [ - "dist", - "src" - ], - "homepage": "https://github.com/riot/compiler#readme", - "jsnext:main": "dist/index.esm.js", - "keywords": [ - "riot", - "Riot.js", - "components", - "custom components", - "custom elements", - "compiler" - ], - "license": "MIT", - "main": "dist/index.js", - "module": "dist/index.esm.js", - "name": "@riotjs/compiler", - "repository": { - "type": "git", - "url": "git+https://github.com/riot/compiler.git" - }, - "scripts": { - "build": "rollup -c build/rollup.node.config.js && rollup -c build/rollup.browser.config.js", - "cov": "nyc report --reporter=text-lcov | coveralls", - "cov-html": "nyc report --reporter=html", - "debug": "mocha --inspect --inspect-brk -r esm test/*.spec.js test/**/*.spec.js", - "lint": "eslint src/ test/ build/", - "postest": "npm run cov-html", - "prepare": "npm i pug@2.0.3 node-sass@4.12.0 @babel/core@7 @babel/preset-env@7 --no-save", - "prepublishOnly": "npm run build && npm run test", - "test": "npm run lint && nyc mocha -r esm test/*.spec.js test/**/*.spec.js" - }, - "version": "4.3.11" -} diff --git a/node_modules/@riotjs/compiler/src/.DS_Store b/node_modules/@riotjs/compiler/src/.DS_Store deleted file mode 100644 index 57c470d..0000000 Binary files a/node_modules/@riotjs/compiler/src/.DS_Store and /dev/null differ diff --git a/node_modules/@riotjs/compiler/src/constants.js b/node_modules/@riotjs/compiler/src/constants.js deleted file mode 100644 index 0e5b934..0000000 --- a/node_modules/@riotjs/compiler/src/constants.js +++ /dev/null @@ -1,4 +0,0 @@ -export const TAG_LOGIC_PROPERTY = 'exports' -export const TAG_CSS_PROPERTY = 'css' -export const TAG_TEMPLATE_PROPERTY = 'template' -export const TAG_NAME_PROPERTY = 'name' \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/generators/.DS_Store b/node_modules/@riotjs/compiler/src/generators/.DS_Store deleted file mode 100644 index eb03002..0000000 Binary files a/node_modules/@riotjs/compiler/src/generators/.DS_Store and /dev/null differ diff --git a/node_modules/@riotjs/compiler/src/generators/css/index.js b/node_modules/@riotjs/compiler/src/generators/css/index.js deleted file mode 100644 index 77a52ab..0000000 --- a/node_modules/@riotjs/compiler/src/generators/css/index.js +++ /dev/null @@ -1,124 +0,0 @@ -import {builders, types} from '../../utils/build-types' -import {TAG_CSS_PROPERTY} from '../../constants' -import cssEscape from 'cssesc' -import getPreprocessorTypeByAttribute from '../../utils/get-preprocessor-type-by-attribute' -import preprocess from '../../utils/preprocess-node' - -/** - * Matches valid, multiline JavaScript comments in almost all its forms. - * @const {RegExp} - * @static - */ -const R_MLCOMMS = /\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//g - -/** - * Source for creating regexes matching valid quoted, single-line JavaScript strings. - * It recognizes escape characters, including nested quotes and line continuation. - * @const {string} - */ -const S_LINESTR = /"[^"\n\\]*(?:\\[\S\s][^"\n\\]*)*"|'[^'\n\\]*(?:\\[\S\s][^'\n\\]*)*'/.source - -/** - * Matches CSS selectors, excluding those beginning with '@' and quoted strings. - * @const {RegExp} - */ - -const CSS_SELECTOR = RegExp(`([{}]|^)[; ]*((?:[^@ ;{}][^{}]*)?[^@ ;{}:] ?)(?={)|${S_LINESTR}`, 'g') - -/** - * Parses styles enclosed in a "scoped" tag - * The "css" string is received without comments or surrounding spaces. - * - * @param {string} tag - Tag name of the root element - * @param {string} css - The CSS code - * @returns {string} CSS with the styles scoped to the root element - */ -function scopedCSS(tag, css) { - const host = ':host' - const selectorsBlacklist = ['from', 'to'] - - return css.replace(CSS_SELECTOR, function(m, p1, p2) { - // skip quoted strings - if (!p2) return m - - // we have a selector list, parse each individually - p2 = p2.replace(/[^,]+/g, function(sel) { - const s = sel.trim() - - // skip selectors already using the tag name - if (s.indexOf(tag) === 0) { - return sel - } - - // skips the keywords and percents of css animations - if (!s || selectorsBlacklist.indexOf(s) > -1 || s.slice(-1) === '%') { - return sel - } - - // replace the `:host` pseudo-selector, where it is, with the root tag name; - // if `:host` was not included, add the tag name as prefix, and mirror all - // `[data-is]` - if (s.indexOf(host) < 0) { - return `${tag} ${s},[is="${tag}"] ${s}` - } else { - return `${s.replace(host, tag)},${ - s.replace(host, `[is="${tag}"]`)}` - } - }) - - // add the danling bracket char and return the processed selector list - return p1 ? `${p1} ${p2}` : p2 - }) -} - -/** - * Remove comments, compact and trim whitespace - * @param { string } code - compiled css code - * @returns { string } css code normalized - */ -function compactCss(code) { - return code.replace(R_MLCOMMS, '').replace(/\s+/g, ' ').trim() -} - -const escapeBackslashes = s => s.replace(/\\/g, '\\\\') -const escapeIdentifier = identifier => escapeBackslashes(cssEscape(identifier, { - isIdentifier: true -})) - -/** - * Generate the component css - * @param { Object } sourceNode - node generated by the riot compiler - * @param { string } source - original component source code - * @param { Object } meta - compilation meta information - * @param { AST } ast - current AST output - * @returns { AST } the AST generated - */ -export default function css(sourceNode, source, meta, ast) { - const preprocessorName = getPreprocessorTypeByAttribute(sourceNode) - const { options } = meta - const preprocessorOutput = preprocess('css', preprocessorName, meta, sourceNode.text) - const normalizedCssCode = compactCss(preprocessorOutput.code) - const escapedCssIdentifier = escapeIdentifier(meta.tagName) - - const cssCode = (options.scopedCss ? - scopedCSS(escapedCssIdentifier, escapeBackslashes(normalizedCssCode)) : - escapeBackslashes(normalizedCssCode) - ).trim() - - types.visit(ast, { - visitProperty(path) { - if (path.value.key.value === TAG_CSS_PROPERTY) { - path.value.value = builders.templateLiteral( - [builders.templateElement({ raw: cssCode, cooked: '' }, false)], - [] - ) - - return false - } - - this.traverse(path) - } - }) - - return ast -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/generators/javascript/index.js b/node_modules/@riotjs/compiler/src/generators/javascript/index.js deleted file mode 100644 index 49e4e54..0000000 --- a/node_modules/@riotjs/compiler/src/generators/javascript/index.js +++ /dev/null @@ -1,92 +0,0 @@ -import {TAG_LOGIC_PROPERTY} from '../../constants' -import addLinesOffset from '../../utils/add-lines-offset' -import generateAST from '../../utils/generate-ast' -import getPreprocessorTypeByAttribute from '../../utils/get-preprocessor-type-by-attribute' -import isEmptySourcemap from '../../utils/is-empty-sourcemap' -import {isExportDefaultStatement} from '../../utils/ast-nodes-checks' -import preprocess from '../../utils/preprocess-node' -import sourcemapToJSON from '../../utils/sourcemap-as-json' -import {types} from '../../utils/build-types' - -/** - * Find the export default statement - * @param { Array } body - tree structure containing the program code - * @returns { Object } node containing only the code of the export default statement - */ -function findExportDefaultStatement(body) { - return body.find(isExportDefaultStatement) -} - -/** - * Find all the code in an ast program except for the export default statements - * @param { Array } body - tree structure containing the program code - * @returns { Array } array containing all the program code except the export default expressions - */ -function filterNonExportDefaultStatements(body) { - return body.filter(node => !isExportDefaultStatement(node)) -} - -/** - * Get the body of the AST structure - * @param { Object } ast - ast object generated by recast - * @returns { Array } array containing the program code - */ -function getProgramBody(ast) { - return ast.body || ast.program.body -} - -/** - * Extend the AST adding the new tag method containing our tag sourcecode - * @param { Object } ast - current output ast - * @param { Object } exportDefaultNode - tag export default node - * @returns { Object } the output ast having the "tag" key extended with the content of the export default - */ -function extendTagProperty(ast, exportDefaultNode) { - types.visit(ast, { - visitProperty(path) { - if (path.value.key.value === TAG_LOGIC_PROPERTY) { - path.value.value = exportDefaultNode.declaration - return false - } - - this.traverse(path) - } - }) - - return ast -} - -/** - * Generate the component javascript logic - * @param { Object } sourceNode - node generated by the riot compiler - * @param { string } source - original component source code - * @param { Object } meta - compilation meta information - * @param { AST } ast - current AST output - * @returns { AST } the AST generated - */ -export default function javascript(sourceNode, source, meta, ast) { - const preprocessorName = getPreprocessorTypeByAttribute(sourceNode) - const javascriptNode = addLinesOffset(sourceNode.text.text, source, sourceNode) - const { options } = meta - const preprocessorOutput = preprocess('javascript', preprocessorName, meta, { - ...sourceNode, - text: javascriptNode - }) - const inputSourceMap = sourcemapToJSON(preprocessorOutput.map) - const generatedAst = generateAST(preprocessorOutput.code, { - sourceFileName: options.file, - inputSourceMap: isEmptySourcemap(inputSourceMap) ? null : inputSourceMap - }) - const generatedAstBody = getProgramBody(generatedAst) - const bodyWithoutExportDefault = filterNonExportDefaultStatements(generatedAstBody) - const exportDefaultNode = findExportDefaultStatement(generatedAstBody) - const outputBody = getProgramBody(ast) - - // add to the ast the "private" javascript content of our tag script node - outputBody.unshift(...bodyWithoutExportDefault) - - // convert the export default adding its content to the "tag" property exported - if (exportDefaultNode) extendTagProperty(ast, exportDefaultNode) - - return ast -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/generators/template/bindings/each.js b/node_modules/@riotjs/compiler/src/generators/template/bindings/each.js deleted file mode 100644 index 240afd1..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/bindings/each.js +++ /dev/null @@ -1,110 +0,0 @@ -import { - BINDING_CONDITION_KEY, - BINDING_EVALUATE_KEY, - BINDING_GET_KEY_KEY, - BINDING_INDEX_NAME_KEY, - BINDING_ITEM_NAME_KEY, - BINDING_TYPES, - BINDING_TYPE_KEY, - EACH_BINDING_TYPE -} from '../constants' -import { - createASTFromExpression, - createSelectorProperties, - createTemplateProperty, - getAttributeExpression, - getName, - toScopedFunction -} from '../utils' -import { findEachAttribute, findIfAttribute, findKeyAttribute } from '../find' -import {isExpressionStatement, isSequenceExpression} from '../../../utils/ast-nodes-checks' -import {nullNode, simplePropertyNode} from '../../../utils/custom-ast-nodes' -import {builders} from '../../../utils/build-types' -import compose from 'cumpa' -import {createNestedBindings} from '../builder' -import generateJavascript from '../../../utils/generate-javascript' -import panic from '../../../utils/panic' - -const getEachItemName = expression => isSequenceExpression(expression.left) ? expression.left.expressions[0] : expression.left -const getEachIndexName = expression => isSequenceExpression(expression.left) ? expression.left.expressions[1] : null -const getEachValue = expression => expression.right -const nameToliteral = compose(builders.literal, getName) - -const generateEachItemNameKey = expression => simplePropertyNode( - BINDING_ITEM_NAME_KEY, - compose(nameToliteral, getEachItemName)(expression) -) - -const generateEachIndexNameKey = expression => simplePropertyNode( - BINDING_INDEX_NAME_KEY, - compose(nameToliteral, getEachIndexName)(expression) -) - -const generateEachEvaluateKey = (expression, eachExpression, sourceFile, sourceCode) => simplePropertyNode( - BINDING_EVALUATE_KEY, - compose( - e => toScopedFunction(e, sourceFile, sourceCode), - e => ({ - ...eachExpression, - text: generateJavascript(e).code - }), - getEachValue - )(expression) -) - -/** - * Get the each expression properties to create properly the template binding - * @param { DomBinding.Expression } eachExpression - original each expression data - * @param { string } sourceFile - original tag file - * @param { string } sourceCode - original tag source code - * @returns { Array } AST nodes that are needed to build an each binding - */ -export function generateEachExpressionProperties(eachExpression, sourceFile, sourceCode) { - const ast = createASTFromExpression(eachExpression, sourceFile, sourceCode) - const body = ast.program.body - const firstNode = body[0] - - if (!isExpressionStatement(firstNode)) { - panic(`The each directives supported should be of type "ExpressionStatement",you have provided a "${firstNode.type}"`) - } - - const { expression } = firstNode - - return [ - generateEachItemNameKey(expression), - generateEachIndexNameKey(expression), - generateEachEvaluateKey(expression, eachExpression, sourceFile, sourceCode) - ] -} - -/** - * Transform a RiotParser.Node.Tag into an each binding - * @param { RiotParser.Node.Tag } sourceNode - tag containing the each attribute - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } an each binding node - */ -export default function createEachBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - const [ifAttribute, eachAttribute, keyAttribute] = [ - findIfAttribute, - findEachAttribute, - findKeyAttribute - ].map(f => f(sourceNode)) - const attributeOrNull = attribute => attribute ? toScopedFunction(getAttributeExpression(attribute), sourceFile, sourceCode) : nullNode() - - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(EACH_BINDING_TYPE), - false - ), - ), - simplePropertyNode(BINDING_GET_KEY_KEY, attributeOrNull(keyAttribute)), - simplePropertyNode(BINDING_CONDITION_KEY, attributeOrNull(ifAttribute)), - createTemplateProperty(createNestedBindings(sourceNode, sourceFile, sourceCode, selectorAttribute)), - ...createSelectorProperties(selectorAttribute), - ...compose(generateEachExpressionProperties, getAttributeExpression)(eachAttribute) - ]) -} diff --git a/node_modules/@riotjs/compiler/src/generators/template/bindings/if.js b/node_modules/@riotjs/compiler/src/generators/template/bindings/if.js deleted file mode 100644 index 58b5cf1..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/bindings/if.js +++ /dev/null @@ -1,43 +0,0 @@ -import { - BINDING_EVALUATE_KEY, - BINDING_TYPES, - BINDING_TYPE_KEY, - IF_BINDING_TYPE -} from '../constants' -import { - createSelectorProperties, - createTemplateProperty, - toScopedFunction -} from '../utils' -import {builders} from '../../../utils/build-types' -import {createNestedBindings} from '../builder' -import {findIfAttribute} from '../find' -import {simplePropertyNode} from '../../../utils/custom-ast-nodes' - -/** - * Transform a RiotParser.Node.Tag into an if binding - * @param { RiotParser.Node.Tag } sourceNode - tag containing the if attribute - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { stiring } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } an if binding node - */ -export default function createIfBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - const ifAttribute = findIfAttribute(sourceNode) - - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(IF_BINDING_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_EVALUATE_KEY, - toScopedFunction(ifAttribute.expressions[0], sourceFile, sourceCode) - ), - ...createSelectorProperties(selectorAttribute), - createTemplateProperty(createNestedBindings(sourceNode, sourceFile, sourceCode, selectorAttribute)) - ]) -} diff --git a/node_modules/@riotjs/compiler/src/generators/template/bindings/simple.js b/node_modules/@riotjs/compiler/src/generators/template/bindings/simple.js deleted file mode 100644 index b9bf99a..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/bindings/simple.js +++ /dev/null @@ -1,52 +0,0 @@ -import {createAttributeExpressions, createExpression} from '../expressions/index' -import { - createSelectorProperties, - getChildrenNodes -} from '../utils' -import { hasExpressions, isTextNode } from '../checks' -import {BINDING_EXPRESSIONS_KEY} from '../constants' -import {builders} from '../../../utils/build-types' -import {simplePropertyNode} from '../../../utils/custom-ast-nodes' - -/** - * Create the text node expressions - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {Array} array containing all the text node expressions - */ -function createTextNodeExpressions(sourceNode, sourceFile, sourceCode) { - const childrenNodes = getChildrenNodes(sourceNode) - - return childrenNodes - .filter(isTextNode) - .filter(hasExpressions) - .map(node => createExpression( - node, - sourceFile, - sourceCode, - childrenNodes.indexOf(node), - sourceNode - )) -} - -/** - * Add a simple binding to a riot parser node - * @param { RiotParser.Node.Tag } sourceNode - tag containing the if attribute - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } an each binding node - */ -export default function createSimpleBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - return builders.objectExpression([ - ...createSelectorProperties(selectorAttribute), - simplePropertyNode( - BINDING_EXPRESSIONS_KEY, - builders.arrayExpression([ - ...createTextNodeExpressions(sourceNode, sourceFile, sourceCode), - ...createAttributeExpressions(sourceNode, sourceFile, sourceCode) - ]) - ) - ]) -} diff --git a/node_modules/@riotjs/compiler/src/generators/template/bindings/slot.js b/node_modules/@riotjs/compiler/src/generators/template/bindings/slot.js deleted file mode 100644 index 7198c31..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/bindings/slot.js +++ /dev/null @@ -1,38 +0,0 @@ -import { - BINDING_NAME_KEY, - BINDING_TYPES, - BINDING_TYPE_KEY, - DEFAULT_SLOT_NAME, - NAME_ATTRIBUTE, - SLOT_BINDING_TYPE -} from '../constants' -import {builders} from '../../../utils/build-types' -import {createSelectorProperties} from '../utils' -import {findAttribute} from '../find' -import {simplePropertyNode} from '../../../utils/custom-ast-nodes' - -/** - * Transform a RiotParser.Node.Tag of type slot into a slot binding - * @param { RiotParser.Node.Tag } sourceNode - slot node - * @param { string } selectorAttribute - attribute needed to select the target node - * @returns { AST.Node } a slot binding node - */ -export default function createSlotBinding(sourceNode, selectorAttribute) { - const slotNameAttribute = findAttribute(NAME_ATTRIBUTE, sourceNode) - const slotName = slotNameAttribute ? slotNameAttribute.value : DEFAULT_SLOT_NAME - - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(SLOT_BINDING_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_NAME_KEY, - builders.literal(slotName) - ), - ...createSelectorProperties(selectorAttribute) - ]) -} diff --git a/node_modules/@riotjs/compiler/src/generators/template/bindings/tag.js b/node_modules/@riotjs/compiler/src/generators/template/bindings/tag.js deleted file mode 100644 index bab3524..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/bindings/tag.js +++ /dev/null @@ -1,151 +0,0 @@ -import { - BINDING_ATTRIBUTES_KEY, - BINDING_BINDINGS_KEY, - BINDING_EVALUATE_KEY, - BINDING_GET_COMPONENT_KEY, - BINDING_HTML_KEY, - BINDING_ID_KEY, - BINDING_SLOTS_KEY, - BINDING_TYPES, - BINDING_TYPE_KEY, - GET_COMPONENT_FN, - SLOT_ATTRIBUTE, - TAG_BINDING_TYPE -} from '../constants' -import { - cleanAttributes, - createRootNode, - createSelectorProperties, - getAttributesWithoutSelector, - getChildrenNodes, - getCustomNodeNameAsExpression, - getNodeAttributes, - toScopedFunction -} from '../utils' -import build from '../builder' -import {builders} from '../../../utils/build-types' -import compose from 'cumpa' -import {createExpression} from '../expressions/index' -import {simplePropertyNode} from '../../../utils/custom-ast-nodes' - -/** - * Find the slots in the current component and group them under the same id - * @param {RiotParser.Node.Tag} sourceNode - the custom tag - * @returns {Object} object containing all the slots grouped by name - */ -function groupSlots(sourceNode) { - return getChildrenNodes(sourceNode).reduce((acc, node) => { - const slotAttribute = findSlotAttribute(node) - - if (slotAttribute) { - acc[slotAttribute.value] = node - } else { - acc.default = createRootNode({ - nodes: [...getChildrenNodes(acc.default), node] - }) - } - - return acc - }, { - default: null - }) -} - -/** - * Create the slot entity to pass to the riot-dom bindings - * @param {string} id - slot id - * @param {RiotParser.Node.Tag} sourceNode - slot root node - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {AST.Node} ast node containing the slot object properties - */ -function buildSlot(id, sourceNode, sourceFile, sourceCode) { - const cloneNode = { - ...sourceNode, - // avoid to render the slot attribute - attributes: getNodeAttributes(sourceNode).filter(attribute => attribute.name !== SLOT_ATTRIBUTE) - } - const [html, bindings] = build(cloneNode, sourceFile, sourceCode) - - return builders.objectExpression([ - simplePropertyNode(BINDING_ID_KEY, builders.literal(id)), - simplePropertyNode(BINDING_HTML_KEY, builders.literal(html)), - simplePropertyNode(BINDING_BINDINGS_KEY, builders.arrayExpression(bindings)) - ]) -} - -/** - * Create the AST array containing the slots - * @param { RiotParser.Node.Tag } sourceNode - the custom tag - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns {AST.ArrayExpression} array containing the attributes to bind - */ -function createSlotsArray(sourceNode, sourceFile, sourceCode) { - return builders.arrayExpression([ - ...compose( - slots => slots.map(([key, value]) => buildSlot(key, value, sourceFile, sourceCode)), - slots => slots.filter(([,value]) => value), - Object.entries, - groupSlots - )(sourceNode) - ]) -} - -/** - * Create the AST array containing the attributes to bind to this node - * @param { RiotParser.Node.Tag } sourceNode - the custom tag - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns {AST.ArrayExpression} array containing the slot objects - */ -function createBindingAttributes(sourceNode, selectorAttribute, sourceFile, sourceCode) { - return builders.arrayExpression([ - ...compose( - attributes => attributes.map(attribute => createExpression(attribute, sourceFile, sourceCode, 0, sourceNode)), - attributes => getAttributesWithoutSelector(attributes, selectorAttribute), // eslint-disable-line - cleanAttributes - )(sourceNode) - ]) -} - -/** - * Find the slot attribute if it exists - * @param {RiotParser.Node.Tag} sourceNode - the custom tag - * @returns {RiotParser.Node.Attr|undefined} the slot attribute found - */ -function findSlotAttribute(sourceNode) { - return getNodeAttributes(sourceNode).find(attribute => attribute.name === SLOT_ATTRIBUTE) -} - -/** - * Transform a RiotParser.Node.Tag into a tag binding - * @param { RiotParser.Node.Tag } sourceNode - the custom tag - * @param { string } selectorAttribute - attribute needed to select the target node - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns { AST.Node } tag binding node - */ -export default function createTagBinding(sourceNode, selectorAttribute, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(BINDING_TYPES), - builders.identifier(TAG_BINDING_TYPE), - false - ), - ), - simplePropertyNode(BINDING_GET_COMPONENT_KEY, builders.identifier(GET_COMPONENT_FN)), - simplePropertyNode( - BINDING_EVALUATE_KEY, - toScopedFunction(getCustomNodeNameAsExpression(sourceNode), sourceFile, sourceCode) - ), - simplePropertyNode(BINDING_SLOTS_KEY, createSlotsArray(sourceNode, sourceFile, sourceCode)), - simplePropertyNode( - BINDING_ATTRIBUTES_KEY, - createBindingAttributes(sourceNode, selectorAttribute, sourceFile, sourceCode) - ), - ...createSelectorProperties(selectorAttribute) - ]) -} diff --git a/node_modules/@riotjs/compiler/src/generators/template/builder.js b/node_modules/@riotjs/compiler/src/generators/template/builder.js deleted file mode 100644 index deb660d..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/builder.js +++ /dev/null @@ -1,178 +0,0 @@ -import { - cloneNodeWithoutSelectorAttribute, - closeTag, createBindingSelector, - createRootNode, - getChildrenNodes, - getNodeAttributes, - nodeToString -} from './utils' -import { - hasEachAttribute, hasIfAttribute, - hasItsOwnTemplate, - isCustomNode, - isRootNode, - isSlotNode, - isStaticNode, - isTagNode, - isTextNode, - isVoidNode -} from './checks' -import cloneDeep from '../../utils/clone-deep' -import eachBinding from './bindings/each' -import ifBinding from './bindings/if' -import panic from '../../utils/panic' -import simpleBinding from './bindings/simple' -import slotBinding from './bindings/slot' -import tagBinding from './bindings/tag' - - -const BuildingState = Object.freeze({ - html: [], - bindings: [], - parent: null -}) - -/** - * Nodes having bindings should be cloned and new selector properties should be added to them - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} bindingsSelector - temporary string to identify the current node - * @returns {RiotParser.Node} the original node parsed having the new binding selector attribute - */ -function createBindingsTag(sourceNode, bindingsSelector) { - if (!bindingsSelector) return sourceNode - - return { - ...sourceNode, - // inject the selector bindings into the node attributes - attributes: [{ - name: bindingsSelector, - value: bindingsSelector - }, ...getNodeAttributes(sourceNode)] - } -} - -/** - * Create a generic dynamic node (text or tag) and generate its bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and bindings for the current node - */ -function createDynamicNode(sourceNode, sourceFile, sourceCode, state) { - switch (true) { - case isTextNode(sourceNode): - // text nodes will not have any bindings - return [nodeToString(sourceNode), []] - default: - return createTagWithBindings(sourceNode, sourceFile, sourceCode, state) - } -} - -/** - * Create only a dynamic tag node with generating a custom selector and its bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and bindings for the current node - */ -function createTagWithBindings(sourceNode, sourceFile, sourceCode) { - const bindingsSelector = isRootNode(sourceNode) ? null : createBindingSelector() - const cloneNode = createBindingsTag(sourceNode, bindingsSelector) - const tagOpeningHTML = nodeToString(cloneNode) - - switch(true) { - // EACH bindings have prio 1 - case hasEachAttribute(cloneNode): - return [tagOpeningHTML, [eachBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - // IF bindings have prio 2 - case hasIfAttribute(cloneNode): - return [tagOpeningHTML, [ifBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - // TAG bindings have prio 3 - case isCustomNode(cloneNode): - return [tagOpeningHTML, [tagBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - // slot tag - case isSlotNode(cloneNode): - return [tagOpeningHTML, [slotBinding(cloneNode, bindingsSelector)]] - // this node has expressions bound to it - default: - return [tagOpeningHTML, [simpleBinding(cloneNode, bindingsSelector, sourceFile, sourceCode)]] - } -} - -/** - * Parse a node trying to extract its template and bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and bindings for the current node - */ -function parseNode(sourceNode, sourceFile, sourceCode, state) { - // static nodes have no bindings - if (isStaticNode(sourceNode)) return [nodeToString(sourceNode), []] - return createDynamicNode(sourceNode, sourceFile, sourceCode, state) -} - -/** - * Create the tag binding - * @param { RiotParser.Node.Tag } sourceNode - tag containing the each attribute - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @param { string } selector - binding selector - * @returns { Array } array with only the tag binding AST - */ -export function createNestedBindings(sourceNode, sourceFile, sourceCode, selector) { - const mightBeARiotComponent = isCustomNode(sourceNode) - - return mightBeARiotComponent ? [null, [ - tagBinding( - cloneNodeWithoutSelectorAttribute(sourceNode, selector), - null, - sourceFile, - sourceCode - )] - ] : build(createRootNode(sourceNode), sourceFile, sourceCode) -} - -/** - * Build the template and the bindings - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {BuildingState} state - state representing the current building tree state during the recursion - * @returns {Array} array containing the html output and the dom bindings - */ -export default function build( - sourceNode, - sourceFile, - sourceCode, - state -) { - if (!sourceNode) panic('Something went wrong with your tag DOM parsing, your tag template can\'t be created') - - const [nodeHTML, nodeBindings] = parseNode(sourceNode, sourceFile, sourceCode, state) - const childrenNodes = getChildrenNodes(sourceNode) - const currentState = { ...cloneDeep(BuildingState), ...state } - - // mutate the original arrays - currentState.html.push(...nodeHTML) - currentState.bindings.push(...nodeBindings) - - // do recursion if - // this tag has children and it has no special directives bound to it - if (childrenNodes.length && !hasItsOwnTemplate(sourceNode)) { - childrenNodes.forEach(node => build(node, sourceFile, sourceCode, { parent: sourceNode, ...currentState })) - } - - // close the tag if it's not a void one - if (isTagNode(sourceNode) && !isVoidNode(sourceNode)) { - currentState.html.push(closeTag(sourceNode)) - } - - return [ - currentState.html.join(''), - currentState.bindings - ] -} diff --git a/node_modules/@riotjs/compiler/src/generators/template/checks.js b/node_modules/@riotjs/compiler/src/generators/template/checks.js deleted file mode 100644 index 18f2c44..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/checks.js +++ /dev/null @@ -1,194 +0,0 @@ -import { - IS_CUSTOM_NODE, - IS_SPREAD_ATTRIBUTE, - IS_VOID_NODE, - PROGRESS_TAG_NODE_NAME, - SLOT_TAG_NODE_NAME -} from './constants' -import { findEachAttribute, findIfAttribute, findIsAttribute, findKeyAttribute } from './find' -import { - getName, - getNodeAttributes -} from './utils' -import { isBrowserAPI, isBuiltinAPI, isNewExpression, isRaw } from '../../utils/ast-nodes-checks' -import compose from 'cumpa' -import { nodeTypes } from '@riotjs/parser' -import { types } from '../../utils/build-types' - -/** - * True if the node has not expression set nor bindings directives - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only if it's a static node that doesn't need bindings or expressions - */ -export function isStaticNode(node) { - return [ - hasExpressions, - findEachAttribute, - findIfAttribute, - isCustomNode, - isSlotNode - ].every(test => !test(node)) -} - -/** - * Check if a node name is part of the browser or builtin javascript api or it belongs to the current scope - * @param { types.NodePath } path - containing the current node visited - * @returns {boolean} true if it's a global api variable - */ -export function isGlobal({ scope, node }) { - return Boolean( - isRaw(node) || - isBuiltinAPI(node) || - isBrowserAPI(node) || - isNewExpression(node) || - isNodeInScope(scope, node), - ) -} - -/** - * Checks if the identifier of a given node exists in a scope - * @param {Scope} scope - scope where to search for the identifier - * @param {types.Node} node - node to search for the identifier - * @returns {boolean} true if the node identifier is defined in the given scope - */ -function isNodeInScope(scope, node) { - const traverse = (isInScope = false) => { - types.visit(node, { - visitIdentifier(path) { - if (scope.lookup(getName(path.node))) { - isInScope = true - } - - this.abort() - } - }) - - return isInScope - } - - return traverse() -} - -/** - * True if the node has the isCustom attribute set - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if either it's a riot component or a custom element - */ -export function isCustomNode(node) { - return !!(node[IS_CUSTOM_NODE] || hasIsAttribute(node)) -} - -/** - * True the node is - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if it's a slot node - */ -export function isSlotNode(node) { - return node.name === SLOT_TAG_NODE_NAME -} - -/** - * True if the node has the isVoid attribute set - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if the node is self closing - */ -export function isVoidNode(node) { - return !!node[IS_VOID_NODE] -} - -/** - * True if the riot parser did find a tag node - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the tag nodes - */ -export function isTagNode(node) { - return node.type === nodeTypes.TAG -} - -/** - * True if the riot parser did find a text node - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the text nodes - */ -export function isTextNode(node) { - return node.type === nodeTypes.TEXT -} - -/** - * True if the node parsed is the root one - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the root nodes - */ -export function isRootNode(node) { - return node.isRoot -} - -/** - * True if the attribute parsed is of type spread one - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true if the attribute node is of type spread - */ -export function isSpreadAttribute(node) { - return node[IS_SPREAD_ATTRIBUTE] -} - -/** - * True if the node is an attribute and its name is "value" - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for value attribute nodes - */ -export function isValueAttribute(node) { - return node.name === 'value' -} - -/** - * True if the DOM node is a progress tag - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true for the progress tags - */ -export function isProgressNode(node) { - return node.name === PROGRESS_TAG_NODE_NAME -} - -/** - * True if the node is an attribute and a DOM handler - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for dom listener attribute nodes - */ -export const isEventAttribute = (() => { - const EVENT_ATTR_RE = /^on/ - return node => EVENT_ATTR_RE.test(node.name) -})() - -/** - * True if the node has expressions or expression attributes - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} ditto - */ -export function hasExpressions(node) { - return !!( - node.expressions || - // has expression attributes - (getNodeAttributes(node).some(attribute => hasExpressions(attribute))) || - // has child text nodes with expressions - (node.nodes && node.nodes.some(node => isTextNode(node) && hasExpressions(node))) - ) -} - -/** - * True if the node is a directive having its own template - * @param {RiotParser.Node} node - riot parser node - * @returns {boolean} true only for the IF EACH and TAG bindings - */ -export function hasItsOwnTemplate(node) { - return [ - findEachAttribute, - findIfAttribute, - isCustomNode - ].some(test => test(node)) -} - -export const hasIfAttribute = compose(Boolean, findIfAttribute) -export const hasEachAttribute = compose(Boolean, findEachAttribute) -export const hasIsAttribute = compose(Boolean, findIsAttribute) -export const hasKeyAttribute = compose(Boolean, findKeyAttribute) diff --git a/node_modules/@riotjs/compiler/src/generators/template/constants.js b/node_modules/@riotjs/compiler/src/generators/template/constants.js deleted file mode 100644 index 6c1d9b0..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/constants.js +++ /dev/null @@ -1,64 +0,0 @@ -// import {IS_BOOLEAN,IS_CUSTOM,IS_RAW,IS_SPREAD,IS_VOID} from '@riotjs/parser/src/constants' - -export const BINDING_TYPES = 'bindingTypes' -export const EACH_BINDING_TYPE = 'EACH' -export const IF_BINDING_TYPE = 'IF' -export const TAG_BINDING_TYPE = 'TAG' -export const SLOT_BINDING_TYPE = 'SLOT' - - -export const EXPRESSION_TYPES = 'expressionTypes' -export const ATTRIBUTE_EXPRESSION_TYPE = 'ATTRIBUTE' -export const VALUE_EXPRESSION_TYPE = 'VALUE' -export const TEXT_EXPRESSION_TYPE = 'TEXT' -export const EVENT_EXPRESSION_TYPE = 'EVENT' - -export const TEMPLATE_FN = 'template' -export const SCOPE = 'scope' -export const GET_COMPONENT_FN = 'getComponent' - -// keys needed to create the DOM bindings -export const BINDING_SELECTOR_KEY = 'selector' -export const BINDING_GET_COMPONENT_KEY = 'getComponent' -export const BINDING_TEMPLATE_KEY = 'template' -export const BINDING_TYPE_KEY = 'type' -export const BINDING_REDUNDANT_ATTRIBUTE_KEY = 'redundantAttribute' -export const BINDING_CONDITION_KEY = 'condition' -export const BINDING_ITEM_NAME_KEY = 'itemName' -export const BINDING_GET_KEY_KEY = 'getKey' -export const BINDING_INDEX_NAME_KEY = 'indexName' -export const BINDING_EVALUATE_KEY = 'evaluate' -export const BINDING_NAME_KEY = 'name' -export const BINDING_SLOTS_KEY = 'slots' -export const BINDING_EXPRESSIONS_KEY = 'expressions' -export const BINDING_CHILD_NODE_INDEX_KEY = 'childNodeIndex' -// slots keys -export const BINDING_BINDINGS_KEY = 'bindings' -export const BINDING_ID_KEY = 'id' -export const BINDING_HTML_KEY = 'html' -export const BINDING_ATTRIBUTES_KEY = 'attributes' - -// DOM directives -export const IF_DIRECTIVE = 'if' -export const EACH_DIRECTIVE = 'each' -export const KEY_ATTRIBUTE = 'key' -export const SLOT_ATTRIBUTE = 'slot' -export const NAME_ATTRIBUTE = 'name' -export const IS_DIRECTIVE = 'is' - -// Misc -export const DEFAULT_SLOT_NAME = 'default' -export const TEXT_NODE_EXPRESSION_PLACEHOLDER = '' -export const BINDING_SELECTOR_PREFIX = 'expr' -export const SLOT_TAG_NODE_NAME = 'slot' -export const PROGRESS_TAG_NODE_NAME = 'progress' - -// Riot Parser constants -// TODO: import these values dynamically -export const IS_RAW_NODE = 'isRaw' -export const IS_VOID_NODE = 'isVoid' -export const IS_CUSTOM_NODE = 'isCustom' -export const IS_BOOLEAN_ATTRIBUTE = 'isBoolean' -export const IS_SPREAD_ATTRIBUTE = 'isSpread' - - diff --git a/node_modules/@riotjs/compiler/src/generators/template/expressions/attribute.js b/node_modules/@riotjs/compiler/src/generators/template/expressions/attribute.js deleted file mode 100644 index c55ed53..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/expressions/attribute.js +++ /dev/null @@ -1,36 +0,0 @@ -import { - ATTRIBUTE_EXPRESSION_TYPE, - BINDING_EVALUATE_KEY, - BINDING_NAME_KEY, - BINDING_TYPE_KEY, - EXPRESSION_TYPES -} from '../constants' -import {nullNode, simplePropertyNode} from '../../../utils/custom-ast-nodes' -import {builders} from '../../../utils/build-types' -import {createAttributeEvaluationFunction} from '../utils' -import {isSpreadAttribute} from '../checks' - - -/** - * Create a simple attribute expression - * @param {RiotParser.Node.Attr} sourceNode - the custom tag - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {AST.Node} object containing the expression binding keys - */ -export default function createAttributeExpression(sourceNode, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(ATTRIBUTE_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode(BINDING_NAME_KEY, isSpreadAttribute(sourceNode) ? nullNode() : builders.literal(sourceNode.name)), - simplePropertyNode( - BINDING_EVALUATE_KEY, - createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) - ) - ]) -} diff --git a/node_modules/@riotjs/compiler/src/generators/template/expressions/event.js b/node_modules/@riotjs/compiler/src/generators/template/expressions/event.js deleted file mode 100644 index c6fc45f..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/expressions/event.js +++ /dev/null @@ -1,34 +0,0 @@ -import { - BINDING_EVALUATE_KEY, - BINDING_NAME_KEY, - BINDING_TYPE_KEY, - EVENT_EXPRESSION_TYPE, - EXPRESSION_TYPES -} from '../constants' -import {builders} from '../../../utils/build-types' -import {createAttributeEvaluationFunction} from '../utils' -import {simplePropertyNode} from '../../../utils/custom-ast-nodes' - -/** - * Create a simple event expression - * @param {RiotParser.Node.Attr} sourceNode - attribute containing the event handlers - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {AST.Node} object containing the expression binding keys - */ -export default function createEventExpression(sourceNode, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(EVENT_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode(BINDING_NAME_KEY, builders.literal(sourceNode.name)), - simplePropertyNode( - BINDING_EVALUATE_KEY, - createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) - ) - ]) -} diff --git a/node_modules/@riotjs/compiler/src/generators/template/expressions/index.js b/node_modules/@riotjs/compiler/src/generators/template/expressions/index.js deleted file mode 100644 index 09d1b42..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/expressions/index.js +++ /dev/null @@ -1,34 +0,0 @@ -import {isEventAttribute, isProgressNode, isTextNode, isValueAttribute} from '../checks' -import attributeExpression from './attribute' -import eventExpression from './event' -import {findDynamicAttributes} from '../find' -import {hasValueAttribute} from 'dom-nodes' -import textExpression from './text' -import valueExpression from './value' - -export function createExpression(sourceNode, sourceFile, sourceCode, childNodeIndex, parentNode) { - switch (true) { - case isTextNode(sourceNode): - return textExpression(sourceNode, sourceFile, sourceCode, childNodeIndex) - // progress nodes value attributes will be rendered as attributes - // see https://github.com/riot/compiler/issues/122 - case isValueAttribute(sourceNode) && hasValueAttribute(parentNode.name) && !isProgressNode(parentNode): - return valueExpression(sourceNode, sourceFile, sourceCode) - case isEventAttribute(sourceNode): - return eventExpression(sourceNode, sourceFile, sourceCode) - default: - return attributeExpression(sourceNode, sourceFile, sourceCode) - } -} - -/** - * Create the attribute expressions - * @param {RiotParser.Node} sourceNode - any kind of node parsed via riot parser - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @returns {Array} array containing all the attribute expressions - */ -export function createAttributeExpressions(sourceNode, sourceFile, sourceCode) { - return findDynamicAttributes(sourceNode) - .map(attribute => createExpression(attribute, sourceFile, sourceCode, 0, sourceNode)) -} diff --git a/node_modules/@riotjs/compiler/src/generators/template/expressions/text.js b/node_modules/@riotjs/compiler/src/generators/template/expressions/text.js deleted file mode 100644 index cb69cd0..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/expressions/text.js +++ /dev/null @@ -1,90 +0,0 @@ -import { - BINDING_CHILD_NODE_INDEX_KEY, - BINDING_EVALUATE_KEY, - BINDING_TYPE_KEY, - EXPRESSION_TYPES, - TEXT_EXPRESSION_TYPE -} from '../constants' -import {createArrayString, transformExpression, wrapASTInFunctionWithScope} from '../utils' -import {nullNode,simplePropertyNode} from '../../../utils/custom-ast-nodes' -import {builders} from '../../../utils/build-types' -import {isLiteral} from '../../../utils/ast-nodes-checks' -import unescapeChar from '../../../utils/unescape-char' - -/** - * Generate the pure immutable string chunks from a RiotParser.Node.Text - * @param {RiotParser.Node.Text} node - riot parser text node - * @param {string} sourceCode sourceCode - source code - * @returns {Array} array containing the immutable string chunks - */ -function generateLiteralStringChunksFromNode(node, sourceCode) { - return node.expressions.reduce((chunks, expression, index) => { - const start = index ? node.expressions[index - 1].end : node.start - - chunks.push(sourceCode.substring(start, expression.start)) - - // add the tail to the string - if (index === node.expressions.length - 1) - chunks.push(sourceCode.substring(expression.end, node.end)) - - return chunks - }, []).map(str => node.unescape ? unescapeChar(str, node.unescape) : str) -} - -/** - * Simple bindings might contain multiple expressions like for example: "{foo} and {bar}" - * This helper aims to merge them in a template literal if it's necessary - * @param {RiotParser.Node} node - riot parser node - * @param {string} sourceFile - original tag file - * @param {string} sourceCode - original tag source code - * @returns { Object } a template literal expression object - */ -export function mergeNodeExpressions(node, sourceFile, sourceCode) { - if (node.parts.length === 1) - return transformExpression(node.expressions[0], sourceFile, sourceCode) - - const pureStringChunks = generateLiteralStringChunksFromNode(node, sourceCode) - const stringsArray = pureStringChunks.reduce((acc, str, index) => { - const expr = node.expressions[index] - - return [ - ...acc, - builders.literal(str), - expr ? transformExpression(expr, sourceFile, sourceCode) : nullNode() - ] - }, []) - // filter the empty literal expressions - .filter(expr => !isLiteral(expr) || expr.value) - - return createArrayString(stringsArray) -} - -/** - * Create a text expression - * @param {RiotParser.Node.Text} sourceNode - text node to parse - * @param {string} sourceFile - source file path - * @param {string} sourceCode - original source - * @param {number} childNodeIndex - position of the child text node in its parent children nodes - * @returns {AST.Node} object containing the expression binding keys - */ -export default function createTextExpression(sourceNode, sourceFile, sourceCode, childNodeIndex) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(TEXT_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_CHILD_NODE_INDEX_KEY, - builders.literal(childNodeIndex) - ), - simplePropertyNode( - BINDING_EVALUATE_KEY, - wrapASTInFunctionWithScope( - mergeNodeExpressions(sourceNode, sourceFile, sourceCode) - ) - ) - ]) -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/generators/template/expressions/value.js b/node_modules/@riotjs/compiler/src/generators/template/expressions/value.js deleted file mode 100644 index 49ed5d6..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/expressions/value.js +++ /dev/null @@ -1,25 +0,0 @@ -import { - BINDING_EVALUATE_KEY, - BINDING_TYPE_KEY, - EXPRESSION_TYPES, - VALUE_EXPRESSION_TYPE -} from '../constants' -import {builders} from '../../../utils/build-types' -import {createAttributeEvaluationFunction} from '../utils' -import {simplePropertyNode} from '../../../utils/custom-ast-nodes' - -export default function createValueExpression(sourceNode, sourceFile, sourceCode) { - return builders.objectExpression([ - simplePropertyNode(BINDING_TYPE_KEY, - builders.memberExpression( - builders.identifier(EXPRESSION_TYPES), - builders.identifier(VALUE_EXPRESSION_TYPE), - false - ), - ), - simplePropertyNode( - BINDING_EVALUATE_KEY, - createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) - ) - ]) -} diff --git a/node_modules/@riotjs/compiler/src/generators/template/find.js b/node_modules/@riotjs/compiler/src/generators/template/find.js deleted file mode 100644 index e88797e..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/find.js +++ /dev/null @@ -1,47 +0,0 @@ -import { EACH_DIRECTIVE, IF_DIRECTIVE, IS_DIRECTIVE, KEY_ATTRIBUTE } from './constants' -import { getName, getNodeAttributes } from './utils' -import { hasExpressions } from './checks' - -/** - * Find the attribute node - * @param { string } name - name of the attribute we want to find - * @param { riotParser.nodeTypes.TAG } node - a tag node - * @returns { riotParser.nodeTypes.ATTR } attribute node - */ -export function findAttribute(name, node) { - return node.attributes && node.attributes.find(attr => getName(attr) === name) -} - -export function findIfAttribute(node) { - return findAttribute(IF_DIRECTIVE, node) -} - -export function findEachAttribute(node) { - return findAttribute(EACH_DIRECTIVE, node) -} - -export function findKeyAttribute(node) { - return findAttribute(KEY_ATTRIBUTE, node) -} - -export function findIsAttribute(node) { - return findAttribute(IS_DIRECTIVE, node) -} - -/** - * Find all the node attributes that are not expressions - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} list of all the static attributes - */ -export function findStaticAttributes(node) { - return getNodeAttributes(node).filter(attribute => !hasExpressions(attribute)) -} - -/** - * Find all the node attributes that have expressions - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} list of all the dynamic attributes - */ -export function findDynamicAttributes(node) { - return getNodeAttributes(node).filter(hasExpressions) -} diff --git a/node_modules/@riotjs/compiler/src/generators/template/index.js b/node_modules/@riotjs/compiler/src/generators/template/index.js deleted file mode 100644 index 60a7bf0..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/index.js +++ /dev/null @@ -1,74 +0,0 @@ -import {BINDING_TYPES, EXPRESSION_TYPES, GET_COMPONENT_FN, TEMPLATE_FN} from './constants' -import {builders, types} from '../../utils/build-types' -import {callTemplateFunction, createRootNode} from './utils' -import {TAG_TEMPLATE_PROPERTY} from '../../constants' -import build from './builder' - -const templateFunctionArguments = [ - TEMPLATE_FN, - EXPRESSION_TYPES, - BINDING_TYPES, - GET_COMPONENT_FN -].map(builders.identifier) - -/** - * Create the content of the template function - * @param { RiotParser.Node } sourceNode - node generated by the riot compiler - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @returns {AST.BlockStatement} the content of the template function - */ -function createTemplateFunctionContent(sourceNode, sourceFile, sourceCode) { - return builders.blockStatement([ - builders.returnStatement( - callTemplateFunction( - ...build( - createRootNode(sourceNode), - sourceFile, - sourceCode - ) - ) - ) - ]) -} - -/** - * Extend the AST adding the new template property containing our template call to render the component - * @param { Object } ast - current output ast - * @param { string } sourceFile - source file path - * @param { string } sourceCode - original source - * @param { RiotParser.Node } sourceNode - node generated by the riot compiler - * @returns { Object } the output ast having the "template" key - */ -function extendTemplateProperty(ast, sourceFile, sourceCode, sourceNode) { - types.visit(ast, { - visitProperty(path) { - if (path.value.key.value === TAG_TEMPLATE_PROPERTY) { - path.value.value = builders.functionExpression( - null, - templateFunctionArguments, - createTemplateFunctionContent(sourceNode, sourceFile, sourceCode) - ) - - return false - } - - this.traverse(path) - } - }) - - return ast -} - -/** - * Generate the component template logic - * @param { RiotParser.Node } sourceNode - node generated by the riot compiler - * @param { string } source - original component source code - * @param { Object } meta - compilation meta information - * @param { AST } ast - current AST output - * @returns { AST } the AST generated - */ -export default function template(sourceNode, source, meta, ast) { - const { options } = meta - return extendTemplateProperty(ast, options.file, source, sourceNode) -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/generators/template/utils.js b/node_modules/@riotjs/compiler/src/generators/template/utils.js deleted file mode 100644 index f67df1b..0000000 --- a/node_modules/@riotjs/compiler/src/generators/template/utils.js +++ /dev/null @@ -1,486 +0,0 @@ -import { - BINDING_REDUNDANT_ATTRIBUTE_KEY, - BINDING_SELECTOR_KEY, - BINDING_SELECTOR_PREFIX, - BINDING_TEMPLATE_KEY, - EACH_DIRECTIVE, - IF_DIRECTIVE, - IS_BOOLEAN_ATTRIBUTE, - IS_DIRECTIVE, - KEY_ATTRIBUTE, - SCOPE, - SLOT_ATTRIBUTE, - TEMPLATE_FN, - TEXT_NODE_EXPRESSION_PLACEHOLDER -} from './constants' -import { builders, types } from '../../utils/build-types' -import { findIsAttribute, findStaticAttributes } from './find' -import { hasExpressions, isGlobal, isTagNode, isTextNode, isVoidNode } from './checks' -import { isBinaryExpression, isIdentifier, isLiteral, isThisExpression } from '../../utils/ast-nodes-checks' -import { nullNode, simplePropertyNode } from '../../utils/custom-ast-nodes' -import addLinesOffset from '../../utils/add-lines-offset' -import compose from 'cumpa' -import generateAST from '../../utils/generate-ast' -import unescapeChar from '../../utils/unescape-char' - -const scope = builders.identifier(SCOPE) -export const getName = node => node && node.name ? node.name : node - -/** - * Replace the path scope with a member Expression - * @param { types.NodePath } path - containing the current node visited - * @param { types.Node } property - node we want to prefix with the scope identifier - * @returns {undefined} this is a void function - */ -function replacePathScope(path, property) { - path.replace(builders.memberExpression( - scope, - property, - false - )) -} - -/** - * Change the nodes scope adding the `scope` prefix - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean } return false if we want to stop the tree traversal - * @context { types.visit } - */ -function updateNodeScope(path) { - if (!isGlobal(path)) { - replacePathScope(path, path.node) - - return false - } - - this.traverse(path) -} - -/** - * Change the scope of the member expressions - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean } return always false because we want to check only the first node object - */ -function visitMemberExpression(path) { - if (!isGlobal(path) && !isGlobal({ node: path.node.object, scope: path.scope })) { - if (path.value.computed) { - this.traverse(path) - } else if (isBinaryExpression(path.node.object) || path.node.object.computed) { - this.traverse(path.get('object')) - } else if (!path.node.object.callee) { - replacePathScope(path, isThisExpression(path.node.object) ? path.node.property : path.node) - } else { - this.traverse(path.get('object')) - } - } - - return false -} - - -/** - * Objects properties should be handled a bit differently from the Identifier - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean } return false if we want to stop the tree traversal - */ -function visitProperty(path) { - const value = path.node.value - - if (isIdentifier(value)) { - updateNodeScope(path.get('value')) - } else { - this.traverse(path.get('value')) - } - - return false -} - -/** - * The this expressions should be replaced with the scope - * @param { types.NodePath } path - containing the current node visited - * @returns { boolean|undefined } return false if we want to stop the tree traversal - */ -function visitThisExpression(path) { - path.replace(scope) - this.traverse(path) -} - - -/** - * Update the scope of the global nodes - * @param { Object } ast - ast program - * @returns { Object } the ast program with all the global nodes updated - */ -export function updateNodesScope(ast) { - const ignorePath = () => false - - types.visit(ast, { - visitIdentifier: updateNodeScope, - visitMemberExpression, - visitProperty, - visitThisExpression, - visitClassExpression: ignorePath - }) - - return ast -} - -/** - * Convert any expression to an AST tree - * @param { Object } expression - expression parsed by the riot parser - * @param { string } sourceFile - original tag file - * @param { string } sourceCode - original tag source code - * @returns { Object } the ast generated - */ -export function createASTFromExpression(expression, sourceFile, sourceCode) { - const code = sourceFile ? - addLinesOffset(expression.text, sourceCode, expression) : - expression.text - - return generateAST(`(${code})`, { - sourceFileName: sourceFile - }) -} - -/** - * Create the bindings template property - * @param {Array} args - arguments to pass to the template function - * @returns {ASTNode} a binding template key - */ -export function createTemplateProperty(args) { - return simplePropertyNode( - BINDING_TEMPLATE_KEY, - args ? callTemplateFunction(...args) : nullNode() - ) -} - -/** - * Try to get the expression of an attribute node - * @param { RiotParser.Node.Attribute } attribute - riot parser attribute node - * @returns { RiotParser.Node.Expression } attribute expression value - */ -export function getAttributeExpression(attribute) { - return attribute.expressions ? attribute.expressions[0] : { - // if no expression was found try to typecast the attribute value - ...attribute, - text: attribute.value - } -} - -/** - * Wrap the ast generated in a function call providing the scope argument - * @param {Object} ast - function body - * @returns {FunctionExpresion} function having the scope argument injected - */ -export function wrapASTInFunctionWithScope(ast) { - return builders.functionExpression( - null, - [scope], - builders.blockStatement([builders.returnStatement( - ast - )]) - ) -} - -/** - * Convert any parser option to a valid template one - * @param { RiotParser.Node.Expression } expression - expression parsed by the riot parser - * @param { string } sourceFile - original tag file - * @param { string } sourceCode - original tag source code - * @returns { Object } a FunctionExpression object - * - * @example - * toScopedFunction('foo + bar') // scope.foo + scope.bar - * - * @example - * toScopedFunction('foo.baz + bar') // scope.foo.baz + scope.bar - */ -export function toScopedFunction(expression, sourceFile, sourceCode) { - return compose( - wrapASTInFunctionWithScope, - transformExpression, - )(expression, sourceFile, sourceCode) -} - -/** - * Transform an expression node updating its global scope - * @param {RiotParser.Node.Expr} expression - riot parser expression node - * @param {string} sourceFile - source file - * @param {string} sourceCode - source code - * @returns {ASTExpression} ast expression generated from the riot parser expression node - */ -export function transformExpression(expression, sourceFile, sourceCode) { - return compose( - getExpressionAST, - updateNodesScope, - createASTFromExpression - )(expression, sourceFile, sourceCode) -} - -/** - * Get the parsed AST expression of riot expression node - * @param {AST.Program} sourceAST - raw node parsed - * @returns {AST.Expression} program expression output - */ -export function getExpressionAST(sourceAST) { - const astBody = sourceAST.program.body - - return astBody[0] ? astBody[0].expression : astBody -} - -/** - * Create the template call function - * @param {Array|string|Node.Literal} template - template string - * @param {Array} bindings - template bindings provided as AST nodes - * @returns {Node.CallExpression} template call expression - */ -export function callTemplateFunction(template, bindings) { - return builders.callExpression(builders.identifier(TEMPLATE_FN), [ - template ? builders.literal(template) : nullNode(), - bindings ? builders.arrayExpression(bindings) : nullNode() - ]) -} - -/** - * Convert any DOM attribute into a valid DOM selector useful for the querySelector API - * @param { string } attributeName - name of the attribute to query - * @returns { string } the attribute transformed to a query selector - */ -export const attributeNameToDOMQuerySelector = attributeName => `[${attributeName}]` - -/** - * Create the properties to query a DOM node - * @param { string } attributeName - attribute name needed to identify a DOM node - * @returns { Array } array containing the selector properties needed for the binding - */ -export function createSelectorProperties(attributeName) { - return attributeName ? [ - simplePropertyNode(BINDING_REDUNDANT_ATTRIBUTE_KEY, builders.literal(attributeName)), - simplePropertyNode(BINDING_SELECTOR_KEY, - compose(builders.literal, attributeNameToDOMQuerySelector)(attributeName) - ) - ] : [] -} - -/** - * Clone the node filtering out the selector attribute from the attributes list - * @param {RiotParser.Node} node - riot parser node - * @param {string} selectorAttribute - name of the selector attribute to filter out - * @returns {RiotParser.Node} the node with the attribute cleaned up - */ -export function cloneNodeWithoutSelectorAttribute(node, selectorAttribute) { - return { - ...node, - attributes: getAttributesWithoutSelector(getNodeAttributes(node), selectorAttribute) - } -} - - -/** - * Get the node attributes without the selector one - * @param {Array} attributes - attributes list - * @param {string} selectorAttribute - name of the selector attribute to filter out - * @returns {Array} filtered attributes - */ -export function getAttributesWithoutSelector(attributes, selectorAttribute) { - if (selectorAttribute) - return attributes.filter(attribute => attribute.name !== selectorAttribute) - - return attributes -} - -/** - * Clean binding or custom attributes - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} only the attributes that are not bindings or directives - */ -export function cleanAttributes(node) { - return getNodeAttributes(node).filter(attribute => ![ - IF_DIRECTIVE, - EACH_DIRECTIVE, - KEY_ATTRIBUTE, - SLOT_ATTRIBUTE, - IS_DIRECTIVE - ].includes(attribute.name)) -} - -/** - * Create a root node proxing only its nodes and attributes - * @param {RiotParser.Node} node - riot parser node - * @returns {RiotParser.Node} root node - */ -export function createRootNode(node) { - return { - nodes: getChildrenNodes(node), - isRoot: true, - // root nodes shuold't have directives - attributes: cleanAttributes(node) - } -} - -/** - * Get all the child nodes of a RiotParser.Node - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} all the child nodes found - */ -export function getChildrenNodes(node) { - return node && node.nodes ? node.nodes : [] -} - -/** - * Get all the attributes of a riot parser node - * @param {RiotParser.Node} node - riot parser node - * @returns {Array} all the attributes find - */ -export function getNodeAttributes(node) { - return node.attributes ? node.attributes : [] -} -/** - * Get the name of a custom node transforming it into an expression node - * @param {RiotParser.Node} node - riot parser node - * @returns {RiotParser.Node.Attr} the node name as expression attribute - */ -export function getCustomNodeNameAsExpression(node) { - const isAttribute = findIsAttribute(node) - const toRawString = val => `'${val}'` - - if (isAttribute) { - return isAttribute.expressions ? isAttribute.expressions[0] : { - ...isAttribute, - text: toRawString(isAttribute.value) - } - } - - return { ...node, text: toRawString(getName(node)) } -} - -/** - * Convert all the node static attributes to strings - * @param {RiotParser.Node} node - riot parser node - * @returns {string} all the node static concatenated as string - */ -export function staticAttributesToString(node) { - return findStaticAttributes(node) - .map(attribute => attribute[IS_BOOLEAN_ATTRIBUTE] || !attribute.value ? - attribute.name : - `${attribute.name}="${unescapeNode(attribute, 'value').value}"` - ).join(' ') -} - -/** - * Make sure that node escaped chars will be unescaped - * @param {RiotParser.Node} node - riot parser node - * @param {string} key - key property to unescape - * @returns {RiotParser.Node} node with the text property unescaped - */ -export function unescapeNode(node, key) { - if (node.unescape) { - return { - ...node, - [key]: unescapeChar(node[key], node.unescape) - } - } - - return node -} - - -/** - * Convert a riot parser opening node into a string - * @param {RiotParser.Node} node - riot parser node - * @returns {string} the node as string - */ -export function nodeToString(node) { - const attributes = staticAttributesToString(node) - - switch(true) { - case isTagNode(node): - return `<${node.name}${attributes ? ` ${attributes}` : ''}${isVoidNode(node) ? '/' : ''}>` - case isTextNode(node): - return hasExpressions(node) ? TEXT_NODE_EXPRESSION_PLACEHOLDER : unescapeNode(node, 'text').text - default: - return '' - } -} - -/** - * Close an html node - * @param {RiotParser.Node} node - riot parser node - * @returns {string} the closing tag of the html tag node passed to this function - */ -export function closeTag(node) { - return node.name ? `` : '' -} - -/** - * Create a strings array with the `join` call to transform it into a string - * @param {Array} stringsArray - array containing all the strings to concatenate - * @returns {AST.CallExpression} array with a `join` call - */ -export function createArrayString(stringsArray) { - return builders.callExpression( - builders.memberExpression( - builders.arrayExpression(stringsArray), - builders.identifier('join'), - false - ), - [builders.literal('')], - ) -} - -/** - * Simple expression bindings might contain multiple expressions like for example: "class="{foo} red {bar}"" - * This helper aims to merge them in a template literal if it's necessary - * @param {RiotParser.Attr} node - riot parser node - * @param {string} sourceFile - original tag file - * @param {string} sourceCode - original tag source code - * @returns { Object } a template literal expression object - */ -export function mergeAttributeExpressions(node, sourceFile, sourceCode) { - if (!node.parts || node.parts.length === 1) { - return transformExpression(node.expressions[0], sourceFile, sourceCode) - } - const stringsArray = [ - ...node.parts.reduce((acc, str) => { - const expression = node.expressions.find(e => e.text.trim() === str) - - return [ - ...acc, - expression ? transformExpression(expression, sourceFile, sourceCode) : builders.literal(str) - ] - }, []) - ].filter(expr => !isLiteral(expr) || expr.value) - - - return createArrayString(stringsArray) -} - -/** - * Create a selector that will be used to find the node via dom-bindings - * @param {number} id - temporary variable that will be increased anytime this function will be called - * @returns {string} selector attribute needed to bind a riot expression - */ -export const createBindingSelector = (function createSelector(id = 0) { - return () => `${BINDING_SELECTOR_PREFIX}${id++}` -}()) - -/** - * Create an attribute evaluation function - * @param {RiotParser.Attr} sourceNode - riot parser node - * @param {string} sourceFile - original tag file - * @param {string} sourceCode - original tag source code - * @returns { AST.Node } an AST function expression to evaluate the attribute value - */ -export function createAttributeEvaluationFunction(sourceNode, sourceFile, sourceCode) { - return hasExpressions(sourceNode) ? - // dynamic attribute - wrapASTInFunctionWithScope(mergeAttributeExpressions(sourceNode, sourceFile, sourceCode)) : - // static attribute - builders.functionExpression( - null, - [], - builders.blockStatement([ - builders.returnStatement(builders.literal(sourceNode.value || true)) - ]), - ) -} diff --git a/node_modules/@riotjs/compiler/src/index.js b/node_modules/@riotjs/compiler/src/index.js deleted file mode 100644 index 9aab1a2..0000000 --- a/node_modules/@riotjs/compiler/src/index.js +++ /dev/null @@ -1,156 +0,0 @@ -import { TAG_CSS_PROPERTY, TAG_LOGIC_PROPERTY, TAG_NAME_PROPERTY, TAG_TEMPLATE_PROPERTY } from './constants' -import { nullNode, simplePropertyNode } from './utils/custom-ast-nodes' -import { register as registerPostproc, execute as runPostprocessors } from './postprocessors' -import { register as registerPreproc, execute as runPreprocessor } from './preprocessors' -import {builders} from './utils/build-types' -import compose from 'cumpa' -import cssGenerator from './generators/css/index' -import curry from 'curri' -import generateJavascript from './utils/generate-javascript' -import isEmptySourcemap from './utils/is-empty-sourcemap' -import javascriptGenerator from './generators/javascript/index' -import riotParser from '@riotjs/parser' -import sourcemapAsJSON from './utils/sourcemap-as-json' -import templateGenerator from './generators/template/index' - -const DEFAULT_OPTIONS = { - template: 'default', - file: '[unknown-source-file]', - scopedCss: true -} - -/** - * Create the initial AST - * @param {string} tagName - the name of the component we have compiled - * @returns { AST } the initial AST - * - * @example - * // the output represents the following string in AST - */ -export function createInitialInput({tagName}) { - /* - generates - export default { - ${TAG_CSS_PROPERTY}: null, - ${TAG_LOGIC_PROPERTY}: null, - ${TAG_TEMPLATE_PROPERTY}: null - } - */ - return builders.program([ - builders.exportDefaultDeclaration( - builders.objectExpression([ - simplePropertyNode(TAG_CSS_PROPERTY, nullNode()), - simplePropertyNode(TAG_LOGIC_PROPERTY, nullNode()), - simplePropertyNode(TAG_TEMPLATE_PROPERTY, nullNode()), - simplePropertyNode(TAG_NAME_PROPERTY, builders.literal(tagName)) - ]) - )] - ) -} - -/** - * Make sure the input sourcemap is valid otherwise we ignore it - * @param {SourceMapGenerator} map - preprocessor source map - * @returns {Object} sourcemap as json or nothing - */ -function normaliseInputSourceMap(map) { - const inputSourceMap = sourcemapAsJSON(map) - return isEmptySourcemap(inputSourceMap) ? null : inputSourceMap -} - -/** - * Override the sourcemap content making sure it will always contain the tag source code - * @param {Object} map - sourcemap as json - * @param {string} source - component source code - * @returns {Object} original source map with the "sourcesContent" property overriden - */ -function overrideSourcemapContent(map, source) { - return { - ...map, - sourcesContent: [source] - } -} - -/** - * Create the compilation meta object - * @param { string } source - source code of the tag we will need to compile - * @param { string } options - compiling options - * @returns {Object} meta object - */ -function createMeta(source, options) { - return { - tagName: null, - fragments: null, - options: { - ...DEFAULT_OPTIONS, - ...options - }, - source - } -} - -/** - * Generate the output code source together with the sourcemap - * @param { string } source - source code of the tag we will need to compile - * @param { string } opts - compiling options - * @returns { Output } object containing output code and source map - */ -export function compile(source, opts = {}) { - const meta = createMeta(source, opts) - const {options} = meta - const { code, map } = runPreprocessor('template', options.template, meta, source) - const { template, css, javascript } = riotParser(options).parse(code).output - - // extend the meta object with the result of the parsing - Object.assign(meta, { - tagName: template.name, - fragments: { template, css, javascript } - }) - - return compose( - result => ({ ...result, meta }), - result => runPostprocessors(result, meta), - result => ({ - ...result, - map: overrideSourcemapContent(result.map, source) - }), - ast => meta.ast = ast && generateJavascript(ast, { - sourceMapName: `${options.file}.map`, - inputSourceMap: normaliseInputSourceMap(map) - }), - hookGenerator(templateGenerator, template, code, meta), - hookGenerator(javascriptGenerator, javascript, code, meta), - hookGenerator(cssGenerator, css, code, meta), - )(createInitialInput(meta)) -} - -/** - * Prepare the riot parser node transformers - * @param { Function } transformer - transformer function - * @param { Object } sourceNode - riot parser node - * @param { string } source - component source code - * @param { Object } meta - compilation meta information - * @returns { Promise } object containing output code and source map - */ -function hookGenerator(transformer, sourceNode, source, meta) { - if ( - // filter missing nodes - !sourceNode || - // filter nodes without children - (sourceNode.nodes && !sourceNode.nodes.length) || - // filter empty javascript and css nodes - (!sourceNode.nodes && !sourceNode.text)) { - return result => result - } - - return curry(transformer)(sourceNode, source, meta) -} - -// This function can be used to register new preprocessors -// a preprocessor can target either only the css or javascript nodes -// or the complete tag source file ('template') -export const registerPreprocessor = registerPreproc - -// This function can allow you to register postprocessors that will parse the output code -// here we can run prettifiers, eslint fixes... -export const registerPostprocessor = registerPostproc \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/postprocessors.js b/node_modules/@riotjs/compiler/src/postprocessors.js deleted file mode 100644 index 48b144d..0000000 --- a/node_modules/@riotjs/compiler/src/postprocessors.js +++ /dev/null @@ -1,53 +0,0 @@ -import composeSourcemaps from './utils/compose-sourcemaps' -import { createOutput } from './transformer' -import panic from './utils/panic' - -export const postprocessors = new Set() - -/** - * Register a postprocessor that will be used after the parsing and compilation of the riot tags - * @param { Function } postprocessor - transformer that will receive the output code ans sourcemap - * @returns { Set } the postprocessors collection - */ -export function register(postprocessor) { - if (postprocessors.has(postprocessor)) { - panic(`This postprocessor "${postprocessor.name || postprocessor.toString()}" was already registered`) - } - - postprocessors.add(postprocessor) - - return postprocessors -} - -/** - * Unregister a postprocessor - * @param { Function } postprocessor - possibly a postprocessor previously registered - * @returns { Set } the postprocessors collection - */ -export function unregister(postprocessor) { - if (!postprocessors.has(postprocessor)) { - panic(`This postprocessor "${postprocessor.name || postprocessor.toString()}" was never registered`) - } - - postprocessors.delete(postprocessor) - - return postprocessors -} - -/** - * Exec all the postprocessors in sequence combining the sourcemaps generated - * @param { Output } compilerOutput - output generated by the compiler - * @param { Object } meta - compiling meta information - * @returns { Output } object containing output code and source map - */ -export function execute(compilerOutput, meta) { - return Array.from(postprocessors).reduce(function(acc, postprocessor) { - const { code, map } = acc - const output = postprocessor(code, meta) - - return { - code: output.code, - map: composeSourcemaps(map, output.map) - } - }, createOutput(compilerOutput, meta)) -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/preprocessors.js b/node_modules/@riotjs/compiler/src/preprocessors.js deleted file mode 100644 index 648d2dd..0000000 --- a/node_modules/@riotjs/compiler/src/preprocessors.js +++ /dev/null @@ -1,72 +0,0 @@ -import panic from './utils/panic' -import { transform } from './transformer' -/** - * Parsers that can be registered by users to preparse components fragments - * @type { Object } - */ -export const preprocessors = Object.freeze({ - javascript: new Map(), - css: new Map(), - template: new Map().set('default', code => ({ code })) -}) - -// throw a processor type error -function preprocessorTypeError(type) { - panic(`No preprocessor of type "${type}" was found, please make sure to use one of these: 'javascript', 'css' or 'template'`) -} - -// throw an error if the preprocessor was not registered -function preprocessorNameNotFoundError(name) { - panic(`No preprocessor named "${name}" was found, are you sure you have registered it?'`) -} - -/** - * Register a custom preprocessor - * @param { string } type - preprocessor type either 'js', 'css' or 'template' - * @param { string } name - unique preprocessor id - * @param { Function } preprocessor - preprocessor function - * @returns { Map } - the preprocessors map - */ -export function register(type, name, preprocessor) { - if (!type) panic('Please define the type of preprocessor you want to register \'javascript\', \'css\' or \'template\'') - if (!name) panic('Please define a name for your preprocessor') - if (!preprocessor) panic('Please provide a preprocessor function') - if (!preprocessors[type]) preprocessorTypeError(type) - if (preprocessors[type].has(name)) panic(`The preprocessor ${name} was already registered before`) - - preprocessors[type].set(name, preprocessor) - - return preprocessors -} - -/** - * Register a custom preprocessor - * @param { string } type - preprocessor type either 'js', 'css' or 'template' - * @param { string } name - unique preprocessor id - * @returns { Map } - the preprocessors map - */ -export function unregister(type, name) { - if (!type) panic('Please define the type of preprocessor you want to unregister \'javascript\', \'css\' or \'template\'') - if (!name) panic('Please define the name of the preprocessor you want to unregister') - if (!preprocessors[type]) preprocessorTypeError(type) - if (!preprocessors[type].has(name)) preprocessorNameNotFoundError(name) - - preprocessors[type].delete(name) - - return preprocessors -} - -/** - * Exec the compilation of a preprocessor - * @param { string } type - preprocessor type either 'js', 'css' or 'template' - * @param { string } name - unique preprocessor id - * @param { Object } meta - preprocessor meta information - * @param { string } source - source code - * @returns { Output } object containing a sourcemap and a code string - */ -export function execute(type, name, meta, source) { - if (!preprocessors[type]) preprocessorTypeError(type) - if (!preprocessors[type].has(name)) preprocessorNameNotFoundError(name) - - return transform(preprocessors[type].get(name), meta, source) -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/transformer.js b/node_modules/@riotjs/compiler/src/transformer.js deleted file mode 100644 index c5cae26..0000000 --- a/node_modules/@riotjs/compiler/src/transformer.js +++ /dev/null @@ -1,45 +0,0 @@ -import createSourcemap from './utils/create-sourcemap' - -export const Output = Object.freeze({ - code: '', - ast: [], - meta: {}, - map: null -}) - -/** - * Create the right output data result of a parsing - * @param { Object } data - output data - * @param { string } data.code - code generated - * @param { AST } data.ast - ast representing the code - * @param { SourceMapGenerator } data.map - source map generated along with the code - * @param { Object } meta - compilation meta infomration - * @returns { Output } output container object - */ -export function createOutput(data, meta) { - const output = { - ...Output, - ...data, - meta - } - - if (!output.map && meta && meta.options && meta.options.file) - return { - ...output, - map: createSourcemap({ file: meta.options.file }) - } - - return output -} - -/** - * Transform the source code received via a compiler function - * @param { Function } compiler - function needed to generate the output code - * @param { Object } meta - compilation meta information - * @param { string } source - source code - * @returns { Output } output - the result of the compiler - */ -export function transform(compiler, meta, source) { - const result = (compiler ? compiler(source, meta) : { code: source }) - return createOutput(result, meta) -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/add-lines-offset.js b/node_modules/@riotjs/compiler/src/utils/add-lines-offset.js deleted file mode 100644 index 4e9028e..0000000 --- a/node_modules/@riotjs/compiler/src/utils/add-lines-offset.js +++ /dev/null @@ -1,13 +0,0 @@ -import getLineAndColumnByPosition from './get-line-and-column-by-position' - -/** - * Add the offset to the code that must be parsed in order to generate properly the sourcemaps - * @param {string} input - input string - * @param {string} source - original source code - * @param {RiotParser.Node} node - node that we are going to transform - * @return {string} the input string with the offset properly set - */ -export default function addLineOffset(input, source, node) { - const {column, line} = getLineAndColumnByPosition(source, node.start) - return `${'\n'.repeat(line - 1)}${' '.repeat(column + 1)}${input}` -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/ast-nodes-checks.js b/node_modules/@riotjs/compiler/src/utils/ast-nodes-checks.js deleted file mode 100644 index 699d2d9..0000000 --- a/node_modules/@riotjs/compiler/src/utils/ast-nodes-checks.js +++ /dev/null @@ -1,19 +0,0 @@ -import globalScope from 'globals' -import {namedTypes} from './build-types' - -const browserAPIs = Object.keys(globalScope.browser) -const builtinAPIs = Object.keys(globalScope.builtin) - -export const isIdentifier = namedTypes.Identifier.check.bind(namedTypes.Identifier) -export const isLiteral = namedTypes.Literal.check.bind(namedTypes.Literal) -export const isExpressionStatement = namedTypes.ExpressionStatement.check.bind(namedTypes.ExpressionStatement) -export const isObjectExpression = namedTypes.ObjectExpression.check.bind(namedTypes.ObjectExpression) -export const isThisExpression = namedTypes.ThisExpression.check.bind(namedTypes.ThisExpression) -export const isNewExpression = namedTypes.NewExpression.check.bind(namedTypes.NewExpression) -export const isSequenceExpression = namedTypes.SequenceExpression.check.bind(namedTypes.SequenceExpression) -export const isBinaryExpression = namedTypes.BinaryExpression.check.bind(namedTypes.BinaryExpression) -export const isExportDefaultStatement = namedTypes.ExportDefaultDeclaration.check.bind(namedTypes.ExportDefaultDeclaration) - -export const isBrowserAPI = ({name}) => browserAPIs.includes(name) -export const isBuiltinAPI = ({name}) => builtinAPIs.includes(name) -export const isRaw = (node) => node && node.raw // eslint-disable-line \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/build-types.js b/node_modules/@riotjs/compiler/src/utils/build-types.js deleted file mode 100644 index 67573df..0000000 --- a/node_modules/@riotjs/compiler/src/utils/build-types.js +++ /dev/null @@ -1,5 +0,0 @@ -import {types as astTypes} from 'recast' - -export const types = astTypes -export const builders = astTypes.builders -export const namedTypes = astTypes.namedTypes \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/clone-deep.js b/node_modules/@riotjs/compiler/src/utils/clone-deep.js deleted file mode 100644 index 9a118b3..0000000 --- a/node_modules/@riotjs/compiler/src/utils/clone-deep.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Simple clone deep function, do not use it for classes or recursive objects! - * @param {*} source - possibily an object to clone - * @returns {*} the object we wanted to clone - */ -export default function cloneDeep(source) { - return JSON.parse(JSON.stringify(source)) -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/compose-sourcemaps.js b/node_modules/@riotjs/compiler/src/utils/compose-sourcemaps.js deleted file mode 100644 index c83395a..0000000 --- a/node_modules/@riotjs/compiler/src/utils/compose-sourcemaps.js +++ /dev/null @@ -1,22 +0,0 @@ -import asJSON from './sourcemap-as-json' -import {composeSourceMaps} from 'recast/lib/util' -import isNode from './is-node' - -/** - * Compose two sourcemaps - * @param { SourceMapGenerator } formerMap - original sourcemap - * @param { SourceMapGenerator } latterMap - target sourcemap - * @returns { Object } sourcemap json - */ -export default function composeSourcemaps(formerMap, latterMap) { - if ( - isNode() && - formerMap && latterMap && latterMap.mappings - ) { - return composeSourceMaps(asJSON(formerMap), asJSON(latterMap)) - } else if (isNode() && formerMap) { - return asJSON(formerMap) - } - - return {} -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/create-sourcemap.js b/node_modules/@riotjs/compiler/src/utils/create-sourcemap.js deleted file mode 100644 index 95d92ff..0000000 --- a/node_modules/@riotjs/compiler/src/utils/create-sourcemap.js +++ /dev/null @@ -1,10 +0,0 @@ -import { SourceMapGenerator } from 'source-map' - -/** - * Create a new sourcemap generator - * @param { Object } options - sourcemap options - * @returns { SourceMapGenerator } SourceMapGenerator instance - */ -export default function createSourcemap(options) { - return new SourceMapGenerator(options) -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/custom-ast-nodes.js b/node_modules/@riotjs/compiler/src/utils/custom-ast-nodes.js deleted file mode 100644 index b817d38..0000000 --- a/node_modules/@riotjs/compiler/src/utils/custom-ast-nodes.js +++ /dev/null @@ -1,9 +0,0 @@ -import {builders} from './build-types' - -export function nullNode() { - return builders.literal(null) -} - -export function simplePropertyNode(key, value) { - return builders.property('init', builders.literal(key), value, false) -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/generate-ast.js b/node_modules/@riotjs/compiler/src/utils/generate-ast.js deleted file mode 100644 index 8295dab..0000000 --- a/node_modules/@riotjs/compiler/src/utils/generate-ast.js +++ /dev/null @@ -1,22 +0,0 @@ -import {Parser} from 'acorn' -import {parse} from 'recast' - -/** - * Parse a js source to generate the AST - * @param {string} source - javascript source - * @param {Object} options - parser options - * @returns {AST} AST tree - */ -export default function generateAST(source, options) { - return parse(source, { - parser: { - parse(source, opts) { - return Parser.parse(source, { - ...opts, - ecmaVersion: 2020 - }) - } - }, - ...options - }) -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/generate-javascript.js b/node_modules/@riotjs/compiler/src/utils/generate-javascript.js deleted file mode 100644 index 5eaec9b..0000000 --- a/node_modules/@riotjs/compiler/src/utils/generate-javascript.js +++ /dev/null @@ -1,15 +0,0 @@ -import {print} from 'recast' - -/** - * Generate the javascript from an ast source - * @param {AST} ast - ast object - * @param {Object} options - printer options - * @returns {Object} code + map - */ -export default function generateJavascript(ast, options) { - return print(ast, { - ...options, - tabWidth: 2, - quote: 'single' - }) -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/get-line-and-column-by-position.js b/node_modules/@riotjs/compiler/src/utils/get-line-and-column-by-position.js deleted file mode 100644 index 057409f..0000000 --- a/node_modules/@riotjs/compiler/src/utils/get-line-and-column-by-position.js +++ /dev/null @@ -1,16 +0,0 @@ -import splitStringByEOL from './split-string-by-EOL' - -/** - * Get the line and the column of a source text based on its position in the string - * @param { string } string - target string - * @param { number } position - target position - * @returns { Object } object containing the source text line and column - */ -export default function getLineAndColumnByPosition(string, position) { - const lines = splitStringByEOL(string.slice(0, position)) - - return { - line: lines.length, - column: lines[lines.length - 1].length - } -} diff --git a/node_modules/@riotjs/compiler/src/utils/get-preprocessor-type-by-attribute.js b/node_modules/@riotjs/compiler/src/utils/get-preprocessor-type-by-attribute.js deleted file mode 100644 index 582232e..0000000 --- a/node_modules/@riotjs/compiler/src/utils/get-preprocessor-type-by-attribute.js +++ /dev/null @@ -1,24 +0,0 @@ -const ATTRIBUTE_TYPE_NAME = 'type' - -/** - * Get the type attribute from a node generated by the riot parser - * @param { Object} sourceNode - riot parser node - * @returns { string|null } a valid type to identify the preprocessor to use or nothing - */ -export default function getPreprocessorTypeByAttribute(sourceNode) { - const typeAttribute = sourceNode.attributes ? - sourceNode.attributes.find(attribute => attribute.name === ATTRIBUTE_TYPE_NAME) : - null - - return typeAttribute ? normalize(typeAttribute.value) : null -} - - -/** - * Remove the noise in case a user has defined the preprocessor type='text/scss' - * @param { string } value - input string - * @returns { string } normalized string - */ -function normalize(value) { - return value.replace('text/', '') -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/is-empty-sourcemap.js b/node_modules/@riotjs/compiler/src/utils/is-empty-sourcemap.js deleted file mode 100644 index b96e078..0000000 --- a/node_modules/@riotjs/compiler/src/utils/is-empty-sourcemap.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * True if the sourcemap has no mappings, it is empty - * @param {Object} map - sourcemap json - * @returns {boolean} true if empty - */ -export default function isEmptySourcemap(map) { - return !map || !map.mappings || !map.mappings.length -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/is-node.js b/node_modules/@riotjs/compiler/src/utils/is-node.js deleted file mode 100644 index 176fab7..0000000 --- a/node_modules/@riotjs/compiler/src/utils/is-node.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Detect node js environements - * @returns { boolean } true if the runtime is node - */ -export default function isNode() { - return typeof process !== 'undefined' -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/panic.js b/node_modules/@riotjs/compiler/src/utils/panic.js deleted file mode 100644 index 5c966ac..0000000 --- a/node_modules/@riotjs/compiler/src/utils/panic.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Throw an error with a descriptive message - * @param { string } message - error message - * @returns { undefined } hoppla.. at this point the program should stop working - */ -export default function panic(message) { - throw new Error(message) -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/preprocess-node.js b/node_modules/@riotjs/compiler/src/utils/preprocess-node.js deleted file mode 100644 index d9ef32a..0000000 --- a/node_modules/@riotjs/compiler/src/utils/preprocess-node.js +++ /dev/null @@ -1,18 +0,0 @@ -import {execute as runPreprocessor} from '../preprocessors' - -/** - * Preprocess a riot parser node - * @param { string } preprocessorType - either css, js - * @param { string } preprocessorName - preprocessor id - * @param { Object } meta - compilation meta information - * @param { RiotParser.nodeTypes } node - css node detected by the parser - * @returns { Output } code and sourcemap generated by the preprocessor - */ -export default function preprocess(preprocessorType, preprocessorName, meta, node) { - const code = node.text - - return (preprocessorName ? - runPreprocessor(preprocessorType, preprocessorName, meta, code) : - { code } - ) -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/sourcemap-as-json.js b/node_modules/@riotjs/compiler/src/utils/sourcemap-as-json.js deleted file mode 100644 index 7395d00..0000000 --- a/node_modules/@riotjs/compiler/src/utils/sourcemap-as-json.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Return a source map as JSON, it it has not the toJSON method it means it can - * be used right the way - * @param { SourceMapGenerator|Object } map - a sourcemap generator or simply an json object - * @returns { Object } the source map as JSON - */ -export default function sourcemapAsJSON(map) { - if (map && map.toJSON) return map.toJSON() - return map -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/split-string-by-EOL.js b/node_modules/@riotjs/compiler/src/utils/split-string-by-EOL.js deleted file mode 100644 index 2e34e63..0000000 --- a/node_modules/@riotjs/compiler/src/utils/split-string-by-EOL.js +++ /dev/null @@ -1,10 +0,0 @@ -const LINES_RE = /\r\n?|\n/g - -/** - * Split a string into a rows array generated from its EOL matches - * @param { string } string [description] - * @returns { Array } array containing all the string rows - */ -export default function splitStringByEOL(string) { - return string.split(LINES_RE) -} \ No newline at end of file diff --git a/node_modules/@riotjs/compiler/src/utils/unescape-char.js b/node_modules/@riotjs/compiler/src/utils/unescape-char.js deleted file mode 100644 index 9cab2d3..0000000 --- a/node_modules/@riotjs/compiler/src/utils/unescape-char.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Unescape the user escaped chars - * @param {string} string - input string - * @param {string} char - probably a '{' or anything the user want's to escape - * @returns {string} cleaned up string - */ -export default function unescapeChar(string, char) { - return string.replace(RegExp(`\\\\${char}`, 'gm'), char) -} \ No newline at end of file diff --git a/node_modules/@riotjs/dom-bindings/LICENSE b/node_modules/@riotjs/dom-bindings/LICENSE deleted file mode 100644 index 74e6791..0000000 --- a/node_modules/@riotjs/dom-bindings/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Gianluca Guarini - -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. diff --git a/node_modules/@riotjs/dom-bindings/README.md b/node_modules/@riotjs/dom-bindings/README.md deleted file mode 100644 index 1e00c5e..0000000 --- a/node_modules/@riotjs/dom-bindings/README.md +++ /dev/null @@ -1,546 +0,0 @@ -# dom-bindings - -[![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] - -## Usage - -```js -import { template, expressionTypes } from '@riotjs/dom-bindings' - -// Create the app template -const tmpl = template('

', [{ - selector: 'p', - expressions: [ - { - type: expressionTypes.TEXT, - childNodeIndex: 0, - evaluate: scope => scope.greeting, - }, - ], -}]) - -// Mount the template to any DOM node -const target = document.getElementById('app') - -const app = tmpl.mount(target, { - greeting: 'Hello World' -}) -``` - -[travis-image]:https://img.shields.io/travis/riot/dom-bindings.svg?style=flat-square -[travis-url]:https://travis-ci.org/riot/dom-bindings - -[license-image]:http://img.shields.io/badge/license-MIT-000000.svg?style=flat-square -[license-url]:LICENSE - -[npm-version-image]:http://img.shields.io/npm/v/@riotjs/dom-bindings.svg?style=flat-square -[npm-downloads-image]:http://img.shields.io/npm/dm/@riotjs/dom-bindings.svg?style=flat-square -[npm-url]:https://npmjs.org/package/@riotjs/dom-bindings - -[coverage-image]:https://img.shields.io/coveralls/riot/dom-bindings/master.svg?style=flat-square -[coverage-url]:https://coveralls.io/r/riot/dom-bindings/?branch=master - -[codeclimate-image]:https://api.codeclimate.com/v1/badges/d0b7c555a1673354d66f/maintainability -[codeclimate-url]:https://codeclimate.com/github/riot/dom-bindings/maintainability - -## API - -### template(String, Array) - -The template method is the most important of this package. -It will create a `TemplateChunk` that could be mounted, updated and unmounted to any DOM node. - -
- Details - -A template will always need a string as first argument and a list of `Bindings` to work properly. -Consider the following example: - -```js -const tmpl = template('

', [{ - selector: 'p', - expressions: [ - { - type: expressionTypes.TEXT, - childNodeIndex: 0, - evaluate: scope => scope.greeting - } - ], -}]) -``` - -The template object above will bind a [simple binding](#simple-binding) to the `

` tag. - -

- -### bindingTypes - -Object containing all the type of bindings supported - -### expressionTypes - -Object containing all the expressions types supported - -## Bindings - -A binding is simply an object that will be used internally to map the data structure provided to a DOM tree. - -
- Details -To create a binding object you might use the following properties: - -- `expressions` - - type: `Array` - - required: `true` - - description: array containing instructions to execute DOM manipulation on the node queried -- `type` - - type: `Number` - - default:`bindingTypes.SIMPLE` - - optional: `true` - - description: id of the binding to use on the node queried. This id must be one of the keys available in the `bindingTypes` object -- `selector` - - type: `String` - - default: binding root **HTMLElement** - - optional: `true` - - description: property to query the node element that needs to updated - -The bindings supported are only of 4 different types: - -- [`simple`](#simple-binding) to bind simply the expressions to a DOM structure -- [`each`](#each-binding) to render DOM lists -- [`if`](#if-binding) to handle conditional DOM structures -- [`tag`](#tag-binding) to mount a coustom tag template to any DOM node - -Combining the bindings above we can map any javascript object to a DOM template. - -
- -### Simple Binding - -These kind of bindings will be only used to connect the expressions to DOM nodes in order to manipulate them. - -
- Details - -**Simple bindings will never modify the DOM tree structure, they will only target a single node.**
-A simple binding must always contain at least one of the following expression: - -- `attribute` to update the node attributes -- `event` to set the event handling -- `text` to update the node content -- `value` to update the node value - -For example, let's consider the following binding: - -```js -const pGreetingBinding = { - selector: 'p', - expressions: [{ - type: expressionTypes.Text, - childNodeIndex: 0, - evaluate: scope => scope.greeting, - }] -} - -template('

', [pGreeting]) -``` - -In this case we have created a binding to update only the content of a `p` tag.
-*Notice that the `p` tag has an empty comment that will be replaced with the value of the binding expression whenever the template will be mounted* - -
- -#### Simple Binding Expressions - -The simple binding supports DOM manipulations only via expressions. - -
- Details -An expression object must have always at least the following properties: - -- `evaluate` - - type: `Function` - - description: function that will receive the current template scope and will return the current expression value -- `type` - - type: `Number` - - description: id to find the expression we need to apply to the node. This id must be one of the keys available in the `expressionTypes` object - -
- -##### Attribute Expression - -The attribute expression allows to update all the DOM node attributes. - -
- Details - This expression might contain the optional `name` key to update a single attribute for example: - - ```js - // update only the class attribute - { type: expressionTypes.ATTRIBUTE, name: 'class', evaluate(scope) { return scope.attr }} - ``` - - If the `name` key will not be defined and the return of the `evaluate` function will be an object, this expression will set all the pairs `key, value` as DOM attributes.
- Given the current scope `{ attr: { class: 'hello', 'name': 'world' }}`, the following expression will allow to set all the object attributes: - - ```js - { type: expressionTypes.ATTRIBUTE, evaluate(scope) { return scope.attr }} - ``` - - If the return value of the evaluate function will be a `Boolean` the attribute will be considered a boolean attribute like `checked` or `selected`... -
- -##### Event Expression - -The event expression is really simple, It must contain the `name` attribute and it will set the callback as `dom[name] = callback`. - -
- Details -For example: - -```js -// add an event listener -{ type: expressionTypes.EVENT, name: 'onclick', evaluate(scope) { return function() { console.log('Hello There') } }} -``` - -To remove an event listener you should only `return null` via evaluate function: - -```js -// remove an event listener -{ type: expressionTypes.EVENT, name: 'onclick', evaluate(scope) { return null } }} -``` - -
- -##### Text Expression - -The text expression must contain the `childNodeIndex` that will be used to identify which childNode from the `element.childNodes` collection will need to update its text content. - -
- Details -Given for example the following template: - -```html -

Your name is:user_icon

-``` - -we could use the following text expression to replace the CommentNode with a TextNode - -```js -{ type: expressionTypes.TEXT, childNodeIndex: 2, evaluate(scope) { return 'Gianluca' } }} -``` -
- -##### Value Expression - -The value expression will just set the `element.value` with the value received from the evaluate function. - -
- Details -It should be used only for form elements and it might look like the example below: - -```js -{ type: expressionTypes.VALUE, evaluate(scope) { return scope.val }} -``` - -
- -### Each Binding - -The `each` binding is used to create multiple DOM nodes of the same type. This binding is typically used in to render javascript collections. - -
- Details - -**`each` bindings will need a template that will be cloned, mounted and updated for all the instances of the collection.**
-An each binding should contain the following properties: - -- `itemName` - - type: `String` - - required: `true` - - description: name to identify the item object of the current iteration -- `indexName` - - type: `Number` - - optional: `true` - - description: name to identify the current item index -- `evaluate` - - type: `Function` - - required: `true` - - description: function that will return the collection to iterate -- `template` - - type: `TemplateChunk` - - required: `true` - - description: a dom-bindings template that will be used as skeleton for the DOM elements created -- `condition` - - type: `Function` - - optional: `true` - - description: function that can be used to filter the items from the collection - -The each bindings have the highest [hierarchical priority](#bindings-hierarchy) compared to the other riot bindings. -The following binding will loop through the `scope.items` collection creating several `p` tags having as TextNode child value dependent loop item received - -```js -const eachBinding = { - type: bindingTypes.EACH, - itemName: 'val', - indexName: 'index' - evaluate: scope => scope.items, - template: template('', [{ - expressions: [ - { - type: expressionTypes.TEXT, - childNodeIndex: 0, - evaluate: scope => `${scope.val} - ${scope.index}` - } - ] - } -} - -template('

', [eachBinding]) -``` -
- -### If Binding - -The `if` bindings are needed to handle conditionally entire parts of your components templates - -
- Details - -**`if` bindings will need a template that will be mounted and unmounted depending on the return value of the evaluate function.**
-An if binding should contain the following properties: - -- `evaluate` - - type: `Function` - - required: `true` - - description: if this function will return truthy values the template will be mounted otherwise unmounted -- `template` - - type: `TemplateChunk` - - required: `true` - - description: a dom-bindings template that will be used as skeleton for the DOM element created - -The following binding will render the `b` tag only if the `scope.isVisible` property will be truthy. Otherwise the `b` tag will be removed from the template - -```js -const ifBinding = { - type: bindingTypes.IF, - evaluate: scope => scope.isVisible, - selector: 'b' - template: template('', [{ - expressions: [ - { - type: expressionTypes.TEXT, - childNodeIndex: 0, - evaluate: scope => scope.name - } - ] - }]) -} - -template('

Hello there

', [ifBinding]) -``` -
- -### Tag Binding - -The `tag` bindings are needed to mount custom components implementations - -
- Details - -`tag` bindings will enhance any child node with a custom component factory function. These bindings are likely riot components that must be mounted as children in a parent component template - -A tag binding might contain the following properties: - -- `getComponent` - - type: `Function` - - required: `true` - - description: the factory function responsible for the tag creation -- `evaluate` - - type: `Function` - - required: `true` - - description: it will receive the current scope and it must return the component id that will be passed as first argument to the `getComponent` function -- `slots` - - type: `Array` - - optional: `true` - - description: array containing the slots that must be mounted into the child tag -- `attributes` - - type: `Array` - - optional: `true` - - description: array containing the attribute values that should be passed to the child tag - -The following tag binding will upgrade the `time` tag using the `human-readable-time` template. -This is how the `human-readable-time` template might look like - -```js -import moment from 'moment' - -export default function HumanReadableTime({ attributes }) { - const dateTimeAttr = attributes.find(({ name }) => name === 'datetime') - - return template('', [{ - expressions: [{ - type: expressionTypes.TEXT, - childNodeIndex: 0, - evaluate(scope) { - const dateTimeValue = dateTimeAttr.evaluate(scope) - return moment(new Date(dateTimeValue)).fromNow() - } - }, ...attributes.map(attr => { - return { - ...attr, - type: expressionTypes.ATTRIBUTE - } - })] - }]) -} -``` - -Here it's how the previous tag might be used in a `tag` binding -```js -import HumanReadableTime from './human-readable-time' - -const tagBinding = { - type: bindingTypes.TAG, - evaluate: () => 'human-readable-time', - getComponent: () => HumanReadableTime, - selector: 'time', - attributes: [{ - evaluate: scope => scope.time, - name: 'datetime' - }] -} - -template('

Your last commit was:

', [tagBinding]).mount(app, { - time: '2017-02-14' -}) -``` - -The `tag` bindings have always a lower priority compared to the `if` and `each` bindings -
- -#### Slot Binding - -The slot binding will be used to manage nested slotted templates that will be update using parent scope - -
- Details -An expression object must have always at least the following properties: - -- `evaluate` - - type: `Function` - - description: function that will receive the current template scope and will return the current expression value -- `type` - - type: `Number` - - description: id to find the expression we need to apply to the node. This id must be one of the keys available in the `expressionTypes` object -- `name` - - type: `String` - - description: the name to identify the binding html we need to mount in this node - - -```js -// slots array that will be mounted receiving the scope of the parent template -const slots = [{ - id: 'foo', - bindings: [{ - selector: '[expr1]', - expressions: [{ - type: expressionTypes.TEXT, - childNodeIndex: 0, - evaluate: scope => scope.text - }] - }], - html: '

' -}] - -const el = template('
', [{ - type: bindingTypes.SLOT, - selector: '[expr0]', - name: 'foo' -}]).mount(app, { - slots -}, { text: 'hello' }) -``` - -
- -## Bindings Hierarchy - -If the same DOM node has multiple bindings bound to it, they should be created following the order below: - -1. Each Binding -2. If Binding -3. Tag Binding - -
- Details - -Let's see some cases where we might combine multiple bindings on the same DOM node and how to handle them properly. - -### Each and If Bindings -Let's consider for example a DOM node that sould handle in parallel the Each and If bindings. -In that case we could skip the `If Binding` and just use the `condition` function provided by the [`Each Binding`](#each-binding) -Each bindings will handle conditional rendering internally without the need of extra logic. - -### Each and Tag Bindings -A custom tag having an Each Binding bound to it should be handled giving the priority to the Eeach Binding. For example: - -```js -const components = { - 'my-tag': function({ slots, attributes }) { - return { - mount(el, scope) { - // do stuff on the mount - }, - unmount() { - // do stuff on the unmount - } - } - } -} -const el = template('
', [{ - type: bindingTypes.EACH, - itemName: 'val', - selector: '[expr0]', - evaluate: scope => scope.items, - template: template(null, [{ - type: bindingTypes.TAG, - name: 'my-tag', - getComponent(name) { - // name here will be 'my-tag' - return components[name] - } - }]) -}]).mount(target, { items: [1, 2] }) -``` - -The template for the Each Binding above will be created receiving `null` as first argument because we suppose that the custom tag template was already stored and registered somewhere else. - -### If and Tag Bindings -Similar to the previous example, If Bindings have always the priority on the Tag Bindings. For example: - -```js -const el = template('
', [{ - type: bindingTypes.IF, - selector: '[expr0]', - evaluate: scope => scope.isVisible, - template: template(null, [{ - type: bindingTypes.TAG, - evaluate: () => 'my-tag', - getComponent(name) { - // name here will be 'my-tag' - return components[name] - } - }]) -}]).mount(target, { isVisible: true }) -``` - -The template for the IF Binding will mount/unmount the Tag Binding on its own DOM node. - -
- diff --git a/node_modules/@riotjs/dom-bindings/dist/esm.dom-bindings.js b/node_modules/@riotjs/dom-bindings/dist/esm.dom-bindings.js deleted file mode 100644 index 9d677fb..0000000 --- a/node_modules/@riotjs/dom-bindings/dist/esm.dom-bindings.js +++ /dev/null @@ -1,1657 +0,0 @@ -/** - * Remove the child nodes from any DOM node - * @param {HTMLElement} node - target node - * @returns {undefined} - */ -function cleanNode(node) { - clearChildren(node.childNodes); -} - -/** - * Clear multiple children in a node - * @param {HTMLElement[]} children - direct children nodes - * @returns {undefined} - */ -function clearChildren(children) { - Array.from(children).forEach(n => n.parentNode && n.parentNode.removeChild(n)); -} - -const EACH = 0; -const IF = 1; -const SIMPLE = 2; -const TAG = 3; -const SLOT = 4; - -var bindingTypes = { - EACH, - IF, - SIMPLE, - TAG, - SLOT -}; - -/** - * Create the template meta object in case of