pax_global_header00006660000000000000000000000064133762135330014520gustar00rootroot0000000000000052 comment=bd9c41060c9a31f550847b3095148085546db349 snapdragon-node-3.0.0/000077500000000000000000000000001337621353300145775ustar00rootroot00000000000000snapdragon-node-3.0.0/.editorconfig000066400000000000000000000004411337621353300172530ustar00rootroot00000000000000# http://editorconfig.org/ root = true [*] charset = utf-8 end_of_line = lf indent_size = 2 indent_style = space insert_final_newline = true trim_trailing_whitespace = true [{**/{actual,fixtures,expected,templates}/**,*.md}] trim_trailing_whitespace = false insert_final_newline = false snapdragon-node-3.0.0/.eslintrc.json000066400000000000000000000073251337621353300174020ustar00rootroot00000000000000{ "extends": [ "eslint:recommended" ], "env": { "browser": false, "es6": true, "node": true, "mocha": true }, "parserOptions":{ "ecmaVersion": 9, "sourceType": "module", "ecmaFeatures": { "modules": true, "experimentalObjectRestSpread": true } }, "globals": { "document": false, "navigator": false, "window": false }, "rules": { "accessor-pairs": 2, "arrow-spacing": [2, { "before": true, "after": true }], "block-spacing": [2, "always"], "brace-style": [2, "1tbs", { "allowSingleLine": true }], "comma-dangle": [2, "never"], "comma-spacing": [2, { "before": false, "after": true }], "comma-style": [2, "last"], "constructor-super": 2, "curly": [2, "multi-line"], "dot-location": [2, "property"], "eol-last": 2, "eqeqeq": [2, "allow-null"], "generator-star-spacing": [2, { "before": true, "after": true }], "handle-callback-err": [2, "^(err|error)$" ], "indent": [2, 2, { "SwitchCase": 1 }], "key-spacing": [2, { "beforeColon": false, "afterColon": true }], "keyword-spacing": [2, { "before": true, "after": true }], "new-cap": [2, { "newIsCap": true, "capIsNew": false }], "new-parens": 2, "no-array-constructor": 2, "no-caller": 2, "no-class-assign": 2, "no-cond-assign": 2, "no-const-assign": 2, "no-control-regex": 2, "no-debugger": 2, "no-delete-var": 2, "no-dupe-args": 2, "no-dupe-class-members": 2, "no-dupe-keys": 2, "no-duplicate-case": 2, "no-empty-character-class": 2, "no-eval": 2, "no-ex-assign": 2, "no-extend-native": 2, "no-extra-bind": 2, "no-extra-boolean-cast": 2, "no-extra-parens": [2, "functions"], "no-fallthrough": 2, "no-floating-decimal": 2, "no-func-assign": 2, "no-implied-eval": 2, "no-inner-declarations": [2, "functions"], "no-invalid-regexp": 2, "no-irregular-whitespace": 2, "no-iterator": 2, "no-label-var": 2, "no-labels": 2, "no-lone-blocks": 2, "no-mixed-spaces-and-tabs": 2, "no-multi-spaces": 2, "no-multi-str": 2, "no-multiple-empty-lines": [2, { "max": 1 }], "no-native-reassign": 0, "no-negated-in-lhs": 2, "no-new": 2, "no-new-func": 2, "no-new-object": 2, "no-new-require": 2, "no-new-wrappers": 2, "no-obj-calls": 2, "no-octal": 2, "no-octal-escape": 2, "no-proto": 0, "no-redeclare": 2, "no-regex-spaces": 2, "no-return-assign": 2, "no-self-compare": 2, "no-sequences": 2, "no-shadow-restricted-names": 2, "no-spaced-func": 2, "no-sparse-arrays": 2, "no-this-before-super": 2, "no-throw-literal": 2, "no-trailing-spaces": 0, "no-undef": 2, "no-undef-init": 2, "no-unexpected-multiline": 2, "no-unneeded-ternary": [2, { "defaultAssignment": false }], "no-unreachable": 2, "no-unused-vars": [2, { "vars": "all", "args": "none" }], "no-useless-call": 0, "no-with": 2, "one-var": [0, { "initialized": "never" }], "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }], "padded-blocks": [0, "never"], "quotes": [2, "single", "avoid-escape"], "radix": 2, "semi": [2, "always"], "semi-spacing": [2, { "before": false, "after": true }], "space-before-blocks": [2, "always"], "space-before-function-paren": [2, "never"], "space-in-parens": [2, "never"], "space-infix-ops": 2, "space-unary-ops": [2, { "words": true, "nonwords": false }], "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], "use-isnan": 2, "valid-typeof": 2, "wrap-iife": [2, "any"], "yoda": [2, "never"] } } snapdragon-node-3.0.0/.gitattributes000066400000000000000000000002001337621353300174620ustar00rootroot00000000000000# Enforce Unix newlines * text eol=lf # binaries *.ai binary *.psd binary *.jpg binary *.gif binary *.png binary *.jpeg binary snapdragon-node-3.0.0/.github/000077500000000000000000000000001337621353300161375ustar00rootroot00000000000000snapdragon-node-3.0.0/.github/contributing.md000066400000000000000000000063411337621353300211740ustar00rootroot00000000000000# Contributing to snapdragon-node First and foremost, thank you! We appreciate that you want to contribute to snapdragon-node, your time is valuable, and your contributions mean a lot to us. **What does "contributing" mean?** Creating an issue is the simplest form of contributing to a project. But there are many ways to contribute, including the following: - Updating or correcting documentation - Feature requests - Bug reports If you'd like to learn more about contributing in general, the [Guide to Idiomatic Contributing](https://github.com/jonschlinkert/idiomatic-contributing) has a lot of useful information. **Showing support for snapdragon-node** Please keep in mind that open source software is built by people like you, who spend their free time creating things the rest the community can use. Don't have time to contribute? No worries, here are some other ways to show your support for snapdragon-node: - star the [project](https://github.com/jonschlinkert/snapdragon-node) - tweet your support for snapdragon-node ## Issues ### Before creating an issue Please try to determine if the issue is caused by an underlying library, and if so, create the issue there. Sometimes this is difficult to know. We only ask that you attempt to give a reasonable attempt to find out. Oftentimes the readme will have advice about where to go to create issues. Try to follow these guidelines - **Investigate the issue**: - **Check the readme** - oftentimes you will find notes about creating issues, and where to go depending on the type of issue. - Create the issue in the appropriate repository. ### Creating an issue Please be as descriptive as possible when creating an issue. Give us the information we need to successfully answer your question or address your issue by answering the following in your issue: - **version**: please note the version of snapdragon-node are you using - **extensions, plugins, helpers, etc** (if applicable): please list any extensions you're using - **error messages**: please paste any error messages into the issue, or a [gist](https://gist.github.com/) ## Above and beyond Here are some tips for creating idiomatic issues. Taking just a little bit extra time will make your issue easier to read, easier to resolve, more likely to be found by others who have the same or similar issue in the future. - read the [Guide to Idiomatic Contributing](https://github.com/jonschlinkert/idiomatic-contributing) - take some time to learn basic markdown. This [markdown cheatsheet](https://gist.github.com/jonschlinkert/5854601) is super helpful, as is the GitHub guide to [basic markdown](https://help.github.com/articles/markdown-basics/). - Learn about [GitHub Flavored Markdown](https://help.github.com/articles/github-flavored-markdown/). And if you want to really go above and beyond, read [mastering markdown](https://guides.github.com/features/mastering-markdown/). - use backticks to wrap code. This ensures that code will retain its format, making it much more readable to others - use syntax highlighting by adding the correct language name after the first "code fence" [node-glob]: https://github.com/isaacs/node-glob [micromatch]: https://github.com/jonschlinkert/micromatch [so]: http://stackoverflow.com/questions/tagged/snapdragon-nodesnapdragon-node-3.0.0/.gitignore000066400000000000000000000004561337621353300165740ustar00rootroot00000000000000# always ignore files *.DS_Store .idea .vscode *.sublime-* # test related, or directories generated by tests test/actual actual coverage .nyc* # npm node_modules npm-debug.log # yarn yarn.lock yarn-error.log # misc _gh_pages _draft _drafts bower_components vendor temp tmp TODO.md package-lock.jsonsnapdragon-node-3.0.0/.npmrc000066400000000000000000000000221337621353300157110ustar00rootroot00000000000000package-lock=falsesnapdragon-node-3.0.0/.travis.yml000066400000000000000000000002241337621353300167060ustar00rootroot00000000000000sudo: false os: - linux - osx - windows language: node_js node_js: - node - '11' - '10' - '9' - '8' - '7' - '6' - '5' - '4' snapdragon-node-3.0.0/.verb.md000066400000000000000000000047751337621353300161520ustar00rootroot00000000000000## Usage ```js const Node = require('snapdragon-node'); // either pass on object with "type" and (optional) "val" const node1 = new Node({type: 'star', val: '*'}); // or pass "val" (first) and "type" (second) as string const node2 = new Node('*', 'star'); // both result in => Node { type: 'star', val: '*' } ``` ## Snapdragon usage With [snapdragon][] v0.9.0 and higher, it's recommended that you use `this.node()` to create a new `Node` inside parser handlers (instead of doing `new Node()`). ### Snapdragon ^1.0.0 Example usage inside a [snapdragon][] parser handler function. ```js const Node = require('{%= name %}'); const Token = require('snapdragon-token'); // create a new AST node const node = new Node({ type: 'star', value: '*' }); // convert a Lexer Token into an AST Node const token = new Token({ type: 'star', value: '*' }); const node = new Node(token); ``` ## Node objects AST Nodes are represented as `Node` objects that implement the following interface: ```js interface Node { type: string; value: string | undefined nodes: array | undefined } ``` - `type` **{string}** - A string representing the node variant type. This property is often used for classifying the purpose or nature of the node, so that parsers or compilers can determine what to do with it. - `value` **{string|undefined}** (optional) - In general, value should only be a string when `node.nodes` is undefined. This is not reinforced, but is considered good practice. Use a different property name to store arbitrary strings on the node when `node.nodes` is an array. - `nodes` **{array|undefined}** (optional) - array of child nodes A number of useful methods and non-enumerable properties are also exposed for adding, finding and removing child nodes, etc. Continue reading the API documentation for more details. ## Node API {%= apidocs("index.js") %} ### Non-enumerable properties - `node.isNode` **{boolean}** - this value is set to `true` when a node is created. This can be useful in situationas as a fast alternative to using `instanceof Node` if you [need to determine](#nodeisnode) if a value is a `node` object. - `node.size` **{number}** - the number of child nodes that have been pushed or unshifted onto `node.nodes` using the node's API. This is useful for determining if nodes were added to `node.nodes` without using `node.push()` or `node.unshift()` (for example: `if (node.nodes && node.size !== node.nodes.length)`) - `node.parent` **{object}** (instance of Node) ## Release history See [the changelog](changelog.md). snapdragon-node-3.0.0/CHANGELOG.md000066400000000000000000000030551337621353300164130ustar00rootroot00000000000000# Release history Changelog entries are classified using the following labels from [keep-a-changelog][]: * `added`: for new features * `changed`: for changes in existing functionality * `deprecated`: for once-stable features removed in upcoming releases * `removed`: for deprecated features removed in this release * `fixed`: for any bug fixes Custom labels used in this changelog: * `dependencies`: bumps dependencies * `housekeeping`: code re-organization, minor edits, or other changes that don't fit in one of the other categories. ## 3.0.0 - 2018-11-24 **Removed** - `node.define` was removed. Use [define-property](https://github.com/jonschlinkert/define-property) or `Object.defineProperty` instead. **Added** - `node.isEmpty` method - `node.clone` method, for cloning the node - `node.stringify` method, for returning a string from `node.value` and/or from recursing over child nodes. ## 2.1.0 - 2017-11-14 **Deprecated** - `node.define` is deprecated and will be removed in v3.0.0. Use [define-property](https://github.com/jonschlinkert/define-property) or `Object.defineProperty` instead. ## 2.0.0 - 2017-05-01 **Changed** - `node.unshiftNode` was renamed to [.unshift](readme.md#unshift) - `node.pushNode` was renamed to [.push](readme.md#push) - `node.getNode` was renamed to [.find](readme.md#find) **Added** - [.isNode](readme.md#isNode) - [.isEmpty](readme.md#isEmpty) - [.pop](readme.md#pop) - [.shift](readme.md#shift) - [.remove](readme.md#remove) ### 0.1.0 First release. [keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog snapdragon-node-3.0.0/LICENSE000066400000000000000000000021031337621353300156000ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2017-present, Jon Schlinkert. 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. snapdragon-node-3.0.0/README.md000066400000000000000000000353251337621353300160660ustar00rootroot00000000000000# snapdragon-node [![NPM version](https://img.shields.io/npm/v/snapdragon-node.svg?style=flat)](https://www.npmjs.com/package/snapdragon-node) [![NPM monthly downloads](https://img.shields.io/npm/dm/snapdragon-node.svg?style=flat)](https://npmjs.org/package/snapdragon-node) [![NPM total downloads](https://img.shields.io/npm/dt/snapdragon-node.svg?style=flat)](https://npmjs.org/package/snapdragon-node) [![Linux Build Status](https://img.shields.io/travis/here-be/snapdragon-node.svg?style=flat&label=Travis)](https://travis-ci.org/here-be/snapdragon-node) > Class for creating AST nodes. Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support. ## Install Install with [npm](https://www.npmjs.com/): ```sh $ npm install --save snapdragon-node ``` ## Usage ```js const Node = require('snapdragon-node'); // either pass on object with "type" and (optional) "val" const node1 = new Node({type: 'star', val: '*'}); // or pass "val" (first) and "type" (second) as string const node2 = new Node('*', 'star'); // both result in => Node { type: 'star', val: '*' } ``` ## Snapdragon usage With [snapdragon](https://github.com/here-be/snapdragon) v0.9.0 and higher, it's recommended that you use `this.node()` to create a new `Node` inside parser handlers (instead of doing `new Node()`). ### Snapdragon ^1.0.0 Example usage inside a [snapdragon](https://github.com/here-be/snapdragon) parser handler function. ```js const Node = require('snapdragon-node'); const Token = require('snapdragon-token'); // create a new AST node const node = new Node({ type: 'star', value: '*' }); // convert a Lexer Token into an AST Node const token = new Token({ type: 'star', value: '*' }); const node = new Node(token); ``` ## Node objects AST Nodes are represented as `Node` objects that implement the following interface: ```js interface Node { type: string; value: string | undefined nodes: array | undefined } ``` * `type` **{string}** - A string representing the node variant type. This property is often used for classifying the purpose or nature of the node, so that parsers or compilers can determine what to do with it. * `value` **{string|undefined}** (optional) - In general, value should only be a string when `node.nodes` is undefined. This is not reinforced, but is considered good practice. Use a different property name to store arbitrary strings on the node when `node.nodes` is an array. * `nodes` **{array|undefined}** (optional) - array of child nodes A number of useful methods and non-enumerable properties are also exposed for adding, finding and removing child nodes, etc. Continue reading the API documentation for more details. ## Node API ### [Node](index.js#L20) Create a new AST `Node` with the given `type` and `value`, or an object to initialize with. **Params** * `type` **{object|string}**: Either an object to initialize with, or a string to be used as the `node.type`. * `value` **{string|boolean}**: If the first argument is a string, the second argument may be a string value to set on `node.value`. * `clone` **{boolean}**: When an object is passed as the first argument, pass true as the last argument to deep clone values before assigning them to the new node. * `returns` **{Object}**: node instance **Example** ```js console.log(new Node({ type: 'star', value: '*' })); console.log(new Node('star', '*')); // both result in => Node { type: 'star', value: '*' } ``` ### [.clone](index.js#L50) Return a clone of the node. Values that are arrays or plain objects are deeply cloned. * `returns` **{Object}**: returns a clone of the node **Example** ```js const node = new Node({type: 'star', value: '*'}); consle.log(node.clone() !== node); //=> true ``` ### [.stringify](index.js#L68) Return a string created from `node.value` and/or recursively visiting over `node.nodes`. * `returns` **{String}** **Example** ```js const node = new Node({type: 'star', value: '*'}); consle.log(node.stringify()); //=> '*' ``` ### [.push](index.js#L88) Push a child node onto the `node.nodes` array. **Params** * `node` **{Object}** * `returns` **{Number}**: Returns the length of `node.nodes`, like `Array.push` **Example** ```js const foo = new Node({type: 'foo'}); const bar = new Node({type: 'bar'}); foo.push(bar); ``` ### [.unshift](index.js#L117) Unshift a child node onto `node.nodes`, and set `node` as the parent on `child.parent`. **Params** * `node` **{Object}** * `returns` **{Number}**: Returns the length of `node.nodes` **Example** ```js const foo = new Node({type: 'foo'}); const bar = new Node({type: 'bar'}); foo.unshift(bar); ``` ### [.pop](index.js#L151) Pop a node from `node.nodes`. * `returns` **{Number}**: Returns the popped `node` **Example** ```js const node = new Node({type: 'foo'}); node.push(new Node({type: 'a'})); node.push(new Node({type: 'b'})); node.push(new Node({type: 'c'})); node.push(new Node({type: 'd'})); console.log(node.nodes.length); //=> 4 node.pop(); console.log(node.nodes.length); //=> 3 ``` ### [.shift](index.js#L178) Shift a node from `node.nodes`. * `returns` **{Object}**: Returns the shifted `node` **Example** ```js const node = new Node({type: 'foo'}); node.push(new Node({type: 'a'})); node.push(new Node({type: 'b'})); node.push(new Node({type: 'c'})); node.push(new Node({type: 'd'})); console.log(node.nodes.length); //=> 4 node.shift(); console.log(node.nodes.length); //=> 3 ``` ### [.remove](index.js#L197) Remove `node` from `node.nodes`. **Params** * `node` **{Object}** * `returns` **{Object}**: Returns the removed node. **Example** ```js node.remove(childNode); ``` ### [.find](index.js#L228) Get the first child node from `node.nodes` that matches the given `type`. If `type` is a number, the child node at that index is returned. **Params** * `type` **{String}** * `returns` **{Object}**: Returns a child node or undefined. **Example** ```js const child = node.find(1); //<= index of the node to get const child = node.find('foo'); //<= node.type of a child node const child = node.find(/^(foo|bar)$/); //<= regex to match node.type const child = node.find(['foo', 'bar']); //<= array of node.type(s) ``` ### [.has](index.js#L259) Returns true if `node.nodes` array contains the given `node`. **Params** * `type` **{String}** * `returns` **{Boolean}** **Example** ```js const foo = new Node({type: 'foo'}); const bar = new Node({type: 'bar'}); cosole.log(foo.has(bar)); // false foo.push(bar); cosole.log(foo.has(bar)); // true ``` ### [.hasType](index.js#L284) Return true if the `node.nodes` has the given `type`. **Params** * `type` **{String}** * `returns` **{Boolean}** **Example** ```js const foo = new Node({type: 'foo'}); const bar = new Node({type: 'bar'}); foo.push(bar); cosole.log(foo.hasType('qux')); // false cosole.log(foo.hasType(/^(qux|bar)$/)); // true cosole.log(foo.hasType(['qux', 'bar'])); // true ``` ### [.isType](index.js#L303) Return true if the node is the given `type`. **Params** * `type` **{String}** * `returns` **{Boolean}** **Example** ```js const node = new Node({type: 'bar'}); cosole.log(node.isType('foo')); // false cosole.log(node.isType(/^(foo|bar)$/)); // true cosole.log(node.isType(['foo', 'bar'])); // true ``` ### [.isEmpty](index.js#L323) Returns true if `node.value` is an empty string, or `node.nodes` does not contain any non-empty text nodes. **Params** * `fn` **{Function}**: (optional) Filter function that is called on `node` and/or child nodes. `isEmpty` will return false immediately when the filter function returns false on any nodes. * `returns` **{Boolean}** **Example** ```js const node = new Node({type: 'text'}); node.isEmpty(); //=> true node.value = 'foo'; node.isEmpty(); //=> false ``` ### [.isInside](index.js#L342) Returns true if the node has an ancestor node of the given `type` **Params** * `type` **{String}** * `returns` **{Boolean}** **Example** ```js const box = new Node({type: 'box'}); const marble = new Node({type: 'marble'}); box.push(marble); marble.isInside('box'); //=> true ``` ### [.siblings](index.js#L365) Get the siblings array, or `null` if it doesn't exist. * `returns` **{Array}** **Example** ```js const foo = new Node({type: 'foo'}); const bar = new Node({type: 'bar'}); const baz = new Node({type: 'baz'}); foo.push(bar); foo.push(baz); console.log(bar.siblings.length) // 2 console.log(baz.siblings.length) // 2 ``` ### [.index](index.js#L393) Calculate the node's current index on `node.parent.nodes`, or `-1` if the node does not have a parent, or is not on `node.parent.nodes`. * `returns` **{Number}** **Example** ```js const foo = new Node({type: 'foo'}); const bar = new Node({type: 'bar'}); const baz = new Node({type: 'baz'}); const qux = new Node({type: 'qux'}); foo.push(bar); foo.push(baz); foo.unshift(qux); console.log(bar.index) // 1 console.log(baz.index) // 2 console.log(qux.index) // 0 ``` ### [.prev](index.js#L424) Get the previous node from the [siblings](#siblings) array or `null`. * `returns` **{Object}** **Example** ```js const foo = new Node({type: 'foo'}); const bar = new Node({type: 'bar'}); const baz = new Node({type: 'baz'}); foo.push(bar); foo.push(baz); console.log(baz.prev.type) // 'bar' ``` ### [.next](index.js#L453) Get the next element from the [siblings](#siblings) array, or `null` if a next node does not exist. * `returns` **{Object}** **Example** ```js const parent = new Node({type: 'root'}); const foo = new Node({type: 'foo'}); const bar = new Node({type: 'bar'}); const baz = new Node({type: 'baz'}); parent.push(foo); parent.push(bar); parent.push(baz); console.log(foo.next.type) // 'bar' console.log(bar.next.type) // 'baz' ``` ### [.first](index.js#L480) Get the first child node from `node.nodes`. * `returns` **{Object}**: The first node, or undefiend **Example** ```js const foo = new Node({type: 'foo'}); const bar = new Node({type: 'bar'}); const baz = new Node({type: 'baz'}); const qux = new Node({type: 'qux'}); foo.push(bar); foo.push(baz); foo.push(qux); console.log(foo.first.type) // 'bar' ``` ### [.last](index.js#L504) Get the last child node from `node.nodes`. * `returns` **{Object}**: The last node, or undefiend **Example** ```js const foo = new Node({type: 'foo'}); const bar = new Node({type: 'bar'}); const baz = new Node({type: 'baz'}); const qux = new Node({type: 'qux'}); foo.push(bar); foo.push(baz); foo.push(qux); console.log(foo.last.type) // 'qux' ``` ### [.depth](index.js#L525) Get the `node.depth`. The root node has a depth of 0. Add 1 to child nodes for each level of nesting. * `returns` **{Object}**: The last node, or undefiend **Example** ```js const foo = new Node({type: 'foo'}); foo.push(bar); console.log(foo.depth) // 1 console.log(bar.depth) // 2 ``` ### [Node#isNode](index.js#L545) Static method that returns true if the given value is a node. **Params** * `node` **{Object}** * `returns` **{Boolean}** **Example** ```js const Node = require('snapdragon-node'); const node = new Node({type: 'foo'}); console.log(Node.isNode(node)); //=> true console.log(Node.isNode({})); //=> false ``` ### Non-enumerable properties * `node.isNode` **{boolean}** - this value is set to `true` when a node is created. This can be useful in situationas as a fast alternative to using `instanceof Node` if you [need to determine](#nodeisnode) if a value is a `node` object. * `node.size` **{number}** - the number of child nodes that have been pushed or unshifted onto `node.nodes` using the node's API. This is useful for determining if nodes were added to `node.nodes` without using `node.push()` or `node.unshift()` (for example: `if (node.nodes && node.size !== node.nodes.length)`) * `node.parent` **{object}** (instance of Node) ## Release history See [the changelog](changelog.md). ## About
Contributing Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). Please read the [contributing guide](.github/contributing.md) for advice on opening issues, pull requests, and coding standards.
Running Tests Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command: ```sh $ npm install && npm test ```
Building docs _(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_ To generate the readme, run the following command: ```sh $ npm install -g verbose/verb#dev verb-generate-readme && verb ```
### Related projects You might also be interested in these projects: * [breakdance](https://www.npmjs.com/package/breakdance): Breakdance is a node.js library for converting HTML to markdown. Highly pluggable, flexible and easy… [more](http://breakdance.io) | [homepage](http://breakdance.io "Breakdance is a node.js library for converting HTML to markdown. Highly pluggable, flexible and easy to use. It's time for your markup to get down.") * [snapdragon-capture](https://www.npmjs.com/package/snapdragon-capture): Snapdragon plugin that adds a capture method to the parser instance. | [homepage](https://github.com/jonschlinkert/snapdragon-capture "Snapdragon plugin that adds a capture method to the parser instance.") * [snapdragon-cheerio](https://www.npmjs.com/package/snapdragon-cheerio): Snapdragon plugin for converting a cheerio AST to a snapdragon AST. | [homepage](https://github.com/jonschlinkert/snapdragon-cheerio "Snapdragon plugin for converting a cheerio AST to a snapdragon AST.") * [snapdragon-util](https://www.npmjs.com/package/snapdragon-util): Utilities for the snapdragon parser/compiler. | [homepage](https://github.com/here-be/snapdragon-util "Utilities for the snapdragon parser/compiler.") * [snapdragon](https://www.npmjs.com/package/snapdragon): Easy-to-use plugin system for creating powerful, fast and versatile parsers and compilers, with built-in source-map… [more](https://github.com/here-be/snapdragon) | [homepage](https://github.com/here-be/snapdragon "Easy-to-use plugin system for creating powerful, fast and versatile parsers and compilers, with built-in source-map support.") ### Author **Jon Schlinkert** * [GitHub Profile](https://github.com/jonschlinkert) * [Twitter Profile](https://twitter.com/jonschlinkert) * [LinkedIn Profile](https://linkedin.com/in/jonschlinkert) ### License Copyright © 2018, [Jon Schlinkert](https://github.com/jonschlinkert). Released under the [MIT License](LICENSE). *** _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on November 24, 2018._snapdragon-node-3.0.0/example.js000066400000000000000000000002541337621353300165710ustar00rootroot00000000000000const Node = require('./'); console.log(new Node({ type: 'star', value: '*' })); console.log(new Node('star', '*')); // both result in => Node { type: 'star', value: '*' } snapdragon-node-3.0.0/index.js000066400000000000000000000405461337621353300162550ustar00rootroot00000000000000'use strict'; /** * Create a new AST `Node` with the given `type` and `value`, or an * object to initialize with. * * ```js * console.log(new Node({ type: 'star', value: '*' })); * console.log(new Node('star', '*')); * // both result in => Node { type: 'star', value: '*' } * ``` * @name Node * @param {object|string} `type` Either an object to initialize with, or a string to be used as the `node.type`. * @param {string|boolean} `value` If the first argument is a string, the second argument may be a string value to set on `node.value`. * @param {boolean} `clone` When an object is passed as the first argument, pass true as the last argument to deep clone values before assigning them to the new node. * @return {Object} node instance * @api public */ class Node { constructor(type, value, clone) { define(this, 'isNode', true); define(this, 'parent', null); define(this, 'size', 0); if (isObject(type)) { assign(this, type, clone); } else { this.type = type; if (value != null) { this.value = value; } } } /** * Return a clone of the node. Values that are arrays or plain objects * are deeply cloned. * * ```js * const node = new Node({type: 'star', value: '*'}); * consle.log(node.clone() !== node); * //=> true * ``` * @name .clone * @return {Object} returns a clone of the node * @api public */ clone() { return new this.constructor(this, null, true); } /** * Return a string created from `node.value` and/or recursively * visiting over `node.nodes`. * * ```js * const node = new Node({type: 'star', value: '*'}); * consle.log(node.stringify()); * //=> '*' * ``` * @name .stringify * @return {String} * @api public */ stringify(fn = n => n.value) { let str = ''; visit(this, n => (str += fn(n))); return str; } /** * Push a child node onto the `node.nodes` array. * * ```js * const foo = new Node({type: 'foo'}); * const bar = new Node({type: 'bar'}); * foo.push(bar); * ``` * @name .push * @param {Object} `node` * @return {Number} Returns the length of `node.nodes`, like `Array.push` * @api public */ push(node) { if (!node) return; assert(isObject(node), 'expected node to be an object'); assert(node !== this, 'node should not be the same as node.parent'); if (!this.constructor.isNode(node)) { node = new this.constructor(node); } this.nodes = this.nodes || []; node.parent = this; ensureNodes(node); this.size++; return this.nodes.push(node); } /** * Unshift a child node onto `node.nodes`, and set `node` as * the parent on `child.parent`. * * ```js * const foo = new Node({type: 'foo'}); * const bar = new Node({type: 'bar'}); * foo.unshift(bar); * ``` * @name .unshift * @param {Object} `node` * @return {Number} Returns the length of `node.nodes` * @api public */ unshift(node) { if (!node) return; assert(isObject(node), 'expected node to be an object'); assert(node !== this, 'node should not be the same as node.parent'); if (!this.constructor.isNode(node)) { node = new this.constructor(node); } this.nodes = this.nodes || []; node.parent = this; ensureNodes(node); this.size++; return this.nodes.unshift(node); } /** * Pop a node from `node.nodes`. * * ```js * const node = new Node({type: 'foo'}); * node.push(new Node({type: 'a'})); * node.push(new Node({type: 'b'})); * node.push(new Node({type: 'c'})); * node.push(new Node({type: 'd'})); * console.log(node.nodes.length); * //=> 4 * node.pop(); * console.log(node.nodes.length); * //=> 3 * ``` * @name .pop * @return {Number} Returns the popped `node` * @api public */ pop() { if (this.nodes && this.nodes.length) { this.size--; return this.nodes.pop(); } } /** * Shift a node from `node.nodes`. * * ```js * const node = new Node({type: 'foo'}); * node.push(new Node({type: 'a'})); * node.push(new Node({type: 'b'})); * node.push(new Node({type: 'c'})); * node.push(new Node({type: 'd'})); * console.log(node.nodes.length); * //=> 4 * node.shift(); * console.log(node.nodes.length); * //=> 3 * ``` * @name .shift * @return {Object} Returns the shifted `node` * @api public */ shift() { if (this.nodes && this.nodes.length) { this.size--; return this.nodes.shift(); } } /** * Remove `node` from `node.nodes`. * * ```js * node.remove(childNode); * ``` * @name .remove * @param {Object} `node` * @return {Object} Returns the removed node. * @api public */ remove(node) { if (!this.nodes) return []; assert(this.constructor.isNode(node), 'expected an instance of Node'); assert(node !== this, 'cannot remove a node from itself'); const idx = this.nodes.indexOf(node); node.index = -1; if (idx !== -1) { this.size--; return this.nodes.splice(idx, 1); } return []; } /** * Get the first child node from `node.nodes` that matches the given `type`. * If `type` is a number, the child node at that index is returned. * * ```js * const child = node.find(1); //<= index of the node to get * const child = node.find('foo'); //<= node.type of a child node * const child = node.find(/^(foo|bar)$/); //<= regex to match node.type * const child = node.find(['foo', 'bar']); //<= array of node.type(s) * ``` * @name .find * @param {String} `type` * @return {Object} Returns a child node or undefined. * @api public */ find(type, n = 0) { if (!Array.isArray(this.nodes) || this.nodes.length === 0) return null; if (typeof type === 'number') return this.nodes[type]; for (let i = n; i < this.nodes.length; i++) { const node = this.nodes[i]; if (isType(node, type)) { return node; } } } visit(fn) { return visit(this, fn); } /** * Returns true if `node.nodes` array contains the given `node`. * * ```js * const foo = new Node({type: 'foo'}); * const bar = new Node({type: 'bar'}); * cosole.log(foo.has(bar)); // false * foo.push(bar); * cosole.log(foo.has(bar)); // true * ``` * @name .has * @param {String} `type` * @return {Boolean} * @api public */ has(node) { if (this.constructor.isNode(node)) { return Array.isArray(this.nodes) && this.nodes.includes(node); } return this.hasType(node); } /** * Return true if the `node.nodes` has the given `type`. * * ```js * const foo = new Node({type: 'foo'}); * const bar = new Node({type: 'bar'}); * foo.push(bar); * * cosole.log(foo.hasType('qux')); // false * cosole.log(foo.hasType(/^(qux|bar)$/)); // true * cosole.log(foo.hasType(['qux', 'bar'])); // true * ``` * @name .hasType * @param {String} `type` * @return {Boolean} * @api public */ hasType(type) { return Array.isArray(this.nodes) && this.nodes.find(node => isType(node, type)); } /** * Return true if the node is the given `type`. * * ```js * const node = new Node({type: 'bar'}); * cosole.log(node.isType('foo')); // false * cosole.log(node.isType(/^(foo|bar)$/)); // true * cosole.log(node.isType(['foo', 'bar'])); // true * ``` * @name .isType * @param {String} `type` * @return {Boolean} * @api public */ isType(type) { return isType(this, type); } /** * Returns true if `node.value` is an empty string, or `node.nodes` does * not contain any non-empty text nodes. * * ```js * const node = new Node({type: 'text'}); * node.isEmpty(); //=> true * node.value = 'foo'; * node.isEmpty(); //=> false * ``` * @name .isEmpty * @param {Function} `fn` (optional) Filter function that is called on `node` and/or child nodes. `isEmpty` will return false immediately when the filter function returns false on any nodes. * @return {Boolean} * @api public */ isEmpty(fn) { return isEmpty(this, fn); } /** * Returns true if the node has an ancestor node of the given `type` * * ```js * const box = new Node({type: 'box'}); * const marble = new Node({type: 'marble'}); * box.push(marble); * marble.isInside('box'); //=> true * ``` * @name .isInside * @param {String} `type` * @return {Boolean} * @api public */ isInside(type) { return this.parent && (this.parent.type === type || this.parent.isInside(type)); } /** * Get the siblings array, or `null` if it doesn't exist. * * ```js * const foo = new Node({type: 'foo'}); * const bar = new Node({type: 'bar'}); * const baz = new Node({type: 'baz'}); * foo.push(bar); * foo.push(baz); * * console.log(bar.siblings.length) // 2 * console.log(baz.siblings.length) // 2 * ``` * @getter * @name .siblings * @return {Array} * @api public */ get siblings() { return this.parent ? this.parent.nodes : null; } /** * Calculate the node's current index on `node.parent.nodes`, or `-1` if the * node does not have a parent, or is not on `node.parent.nodes`. * * ```js * const foo = new Node({type: 'foo'}); * const bar = new Node({type: 'bar'}); * const baz = new Node({type: 'baz'}); * const qux = new Node({type: 'qux'}); * foo.push(bar); * foo.push(baz); * foo.unshift(qux); * * console.log(bar.index) // 1 * console.log(baz.index) // 2 * console.log(qux.index) // 0 * ``` * @setter * @getter * @name .index * @return {Number} * @api public */ set index(index) { define(this, '_index', index); } get index() { if (!Array.isArray(this.siblings)) { return -1; } if (this._index === -1 || this.siblings[this._index] !== this) { define(this, '_index', this.siblings.indexOf(this)); } return this._index; } /** * Get the previous node from the [siblings](#siblings) array or `null`. * * ```js * const foo = new Node({type: 'foo'}); * const bar = new Node({type: 'bar'}); * const baz = new Node({type: 'baz'}); * foo.push(bar); * foo.push(baz); * * console.log(baz.prev.type) // 'bar' * ``` * @getter * @name .prev * @return {Object} * @api public */ get prev() { if (Array.isArray(this.siblings)) { return this.siblings[this.index - 1] || this.parent.prev; } return null; } /** * Get the next element from the [siblings](#siblings) array, or `null` if * a next node does not exist. * * ```js * const parent = new Node({type: 'root'}); * const foo = new Node({type: 'foo'}); * const bar = new Node({type: 'bar'}); * const baz = new Node({type: 'baz'}); * parent.push(foo); * parent.push(bar); * parent.push(baz); * * console.log(foo.next.type) // 'bar' * console.log(bar.next.type) // 'baz' * ``` * @getter * @name .next * @return {Object} * @api public */ get next() { if (Array.isArray(this.siblings)) { return this.siblings[this.index + 1] || this.parent.next; } return null; } /** * Get the first child node from `node.nodes`. * * ```js * const foo = new Node({type: 'foo'}); * const bar = new Node({type: 'bar'}); * const baz = new Node({type: 'baz'}); * const qux = new Node({type: 'qux'}); * foo.push(bar); * foo.push(baz); * foo.push(qux); * * console.log(foo.first.type) // 'bar' * ``` * @getter * @name .first * @return {Object} The first node, or undefiend * @api public */ get first() { return Array.isArray(this.nodes) ? this.nodes[0] : null; } /** * Get the last child node from `node.nodes`. * * ```js * const foo = new Node({type: 'foo'}); * const bar = new Node({type: 'bar'}); * const baz = new Node({type: 'baz'}); * const qux = new Node({type: 'qux'}); * foo.push(bar); * foo.push(baz); * foo.push(qux); * * console.log(foo.last.type) // 'qux' * ``` * @getter * @name .last * @return {Object} The last node, or undefiend * @api public */ get last() { return Array.isArray(this.nodes) ? this.nodes[this.nodes.length - 1] : null; } /** * Get the `node.depth`. The root node has a depth of 0. Add 1 to child nodes * for each level of nesting. * * ```js * const foo = new Node({type: 'foo'}); * foo.push(bar); * * console.log(foo.depth) // 1 * console.log(bar.depth) // 2 * ``` * @getter * @name .depth * @return {Object} The last node, or undefiend * @api public */ get depth() { return this.parent ? this.parent.depth + 1 : 0; } /** * Static method that returns true if the given value is a node. * * ```js * const Node = require('snapdragon-node'); * const node = new Node({type: 'foo'}); * console.log(Node.isNode(node)); //=> true * console.log(Node.isNode({})); //=> false * ``` * @name Node#isNode * @param {Object} `node` * @returns {Boolean} * @api public * @static */ static isNode(node) { return isObject(node) && (node instanceof this || node.isNode === true); } } /** * Simplified assertion. Throws an error is `value` is not true. */ function assert(value, message) { if (value !== true) throw new Error(message); } function expect(value, name) { assert(value, 'expected ' + name + ' to be an instance of Node'); } function hasOwn(obj, prop) { return Object.hasOwnProperty.call(obj, prop); } function isEmpty(node, fn) { expect(Node.isNode(node), 'node'); if (!Array.isArray(node.nodes)) { if (typeof fn === 'function') { return fn(node); } return !node.value; } if (node.nodes.length === 0) { return true; } for (const child of node.nodes) { if (!isEmpty(child, fn)) { return false; } } return true; } function isType(node, type) { expect(Node.isNode(node), 'node'); switch (typeOf(type)) { case 'string': return node.type === type; case 'regexp': return type.test(node.type); case 'array': for (const key of type) { if (node.isType(node, key)) { return true; } } return false; default: { throw new TypeError('expected "type" to be an array, string or regexp'); } } } function isObject(val) { return typeOf(val) === 'object'; } function typeOf(val) { if (typeof val === 'string') return 'string'; if (Array.isArray(val)) return 'array'; if (val instanceof RegExp) { return 'regexp'; } if (val === void 0) return 'undefiend'; if (val === null) return 'null'; return typeof val; } /** * assign `token` properties to `node` */ function assign(node, token, clone) { copy(node, token, clone); ensureNodes(node, clone); if (token.constructor && token.constructor.name === 'Token') { copy(node, token.constructor.prototype, clone); } } function copy(receiver, provider, clone) { const descriptors = Object.getOwnPropertyDescriptors(provider); for (const key in descriptors) { if (key === 'constructor' || key in receiver) continue; const desc = descriptors[key]; if (hasOwn(desc, 'value') && clone === true) { desc.value = cloneDeep(desc.value); } Object.defineProperty(receiver, key, desc); } } function ensureNodes(node, clone) { if (!node.nodes) return; if (Array.isArray(node.nodes)) { const len = node.nodes.length; for (let i = 0; i < len; i++) { let child = node.nodes[i]; if (!Node.isNode(child)) { child = node.nodes[i] = new Node(node.nodes[i], null, true); child.parent = node; child.index = i; } ensureNodes(child); } node.size = len; } } /** * Deeply clone plain objects and arrays. */ function cloneDeep(value) { const obj = {}; switch (typeOf(value)) { case 'array': return value.map(ele => cloneDeep(ele)); case 'object': for (const key of Object.keys(value)) { obj[key] = cloneDeep(value[key]); } return obj; default: { return value; } } } function visit(node, fn) { fn(node); return node.nodes ? mapVisit(node, fn) : node; } function mapVisit(node, fn) { node.nodes.forEach(n => visit(n, fn)); return node; } function define(obj, key, value) { Object.defineProperty(obj, key, { configurable: true, enumerable: false, writable: true, value: value }); } /** * Expose `Node` */ exports = module.exports = Node; snapdragon-node-3.0.0/package.json000066400000000000000000000024141337621353300170660ustar00rootroot00000000000000{ "name": "snapdragon-node", "description": "Class for creating AST nodes.", "version": "3.0.0", "homepage": "https://github.com/here-be/snapdragon-node", "author": "Jon Schlinkert (https://github.com/jonschlinkert)", "repository": "here-be/snapdragon-node", "bugs": { "url": "https://github.com/here-be/snapdragon-node/issues" }, "license": "MIT", "files": [ "index.js" ], "main": "index.js", "engines": { "node": ">=4" }, "scripts": { "test": "mocha", "cover": "nyc --reporter=text --reporter=html mocha" }, "devDependencies": { "define-property": "^2.0.2", "gulp-format-md": "^2.0.0", "mocha": "^5.2.0", "nyc": "^13.1.0", "snapdragon": "^0.12.0" }, "keywords": [ "ast", "compile", "compiler", "convert", "node", "parse", "parser", "plugin", "render", "snapdragon", "snapdragonplugin", "token", "transform" ], "verb": { "layout": "default", "tasks": [ "readme" ], "plugins": [ "gulp-format-md" ], "related": { "list": [ "breakdance", "snapdragon", "snapdragon-capture", "snapdragon-cheerio", "snapdragon-util" ] }, "lint": { "reflinks": true } } } snapdragon-node-3.0.0/test/000077500000000000000000000000001337621353300155565ustar00rootroot00000000000000snapdragon-node-3.0.0/test/test.js000066400000000000000000000324721337621353300171030ustar00rootroot00000000000000'use strict'; require('mocha'); const assert = require('assert'); const define = require('define-property'); const Parser = require('snapdragon/lib/parser'); const BaseNode = require('..'); let parser; let ast; class Node extends BaseNode { define(key, value) { define(this, key, value); return this; } } describe('snapdragon-node', function() { beforeEach(function() { parser = new Parser({ Node }) .set('text', function() { let match = this.match(/^[a-z]+/); if (match) { return this.node(match[0]); } }) .set('slash', function() { let match = this.match(/^\//); if (match) { return this.node(match[0]); } }) .set('star', function() { let match = this.match(/^\*/); if (match) { return this.node(match[0]); } }); ast = new Node(parser.parse('a/*/c')); }); describe('node', function() { it('should export a function', function() { assert.equal(typeof Node, 'function'); }); it('should create a new Node with the given object', function() { let node = new Node({ value: '*', type: 'star' }); assert.equal(node.value, '*'); assert.equal(node.type, 'star'); }); it('should create a new Node with the given position', function() { let pos = parser.position(); let node = pos(new Node()); assert(node.position); assert(node.position.start); assert(node.position.start.line); assert(node.position.start.column); assert(node.position.end); assert(node.position.end.line); assert(node.position.end.column); }); it('should create a new Node with the given position and value', function() { let pos = parser.position(); let node = pos(new Node(null, '*')); assert.equal(node.value, '*'); assert(node.position); assert(node.position.start); assert(node.position.start.line); assert(node.position.start.column); assert(node.position.end); assert(node.position.end.line); assert(node.position.end.column); }); it('should create a new Node with the given position, type, and value', function() { let pos = parser.position(); let node = pos(new Node('star', '*')); assert.equal(node.value, '*'); assert.equal(node.type, 'star'); assert(node.position); assert(node.position.start); assert(node.position.start.line); assert(node.position.start.column); assert(node.position.end); assert(node.position.end.line); assert(node.position.end.column); }); it('should create a new Node with the given position and object', function() { let pos = parser.position(); let node = pos(new Node({ value: '*', type: 'star' })); assert.equal(node.value, '*'); assert.equal(node.type, 'star'); assert(node.position); assert(node.position.start); assert(node.position.start.line); assert(node.position.start.column); assert(node.position.end); assert(node.position.end.line); assert(node.position.end.column); }); it('should extend type and value onto a node', function() { let node = new Node({ type: 'foo', value: 'bar' }); assert.equal(node.type, 'foo'); assert.equal(node.value, 'bar'); }); it('should extend arbitrary properties onto a node', function() { let node = new Node({ type: 'foo', value: 'bar', baz: 'qux' }); assert.equal(node.baz, 'qux'); }); it('should not extend existing getter properties onto a node', function() { let node = new Node({ type: 'foo', value: 'bar', index: 11 }); assert.equal(node.index, -1); }); }); describe('.isType', function() { it('should return true if the node is the given type', function() { assert(ast.isType('root')); assert(ast.last.isType('eos')); }); }); describe('.hasType', function() { it('should return true if node.nodes has the given type', function() { assert(ast.hasType('star')); assert(!ast.hasType('foo')); }); it('should return false when a node does not exist', function() { let node = new Node('foo'); assert.equal(node.hasType('slslsllsls'), false); }); }); describe('.first', function() { it('should get the first node from `node.nodes`', function() { assert(ast.first); assert.equal(ast.first.type, 'bos'); }); it('should return null when no nodes exist', function() { let node = new Node('foo'); assert.equal(node.first, null); }); }); describe('.last', function() { it('should get the last node from `node.nodes`', function() { assert(ast.last); assert.equal(ast.last.type, 'eos'); }); it('should return null when no nodes exist', function() { let node = new Node('foo'); assert.equal(node.last, null); }); }); describe('.index', function() { it('should get the index of a node from node.parent.nodes', function() { let foo = new Node({ type: 'foo' }); let bar = new Node({ type: 'bar' }); let baz = new Node({ type: 'baz' }); let qux = new Node({ type: 'qux' }); foo.unshift(qux); foo.push(bar); foo.push(baz); assert.equal(bar.index, 1); assert.equal(baz.index, 2); assert.equal(qux.index, 0); }); it('should allow an index to be set but returns the correct index', function() { let node = new Node('foo'); let foo = new Node('foo'); node.push(foo); foo.index = 42; assert.equal(foo.index, 0); }); it('should return -1 when siblings do not exist', function() { let foo = new Node('foo'); assert.equal(foo.index, -1); }); }); describe('.siblings', function() { it('should get `node.parent.nodes`', function() { let foo = new Node({ type: 'foo' }); let bar = new Node({ type: 'bar' }); let baz = new Node({ type: 'baz' }); let qux = new Node({ type: 'qux' }); foo.push(bar); foo.push(baz); foo.unshift(qux); assert.equal(foo.siblings, null); assert.equal(bar.siblings.length, 3); assert.equal(baz.siblings.length, 3); assert.equal(qux.siblings.length, 3); }); it('should throw an error if set', function() { assert.throws(function() { let node = new Node('foo'); node.siblings = []; }); }); }); describe('.push', function() { it('should push nodes onto node.nodes', function() { let node = new Node({ type: 'foo' }); assert(!node.nodes); node.push(new Node({ type: 'a' })); assert.equal(node.nodes.length, 1); node.push(new Node({ type: 'b' })); assert.equal(node.nodes.length, 2); node.push(new Node({ type: 'c' })); assert.equal(node.nodes.length, 3); node.push(new Node({ type: 'd' })); assert.equal(node.nodes.length, 4); }); }); describe('.unshift', function() { it('should unshift nodes onto node.nodes', function() { let node = new Node({ type: 'foo' }); assert(!node.nodes); node.unshift(new Node({ type: 'a' })); assert.equal(node.nodes.length, 1); node.unshift(new Node({ type: 'b' })); assert.equal(node.nodes.length, 2); node.unshift(new Node({ type: 'c' })); assert.equal(node.nodes.length, 3); node.unshift(new Node({ type: 'd' })); assert.equal(node.nodes.length, 4); }); }); describe('.pop', function() { it('should remove the last node from node.nodes', function() { let node = new Node({ type: 'foo' }); node.push(new Node({ type: 'a' })); node.push(new Node({ type: 'b' })); node.push(new Node({ type: 'c' })); node.push(new Node({ type: 'd' })); assert.equal(node.nodes.length, 4); node.pop(); assert.equal(node.nodes.length, 3); }); }); describe('.shift', function() { it('should remove the last node from node.nodes', function() { let node = new Node({ type: 'foo' }); node.push(new Node({ type: 'a' })); node.push(new Node({ type: 'b' })); node.push(new Node({ type: 'c' })); node.push(new Node({ type: 'd' })); assert.equal(node.nodes.length, 4); node.shift(); assert.equal(node.nodes.length, 3); }); it('should not blow up when no nodes exist', function() { let node = new Node({ type: 'foo' }); node.shift(); assert(!node.nodes); }); }); describe('.remove', function() { it('should not do anything when a node does not exist', function() { let node = new Node({ type: 'foo' }); assert(!node.nodes); node.remove(new Node({ type: 'a' })); assert(!node.nodes); }); it('should remove the given node from node.nodes', function() { let node = new Node({ type: 'foo' }); let a = new Node({ type: 'a' }); let b = new Node({ type: 'b' }); let c = new Node({ type: 'c' }); let d = new Node({ type: 'd' }); node.push(a); node.push(b); node.push(c); node.push(d); assert.equal(node.nodes.length, 4); assert.equal(a.index, 0); assert.equal(b.index, 1); assert.equal(c.index, 2); assert.equal(d.index, 3); node.remove(b); assert.equal(node.nodes.length, 3); assert.equal(a.index, 0); assert.equal(b.index, -1); assert.equal(c.index, 1); assert.equal(d.index, 2); assert.equal(node.find('a'), a); assert.equal(node.find('b'), null); assert.equal(node.find('c'), c); assert.equal(node.find('d'), d); }); }); describe('.prev', function() { it('should throw an error when setter is set', function() { assert.throws(function() { let node = new Node('foo'); node.prev = new Node('bar'); }); }); it('should get the prev node from node.nodes', function() { let foo = new Node({ type: 'foo' }); let bar = new Node({ type: 'bar' }); let baz = new Node({ type: 'baz' }); foo.push(bar); foo.push(baz); assert.equal(bar.prev, null); assert.equal(baz.prev.type, 'bar'); }); it('should get the prev node from `node.parent.nodes`', function() { let parent = new Node({ type: 'parent' }); let a = new Node({ type: 'a' }); let z = new Node({ type: 'z' }); let foo = new Node({ type: 'foo' }); let bar = new Node({ type: 'bar' }); let baz = new Node({ type: 'baz' }); foo.push(bar); foo.push(baz); parent.push(a); parent.push(foo); parent.push(z); assert.equal(bar.prev.type, 'a'); assert.equal(baz.prev.type, 'bar'); }); }); describe('.next', function() { it('should throw an error when setter is set', function() { assert.throws(function() { let node = new Node('foo'); node.next = new Node('bar'); }); }); it('should get the next node from `node.nodes`', function() { let foo = new Node({ type: 'foo' }); let bar = new Node({ type: 'bar' }); let baz = new Node({ type: 'baz' }); foo.push(bar); foo.push(baz); assert.equal(bar.next.type, 'baz'); assert.equal(baz.next, null); }); it('should get the next node from `node.parent.nodes`', function() { let parent = new Node({ type: 'parent' }); let a = new Node({ type: 'a' }); let z = new Node({ type: 'z' }); let foo = new Node({ type: 'foo' }); let bar = new Node({ type: 'bar' }); let baz = new Node({ type: 'baz' }); foo.push(bar); foo.push(baz); parent.push(a); parent.push(foo); parent.push(z); assert.equal(bar.next.type, 'baz'); assert.equal(baz.next.type, 'z'); }); }); describe('.find', function() { it('should get a node by type from `node.nodes`', function() { assert.equal(ast.find('text').type, 'text'); assert.equal(ast.find('star').type, 'star'); }); it('should get a node by index from `node.nodes`', function() { assert.equal(ast.find(0).type, 'bos'); assert.equal(ast.find(1).type, 'text'); }); it('should return null when a node does not exist', function() { let node = new Node('foo'); assert.equal(node.find('slslsllsls'), null); }); }); describe('.push', function() { it('should add a node to `node.nodes`', function() { let node = new Node({ type: 'foo' }); ast.push(node); assert.equal(ast.last.type, 'foo'); }); it('should set the parent on the given node', function() { let node = new Node({ type: 'foo' }); ast.push(node); assert(ast === node.parent); }); it('should set the parent.nodes as siblings', function() { let node = new Node({ type: 'foo' }); ast.push(node); assert.equal(node.siblings.length, 8); }); it('should get the node.index from siblings', function() { let node = new Node({ type: 'foo' }); ast.push(node); assert.equal(node.index, 7); }); }); describe('.remove', function() { it('should remove a node from `node.nodes`', function() { let node = new Node({ type: 'brace', nodes: [] }); let two = new Node('two', 'text'); node.push(new Node('one', 'text')); node.push(two); node.push(new Node('three', 'text')); assert.equal(node.nodes.length, 3); node.remove(two); assert.equal(node.nodes.length, 2); }); }); });