├── .verb.md
├── .travis.yml
├── .gitattributes
├── .editorconfig
├── .gitignore
├── gulpfile.js
├── LICENSE
├── package.json
├── CHANGELOG.md
├── .github
└── contributing.md
├── .eslintrc.json
├── test
├── support
│ └── index.js
└── test.js
├── README.md
└── index.js
/.verb.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | ```js
4 | var util = require('snapdragon-util');
5 | ```
6 |
7 | ## API
8 | {%= apidocs("index.js") %}
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | os:
3 | - linux
4 | - osx
5 | language: node_js
6 | node_js:
7 | - node
8 | - '9'
9 | - '8'
10 | - '7'
11 | - '6'
12 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Enforce Unix newlines
2 | * text eol=lf
3 |
4 | # binaries
5 | *.ai binary
6 | *.psd binary
7 | *.jpg binary
8 | *.gif binary
9 | *.png binary
10 | *.jpeg binary
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org/
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [{**/{actual,fixtures,expected,templates}/**,*.md}]
13 | trim_trailing_whitespace = false
14 | insert_final_newline = false
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # always ignore files
2 | *.DS_Store
3 | .idea
4 | .vscode
5 | *.sublime-*
6 |
7 | # test related, or directories generated by tests
8 | test/actual
9 | actual
10 | coverage
11 | .nyc*
12 |
13 | # npm
14 | node_modules
15 | npm-debug.log
16 |
17 | # yarn
18 | yarn.lock
19 | yarn-error.log
20 |
21 | # misc
22 | _gh_pages
23 | _draft
24 | _drafts
25 | bower_components
26 | vendor
27 | temp
28 | tmp
29 | TODO.md
30 | package-lock.json
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 | var mocha = require('gulp-mocha');
5 | var istanbul = require('gulp-istanbul');
6 | var eslint = require('gulp-eslint');
7 |
8 | gulp.task('coverage', function() {
9 | return gulp.src('index.js')
10 | .pipe(istanbul())
11 | .pipe(istanbul.hookRequire());
12 | });
13 |
14 | gulp.task('test', ['coverage'], function() {
15 | return gulp.src('test/*.js')
16 | .pipe(mocha({reporter: 'spec'}))
17 | .pipe(istanbul.writeReports());
18 | });
19 |
20 | gulp.task('lint', function() {
21 | return gulp.src(['index.js', 'test/*.js'])
22 | .pipe(eslint())
23 | .pipe(eslint.format());
24 | });
25 |
26 | gulp.task('default', ['test', 'lint']);
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017-2018, Jon Schlinkert.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "snapdragon-util",
3 | "description": "Utilities for the snapdragon parser/compiler.",
4 | "version": "5.0.1",
5 | "homepage": "https://github.com/here-be/snapdragon-util",
6 | "author": "Jon Schlinkert (https://github.com/jonschlinkert)",
7 | "contributors": [
8 | "Jon Schlinkert (http://twitter.com/jonschlinkert)",
9 | "Rouven Weßling (www.rouvenwessling.de)"
10 | ],
11 | "repository": "here-be/snapdragon-util",
12 | "bugs": {
13 | "url": "https://github.com/here-be/snapdragon-util/issues"
14 | },
15 | "license": "MIT",
16 | "files": [
17 | "index.js"
18 | ],
19 | "main": "index.js",
20 | "engines": {
21 | "node": ">=6"
22 | },
23 | "scripts": {
24 | "test": "mocha"
25 | },
26 | "dependencies": {
27 | "kind-of": "^6.0.2"
28 | },
29 | "devDependencies": {
30 | "define-property": "^2.0.0",
31 | "gulp": "^3.9.1",
32 | "gulp-eslint": "^4.0.1",
33 | "gulp-format-md": "^1.0.0",
34 | "gulp-istanbul": "^1.1.3",
35 | "gulp-mocha": "^5.0.0",
36 | "isobject": "^3.0.1",
37 | "mocha": "^3.5.3",
38 | "snapdragon": "^0.11.0"
39 | },
40 | "keywords": [
41 | "capture",
42 | "compile",
43 | "compiler",
44 | "convert",
45 | "match",
46 | "parse",
47 | "parser",
48 | "plugin",
49 | "render",
50 | "snapdragon",
51 | "snapdragonplugin",
52 | "transform",
53 | "util"
54 | ],
55 | "verb": {
56 | "layout": "default",
57 | "tasks": [
58 | "readme"
59 | ],
60 | "related": {
61 | "list": [
62 | "snapdragon-node",
63 | "snapdragon-position",
64 | "snapdragon-token"
65 | ]
66 | },
67 | "plugins": [
68 | "gulp-format-md"
69 | ],
70 | "lint": {
71 | "reflinks": true
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Release history
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7 |
8 |
9 | Guiding Principles
10 |
11 | - Changelogs are for humans, not machines.
12 | - There should be an entry for every single version.
13 | - The same types of changes should be grouped.
14 | - Versions and sections should be linkable.
15 | - The latest version comes first.
16 | - The release date of each versions is displayed.
17 | - Mention whether you follow Semantic Versioning.
18 |
19 |
20 |
21 |
22 | Types of changes
23 |
24 | Changelog entries are classified using the following labels _(from [keep-a-changelog](http://keepachangelog.com/)_):
25 |
26 | * `added`: for new features
27 | * `changed`: for changes in existing functionality
28 | * `deprecated`: for once-stable features removed in upcoming releases
29 | * `removed`: for deprecated features removed in this release
30 | * `fixed`: for any bug fixes
31 |
32 | Custom labels used in this repository:
33 |
34 | * `dependencies`: bumps dependencies
35 | * `housekeeping`: code re-organization, minor edits, or other changes that don't fit in one of the other categories.
36 |
37 |
38 |
39 |
40 | ### [5.0.0] - 2018-01-11
41 |
42 | **Changes**
43 |
44 | - Adds support for `node.value`, in anticipation of snapdragon v1.0.0.
45 |
46 |
47 | ### [4.0.0] - 2017-11-01
48 |
49 | **Dependencies**
50 |
51 | - Updated `kind-of` to version 6.0
52 |
53 | ### [3.0.0] - 2017-05-01
54 |
55 | **Changed**
56 |
57 | - `.emit` was renamed to [.append](README.md#append)
58 | - `.addNode` was renamed to [.pushNode](README.md#pushNode)
59 | - `.getNode` was renamed to [.findNode](README.md#findNode)
60 | - `.isEmptyNodes` was renamed to [.isEmpty](README.md#isEmpty): also now works with `node.nodes` and/or `node.val`
61 |
62 | **Added**
63 |
64 | - [.identity](README.md#identity)
65 | - [.removeNode](README.md#removeNode)
66 | - [.shiftNode](README.md#shiftNode)
67 | - [.popNode](README.md#popNode)
68 |
69 | ### 0.1.0
70 |
71 | First release.
72 |
73 | [keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog
74 |
75 | [5.0.0]: https://github.com/here-be/snapdragon-util/compare/4.0.0...5.0.0
76 | [4.0.0]: https://github.com/here-be/snapdragon-util/compare/4.0.0...3.0.0
77 | [3.0.1]: https://github.com/here-be/snapdragon-util/compare/3.0.0...3.0.1
78 | [3.0.0]: https://github.com/here-be/snapdragon-util/compare/2.1.1...3.0.0
79 |
80 | [Unreleased]: https://github.com/here-be/snapdragon-util/compare/0.1.1...HEAD
81 | [keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog
82 |
83 |
--------------------------------------------------------------------------------
/.github/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing to snapdragon-util
2 |
3 | First and foremost, thank you! We appreciate that you want to contribute to snapdragon-util, your time is valuable, and your contributions mean a lot to us.
4 |
5 | **What does "contributing" mean?**
6 |
7 | Creating an issue is the simplest form of contributing to a project. But there are many ways to contribute, including the following:
8 |
9 | - Updating or correcting documentation
10 | - Feature requests
11 | - Bug reports
12 |
13 | 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.
14 |
15 | **Showing support for snapdragon-util**
16 |
17 | 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.
18 |
19 | Don't have time to contribute? No worries, here are some other ways to show your support for snapdragon-util:
20 |
21 | - star the [project](https://github.com/here-be/snapdragon-util)
22 | - tweet your support for snapdragon-util
23 |
24 | ## Issues
25 |
26 | ### Before creating an issue
27 |
28 | 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.
29 |
30 | Try to follow these guidelines
31 |
32 | - **Investigate the issue**:
33 | - **Check the readme** - oftentimes you will find notes about creating issues, and where to go depending on the type of issue.
34 | - Create the issue in the appropriate repository.
35 |
36 | ### Creating an issue
37 |
38 | 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:
39 |
40 | - **version**: please note the version of snapdragon-util are you using
41 | - **extensions, plugins, helpers, etc** (if applicable): please list any extensions you're using
42 | - **error messages**: please paste any error messages into the issue, or a [gist](https://gist.github.com/)
43 |
44 | ## Above and beyond
45 |
46 | 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.
47 |
48 | - read the [Guide to Idiomatic Contributing](https://github.com/jonschlinkert/idiomatic-contributing)
49 | - 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/).
50 | - 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/).
51 | - use backticks to wrap code. This ensures that code will retain its format, making it much more readable to others
52 | - use syntax highlighting by adding the correct language name after the first "code fence"
53 |
54 |
55 | [node-glob]: https://github.com/isaacs/node-glob
56 | [micromatch]: https://github.com/jonschlinkert/micromatch
57 | [so]: http://stackoverflow.com/questions/tagged/snapdragon-util
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": false,
4 | "es6": true,
5 | "node": true,
6 | "mocha": true
7 | },
8 |
9 | "globals": {
10 | "document": false,
11 | "navigator": false,
12 | "window": false
13 | },
14 |
15 | "rules": {
16 | "accessor-pairs": 2,
17 | "arrow-spacing": [2, { "before": true, "after": true }],
18 | "block-spacing": [2, "always"],
19 | "brace-style": [2, "1tbs", { "allowSingleLine": true }],
20 | "comma-dangle": [2, "never"],
21 | "comma-spacing": [2, { "before": false, "after": true }],
22 | "comma-style": [2, "last"],
23 | "constructor-super": 2,
24 | "curly": [2, "multi-line"],
25 | "dot-location": [2, "property"],
26 | "eol-last": 2,
27 | "eqeqeq": [2, "allow-null"],
28 | "generator-star-spacing": [2, { "before": true, "after": true }],
29 | "handle-callback-err": [2, "^(err|error)$" ],
30 | "indent": [2, 2, { "SwitchCase": 1 }],
31 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
32 | "keyword-spacing": [2, { "before": true, "after": true }],
33 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
34 | "new-parens": 2,
35 | "no-array-constructor": 2,
36 | "no-caller": 2,
37 | "no-class-assign": 2,
38 | "no-cond-assign": 2,
39 | "no-const-assign": 2,
40 | "no-control-regex": 2,
41 | "no-debugger": 2,
42 | "no-delete-var": 2,
43 | "no-dupe-args": 2,
44 | "no-dupe-class-members": 2,
45 | "no-dupe-keys": 2,
46 | "no-duplicate-case": 2,
47 | "no-empty-character-class": 2,
48 | "no-eval": 2,
49 | "no-ex-assign": 2,
50 | "no-extend-native": 2,
51 | "no-extra-bind": 2,
52 | "no-extra-boolean-cast": 2,
53 | "no-extra-parens": [2, "functions"],
54 | "no-fallthrough": 2,
55 | "no-floating-decimal": 2,
56 | "no-func-assign": 2,
57 | "no-implied-eval": 2,
58 | "no-inner-declarations": [2, "functions"],
59 | "no-invalid-regexp": 2,
60 | "no-irregular-whitespace": 2,
61 | "no-iterator": 2,
62 | "no-label-var": 2,
63 | "no-labels": 2,
64 | "no-lone-blocks": 2,
65 | "no-mixed-spaces-and-tabs": 2,
66 | "no-multi-spaces": 2,
67 | "no-multi-str": 2,
68 | "no-multiple-empty-lines": [2, { "max": 1 }],
69 | "no-native-reassign": 0,
70 | "no-negated-in-lhs": 2,
71 | "no-new": 2,
72 | "no-new-func": 2,
73 | "no-new-object": 2,
74 | "no-new-require": 2,
75 | "no-new-wrappers": 2,
76 | "no-obj-calls": 2,
77 | "no-octal": 2,
78 | "no-octal-escape": 2,
79 | "no-proto": 0,
80 | "no-redeclare": 2,
81 | "no-regex-spaces": 2,
82 | "no-return-assign": 2,
83 | "no-self-compare": 2,
84 | "no-sequences": 2,
85 | "no-shadow-restricted-names": 2,
86 | "no-spaced-func": 2,
87 | "no-sparse-arrays": 2,
88 | "no-this-before-super": 2,
89 | "no-throw-literal": 2,
90 | "no-trailing-spaces": 0,
91 | "no-undef": 2,
92 | "no-undef-init": 2,
93 | "no-unexpected-multiline": 2,
94 | "no-unneeded-ternary": [2, { "defaultAssignment": false }],
95 | "no-unreachable": 2,
96 | "no-unused-vars": [2, { "vars": "all", "args": "none" }],
97 | "no-useless-call": 0,
98 | "no-with": 2,
99 | "one-var": [0, { "initialized": "never" }],
100 | "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }],
101 | "padded-blocks": [0, "never"],
102 | "quotes": [2, "single", "avoid-escape"],
103 | "radix": 2,
104 | "semi": [2, "always"],
105 | "semi-spacing": [2, { "before": false, "after": true }],
106 | "space-before-blocks": [2, "always"],
107 | "space-before-function-paren": [2, "never"],
108 | "space-in-parens": [2, "never"],
109 | "space-infix-ops": 2,
110 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
111 | "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }],
112 | "use-isnan": 2,
113 | "valid-typeof": 2,
114 | "wrap-iife": [2, "any"],
115 | "yoda": [2, "never"]
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/test/support/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 | var isObject = require('isobject');
5 | var define = require('define-property');
6 | var getters = ['siblings', 'index', 'first', 'last', 'prev', 'next'];
7 |
8 | /**
9 | * This is a shim used in the unit tests
10 | * to ensure that snapdragon-util works with
11 | * older and newer versions of snapdragon-node
12 | */
13 |
14 | function isNode(node) {
15 | return isObject(node) && node.isNode === true;
16 | }
17 |
18 | module.exports = function(node) {
19 |
20 | /**
21 | * Define a non-enumberable property on the node instance.
22 | *
23 | * ```js
24 | * var node = new Node();
25 | * node.define('foo', 'something non-enumerable');
26 | * ```
27 | * @param {String} `name`
28 | * @param {any} `value`
29 | * @return {Object} returns the node instance
30 | * @api public
31 | */
32 |
33 | node.define = function(name, value) {
34 | define(this, name, value);
35 | return this;
36 | };
37 |
38 | /**
39 | * Given node `foo` and node `bar`, push node `bar` onto `foo.nodes`, and
40 | * set `foo` as `bar.parent`.
41 | *
42 | * ```js
43 | * var foo = new Node({type: 'foo'});
44 | * var bar = new Node({type: 'bar'});
45 | * foo.push(bar);
46 | * ```
47 | * @param {Object} `node`
48 | * @return {Number} Returns the length of `node.nodes`
49 | * @api public
50 | */
51 |
52 | node.push = function(node) {
53 | assert(isNode(node), 'expected node to be an instance of Node');
54 | define(node, 'parent', this);
55 |
56 | this.nodes = this.nodes || [];
57 | return this.nodes.push(node);
58 | };
59 |
60 | /**
61 | * Given node `foo` and node `bar`, unshift node `bar` onto `foo.nodes`, and
62 | * set `foo` as `bar.parent`.
63 | *
64 | * ```js
65 | * var foo = new Node({type: 'foo'});
66 | * var bar = new Node({type: 'bar'});
67 | * foo.unshift(bar);
68 | * ```
69 | * @param {Object} `node`
70 | * @return {Number} Returns the length of `node.nodes`
71 | * @api public
72 | */
73 |
74 | node.unshift = function(node) {
75 | assert(isNode(node), 'expected node to be an instance of Node');
76 | define(node, 'parent', this);
77 |
78 | this.nodes = this.nodes || [];
79 | return this.nodes.unshift(node);
80 | };
81 |
82 | /**
83 | * Pop a node from `node.nodes`.
84 | *
85 | * ```js
86 | * var node = new Node({type: 'foo'});
87 | * node.push(new Node({type: 'a'}));
88 | * node.push(new Node({type: 'b'}));
89 | * node.push(new Node({type: 'c'}));
90 | * node.push(new Node({type: 'd'}));
91 | * console.log(node.nodes.length);
92 | * //=> 4
93 | * node.pop();
94 | * console.log(node.nodes.length);
95 | * //=> 3
96 | * ```
97 | * @return {Number} Returns the popped `node`
98 | * @api public
99 | */
100 |
101 | node.pop = function() {
102 | return this.nodes && this.nodes.pop();
103 | };
104 |
105 | /**
106 | * Shift a node from `node.nodes`.
107 | *
108 | * ```js
109 | * var node = new Node({type: 'foo'});
110 | * node.push(new Node({type: 'a'}));
111 | * node.push(new Node({type: 'b'}));
112 | * node.push(new Node({type: 'c'}));
113 | * node.push(new Node({type: 'd'}));
114 | * console.log(node.nodes.length);
115 | * //=> 4
116 | * node.shift();
117 | * console.log(node.nodes.length);
118 | * //=> 3
119 | * ```
120 | * @return {Object} Returns the shifted `node`
121 | * @api public
122 | */
123 |
124 | node.shift = function() {
125 | return this.nodes && this.nodes.shift();
126 | };
127 |
128 | /**
129 | * Remove `node` from `node.nodes`.
130 | *
131 | * ```js
132 | * node.remove(childNode);
133 | * ```
134 | * @param {Object} `node`
135 | * @return {Object} Returns the removed node.
136 | * @api public
137 | */
138 |
139 | node.remove = function(node) {
140 | assert(isNode(node), 'expected node to be an instance of Node');
141 | this.nodes = this.nodes || [];
142 | var idx = node.index;
143 | if (idx !== -1) {
144 | return this.nodes.splice(idx, 1);
145 | }
146 | return null;
147 | };
148 | };
149 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # snapdragon-util [](https://www.npmjs.com/package/snapdragon-util) [](https://npmjs.org/package/snapdragon-util) [](https://npmjs.org/package/snapdragon-util) [](https://travis-ci.org/here-be/snapdragon-util)
2 |
3 | > Utilities for the snapdragon parser/compiler.
4 |
5 | Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support.
6 |
7 | ## Install
8 |
9 | Install with [npm](https://www.npmjs.com/):
10 |
11 | ```sh
12 | $ npm install --save snapdragon-util
13 | ```
14 |
15 | ## Usage
16 |
17 | ```js
18 | var util = require('snapdragon-util');
19 | ```
20 |
21 | ## API
22 |
23 | ### [.isNode](index.js#L20)
24 |
25 | Returns true if the given value is a node.
26 |
27 | **Params**
28 |
29 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
30 | * `returns` **{Boolean}**
31 |
32 | **Example**
33 |
34 | ```js
35 | var Node = require('snapdragon-node');
36 | var node = new Node({type: 'foo'});
37 | console.log(utils.isNode(node)); //=> true
38 | console.log(utils.isNode({})); //=> false
39 | ```
40 |
41 | ### [.noop](index.js#L36)
42 |
43 | Emit an empty string for the given `node`.
44 |
45 | **Params**
46 |
47 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
48 | * `returns` **{undefined}**
49 |
50 | **Example**
51 |
52 | ```js
53 | // do nothing for beginning-of-string
54 | snapdragon.compiler.set('bos', utils.noop);
55 | ```
56 |
57 | ### [.value](index.js#L54)
58 |
59 | Returns `node.value` or `node.val`.
60 |
61 | **Params**
62 |
63 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
64 | * `returns` **{String}**: returns
65 |
66 | **Example**
67 |
68 | ```js
69 | const star = new Node({type: 'star', value: '*'});
70 | const slash = new Node({type: 'slash', val: '/'});
71 | console.log(utils.value(star)) //=> '*'
72 | console.log(utils.value(slash)) //=> '/'
73 | ```
74 |
75 | ### [.identity](index.js#L72)
76 |
77 | Append `node.value` to `compiler.output`.
78 |
79 | **Params**
80 |
81 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
82 | * `returns` **{undefined}**
83 |
84 | **Example**
85 |
86 | ```js
87 | snapdragon.compiler.set('text', utils.identity);
88 | ```
89 |
90 | ### [.append](index.js#L95)
91 |
92 | Previously named `.emit`, this method appends the given `value` to `compiler.output` for the given node. Useful when you know what value should be appended advance, regardless of the actual value of `node.value`.
93 |
94 | **Params**
95 |
96 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
97 | * `returns` **{Function}**: Returns a compiler middleware function.
98 |
99 | **Example**
100 |
101 | ```js
102 | snapdragon.compiler
103 | .set('i', function(node) {
104 | this.mapVisit(node);
105 | })
106 | .set('i.open', utils.append(''))
107 | .set('i.close', utils.append(''))
108 | ```
109 |
110 | ### [.toNoop](index.js#L118)
111 |
112 | Used in compiler middleware, this onverts an AST node into an empty `text` node and deletes `node.nodes` if it exists. The advantage of this method is that, as opposed to completely removing the node, indices will not need to be re-calculated in sibling nodes, and nothing is appended to the output.
113 |
114 | **Params**
115 |
116 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
117 | * `nodes` **{Array}**: Optionally pass a new `nodes` value, to replace the existing `node.nodes` array.
118 |
119 | **Example**
120 |
121 | ```js
122 | utils.toNoop(node);
123 | // convert `node.nodes` to the given value instead of deleting it
124 | utils.toNoop(node, []);
125 | ```
126 |
127 | ### [.visit](index.js#L147)
128 |
129 | Visit `node` with the given `fn`. The built-in `.visit` method in snapdragon automatically calls registered compilers, this allows you to pass a visitor function.
130 |
131 | **Params**
132 |
133 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
134 | * `fn` **{Function}**
135 | * `returns` **{Object}**: returns the node after recursively visiting all child nodes.
136 |
137 | **Example**
138 |
139 | ```js
140 | snapdragon.compiler.set('i', function(node) {
141 | utils.visit(node, function(childNode) {
142 | // do stuff with "childNode"
143 | return childNode;
144 | });
145 | });
146 | ```
147 |
148 | ### [.mapVisit](index.js#L174)
149 |
150 | Map [visit](#visit) the given `fn` over `node.nodes`. This is called by [visit](#visit), use this method if you do not want `fn` to be called on the first node.
151 |
152 | **Params**
153 |
154 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
155 | * `options` **{Object}**
156 | * `fn` **{Function}**
157 | * `returns` **{Object}**: returns the node
158 |
159 | **Example**
160 |
161 | ```js
162 | snapdragon.compiler.set('i', function(node) {
163 | utils.mapVisit(node, function(childNode) {
164 | // do stuff with "childNode"
165 | return childNode;
166 | });
167 | });
168 | ```
169 |
170 | ### [.addOpen](index.js#L213)
171 |
172 | Unshift an `*.open` node onto `node.nodes`.
173 |
174 | **Params**
175 |
176 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
177 | * `Node` **{Function}**: (required) Node constructor function from [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node).
178 | * `filter` **{Function}**: Optionaly specify a filter function to exclude the node.
179 | * `returns` **{Object}**: Returns the created opening node.
180 |
181 | **Example**
182 |
183 | ```js
184 | var Node = require('snapdragon-node');
185 | snapdragon.parser.set('brace', function(node) {
186 | var match = this.match(/^{/);
187 | if (match) {
188 | var parent = new Node({type: 'brace'});
189 | utils.addOpen(parent, Node);
190 | console.log(parent.nodes[0]):
191 | // { type: 'brace.open', value: '' };
192 |
193 | // push the parent "brace" node onto the stack
194 | this.push(parent);
195 |
196 | // return the parent node, so it's also added to the AST
197 | return brace;
198 | }
199 | });
200 | ```
201 |
202 | ### [.addClose](index.js#L263)
203 |
204 | Push a `*.close` node onto `node.nodes`.
205 |
206 | **Params**
207 |
208 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
209 | * `Node` **{Function}**: (required) Node constructor function from [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node).
210 | * `filter` **{Function}**: Optionaly specify a filter function to exclude the node.
211 | * `returns` **{Object}**: Returns the created closing node.
212 |
213 | **Example**
214 |
215 | ```js
216 | var Node = require('snapdragon-node');
217 | snapdragon.parser.set('brace', function(node) {
218 | var match = this.match(/^}/);
219 | if (match) {
220 | var parent = this.parent();
221 | if (parent.type !== 'brace') {
222 | throw new Error('missing opening: ' + '}');
223 | }
224 |
225 | utils.addClose(parent, Node);
226 | console.log(parent.nodes[parent.nodes.length - 1]):
227 | // { type: 'brace.close', value: '' };
228 |
229 | // no need to return a node, since the parent
230 | // was already added to the AST
231 | return;
232 | }
233 | });
234 | ```
235 |
236 | ### [.wrapNodes](index.js#L293)
237 |
238 | Wraps the given `node` with `*.open` and `*.close` nodes.
239 |
240 | **Params**
241 |
242 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
243 | * `Node` **{Function}**: (required) Node constructor function from [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node).
244 | * `filter` **{Function}**: Optionaly specify a filter function to exclude the node.
245 | * `returns` **{Object}**: Returns the node
246 |
247 | ### [.pushNode](index.js#L318)
248 |
249 | Push the given `node` onto `parent.nodes`, and set `parent` as `node.parent.
250 |
251 | **Params**
252 |
253 | * `parent` **{Object}**
254 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
255 | * `returns` **{Object}**: Returns the child node
256 |
257 | **Example**
258 |
259 | ```js
260 | var parent = new Node({type: 'foo'});
261 | var node = new Node({type: 'bar'});
262 | utils.pushNode(parent, node);
263 | console.log(parent.nodes[0].type) // 'bar'
264 | console.log(node.parent.type) // 'foo'
265 | ```
266 |
267 | ### [.unshiftNode](index.js#L348)
268 |
269 | Unshift `node` onto `parent.nodes`, and set `parent` as `node.parent.
270 |
271 | **Params**
272 |
273 | * `parent` **{Object}**
274 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
275 | * `returns` **{undefined}**
276 |
277 | **Example**
278 |
279 | ```js
280 | var parent = new Node({type: 'foo'});
281 | var node = new Node({type: 'bar'});
282 | utils.unshiftNode(parent, node);
283 | console.log(parent.nodes[0].type) // 'bar'
284 | console.log(node.parent.type) // 'foo'
285 | ```
286 |
287 | ### [.popNode](index.js#L381)
288 |
289 | Pop the last `node` off of `parent.nodes`. The advantage of using this method is that it checks for `node.nodes` and works with any version of `snapdragon-node`.
290 |
291 | **Params**
292 |
293 | * `parent` **{Object}**
294 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
295 | * `returns` **{Number|Undefined}**: Returns the length of `node.nodes` or undefined.
296 |
297 | **Example**
298 |
299 | ```js
300 | var parent = new Node({type: 'foo'});
301 | utils.pushNode(parent, new Node({type: 'foo'}));
302 | utils.pushNode(parent, new Node({type: 'bar'}));
303 | utils.pushNode(parent, new Node({type: 'baz'}));
304 | console.log(parent.nodes.length); //=> 3
305 | utils.popNode(parent);
306 | console.log(parent.nodes.length); //=> 2
307 | ```
308 |
309 | ### [.shiftNode](index.js#L409)
310 |
311 | Shift the first `node` off of `parent.nodes`. The advantage of using this method is that it checks for `node.nodes` and works with any version of `snapdragon-node`.
312 |
313 | **Params**
314 |
315 | * `parent` **{Object}**
316 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
317 | * `returns` **{Number|Undefined}**: Returns the length of `node.nodes` or undefined.
318 |
319 | **Example**
320 |
321 | ```js
322 | var parent = new Node({type: 'foo'});
323 | utils.pushNode(parent, new Node({type: 'foo'}));
324 | utils.pushNode(parent, new Node({type: 'bar'}));
325 | utils.pushNode(parent, new Node({type: 'baz'}));
326 | console.log(parent.nodes.length); //=> 3
327 | utils.shiftNode(parent);
328 | console.log(parent.nodes.length); //=> 2
329 | ```
330 |
331 | ### [.removeNode](index.js#L436)
332 |
333 | Remove the specified `node` from `parent.nodes`.
334 |
335 | **Params**
336 |
337 | * `parent` **{Object}**
338 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
339 | * `returns` **{Object|undefined}**: Returns the removed node, if successful, or undefined if it does not exist on `parent.nodes`.
340 |
341 | **Example**
342 |
343 | ```js
344 | var parent = new Node({type: 'abc'});
345 | var foo = new Node({type: 'foo'});
346 | utils.pushNode(parent, foo);
347 | utils.pushNode(parent, new Node({type: 'bar'}));
348 | utils.pushNode(parent, new Node({type: 'baz'}));
349 | console.log(parent.nodes.length); //=> 3
350 | utils.removeNode(parent, foo);
351 | console.log(parent.nodes.length); //=> 2
352 | ```
353 |
354 | ### [.isType](index.js#L467)
355 |
356 | Returns true if `node.type` matches the given `type`. Throws a `TypeError` if `node` is not an instance of `Node`.
357 |
358 | **Params**
359 |
360 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
361 | * `type` **{String}**
362 | * `returns` **{Boolean}**
363 |
364 | **Example**
365 |
366 | ```js
367 | var Node = require('snapdragon-node');
368 | var node = new Node({type: 'foo'});
369 | console.log(utils.isType(node, 'foo')); // false
370 | console.log(utils.isType(node, 'bar')); // true
371 | ```
372 |
373 | ### [.hasType](index.js#L509)
374 |
375 | Returns true if the given `node` has the given `type` in `node.nodes`. Throws a `TypeError` if `node` is not an instance of `Node`.
376 |
377 | **Params**
378 |
379 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
380 | * `type` **{String}**
381 | * `returns` **{Boolean}**
382 |
383 | **Example**
384 |
385 | ```js
386 | var Node = require('snapdragon-node');
387 | var node = new Node({
388 | type: 'foo',
389 | nodes: [
390 | new Node({type: 'bar'}),
391 | new Node({type: 'baz'})
392 | ]
393 | });
394 | console.log(utils.hasType(node, 'xyz')); // false
395 | console.log(utils.hasType(node, 'baz')); // true
396 | ```
397 |
398 | ### [.firstOfType](index.js#L542)
399 |
400 | Returns the first node from `node.nodes` of the given `type`
401 |
402 | **Params**
403 |
404 | * `nodes` **{Array}**
405 | * `type` **{String}**
406 | * `returns` **{Object|undefined}**: Returns the first matching node or undefined.
407 |
408 | **Example**
409 |
410 | ```js
411 | var node = new Node({
412 | type: 'foo',
413 | nodes: [
414 | new Node({type: 'text', value: 'abc'}),
415 | new Node({type: 'text', value: 'xyz'})
416 | ]
417 | });
418 |
419 | var textNode = utils.firstOfType(node.nodes, 'text');
420 | console.log(textNode.value);
421 | //=> 'abc'
422 | ```
423 |
424 | ### [.findNode](index.js#L578)
425 |
426 | Returns the node at the specified index, or the first node of the given `type` from `node.nodes`.
427 |
428 | **Params**
429 |
430 | * `nodes` **{Array}**
431 | * `type` **{String|Number}**: Node type or index.
432 | * `returns` **{Object}**: Returns a node or undefined.
433 |
434 | **Example**
435 |
436 | ```js
437 | var node = new Node({
438 | type: 'foo',
439 | nodes: [
440 | new Node({type: 'text', value: 'abc'}),
441 | new Node({type: 'text', value: 'xyz'})
442 | ]
443 | });
444 |
445 | var nodeOne = utils.findNode(node.nodes, 'text');
446 | console.log(nodeOne.value);
447 | //=> 'abc'
448 |
449 | var nodeTwo = utils.findNode(node.nodes, 1);
450 | console.log(nodeTwo.value);
451 | //=> 'xyz'
452 | ```
453 |
454 | ### [.isOpen](index.js#L602)
455 |
456 | Returns true if the given node is an "*.open" node.
457 |
458 | **Params**
459 |
460 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
461 | * `returns` **{Boolean}**
462 |
463 | **Example**
464 |
465 | ```js
466 | var Node = require('snapdragon-node');
467 | var brace = new Node({type: 'brace'});
468 | var open = new Node({type: 'brace.open'});
469 | var close = new Node({type: 'brace.close'});
470 |
471 | console.log(utils.isOpen(brace)); // false
472 | console.log(utils.isOpen(open)); // true
473 | console.log(utils.isOpen(close)); // false
474 | ```
475 |
476 | ### [.isClose](index.js#L631)
477 |
478 | Returns true if the given node is a "*.close" node.
479 |
480 | **Params**
481 |
482 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
483 | * `returns` **{Boolean}**
484 |
485 | **Example**
486 |
487 | ```js
488 | var Node = require('snapdragon-node');
489 | var brace = new Node({type: 'brace'});
490 | var open = new Node({type: 'brace.open'});
491 | var close = new Node({type: 'brace.close'});
492 |
493 | console.log(utils.isClose(brace)); // false
494 | console.log(utils.isClose(open)); // false
495 | console.log(utils.isClose(close)); // true
496 | ```
497 |
498 | ### [.isBlock](index.js#L662)
499 |
500 | Returns true if the given node is an "*.open" node.
501 |
502 | **Params**
503 |
504 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
505 | * `returns` **{Boolean}**
506 |
507 | **Example**
508 |
509 | ```js
510 | var Node = require('snapdragon-node');
511 | var brace = new Node({type: 'brace'});
512 | var open = new Node({type: 'brace.open', value: '{'});
513 | var inner = new Node({type: 'text', value: 'a,b,c'});
514 | var close = new Node({type: 'brace.close', value: '}'});
515 | brace.push(open);
516 | brace.push(inner);
517 | brace.push(close);
518 |
519 | console.log(utils.isBlock(brace)); // true
520 | ```
521 |
522 | ### [.hasNode](index.js#L691)
523 |
524 | Returns true if `parent.nodes` has the given `node`.
525 |
526 | **Params**
527 |
528 | * `type` **{String}**
529 | * `returns` **{Boolean}**
530 |
531 | **Example**
532 |
533 | ```js
534 | const foo = new Node({type: 'foo'});
535 | const bar = new Node({type: 'bar'});
536 | cosole.log(util.hasNode(foo, bar)); // false
537 | foo.push(bar);
538 | cosole.log(util.hasNode(foo, bar)); // true
539 | ```
540 |
541 | ### [.hasOpen](index.js#L723)
542 |
543 | Returns true if `node.nodes` **has** an `.open` node
544 |
545 | **Params**
546 |
547 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
548 | * `returns` **{Boolean}**
549 |
550 | **Example**
551 |
552 | ```js
553 | var Node = require('snapdragon-node');
554 | var brace = new Node({
555 | type: 'brace',
556 | nodes: []
557 | });
558 |
559 | var open = new Node({type: 'brace.open'});
560 | console.log(utils.hasOpen(brace)); // false
561 |
562 | brace.pushNode(open);
563 | console.log(utils.hasOpen(brace)); // true
564 | ```
565 |
566 | ### [.hasClose](index.js#L754)
567 |
568 | Returns true if `node.nodes` **has** a `.close` node
569 |
570 | **Params**
571 |
572 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
573 | * `returns` **{Boolean}**
574 |
575 | **Example**
576 |
577 | ```js
578 | var Node = require('snapdragon-node');
579 | var brace = new Node({
580 | type: 'brace',
581 | nodes: []
582 | });
583 |
584 | var close = new Node({type: 'brace.close'});
585 | console.log(utils.hasClose(brace)); // false
586 |
587 | brace.pushNode(close);
588 | console.log(utils.hasClose(brace)); // true
589 | ```
590 |
591 | ### [.hasOpenAndClose](index.js#L789)
592 |
593 | Returns true if `node.nodes` has both `.open` and `.close` nodes
594 |
595 | **Params**
596 |
597 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
598 | * `returns` **{Boolean}**
599 |
600 | **Example**
601 |
602 | ```js
603 | var Node = require('snapdragon-node');
604 | var brace = new Node({
605 | type: 'brace',
606 | nodes: []
607 | });
608 |
609 | var open = new Node({type: 'brace.open'});
610 | var close = new Node({type: 'brace.close'});
611 | console.log(utils.hasOpen(brace)); // false
612 | console.log(utils.hasClose(brace)); // false
613 |
614 | brace.pushNode(open);
615 | brace.pushNode(close);
616 | console.log(utils.hasOpen(brace)); // true
617 | console.log(utils.hasClose(brace)); // true
618 | ```
619 |
620 | ### [.addType](index.js#L811)
621 |
622 | Push the given `node` onto the `state.inside` array for the given type. This array is used as a specialized "stack" for only the given `node.type`.
623 |
624 | **Params**
625 |
626 | * `state` **{Object}**: The `compiler.state` object or custom state object.
627 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
628 | * `returns` **{Array}**: Returns the `state.inside` stack for the given type.
629 |
630 | **Example**
631 |
632 | ```js
633 | var state = { inside: {}};
634 | var node = new Node({type: 'brace'});
635 | utils.addType(state, node);
636 | console.log(state.inside);
637 | //=> { brace: [{type: 'brace'}] }
638 | ```
639 |
640 | ### [.removeType](index.js#L851)
641 |
642 | Remove the given `node` from the `state.inside` array for the given type. This array is used as a specialized "stack" for only the given `node.type`.
643 |
644 | **Params**
645 |
646 | * `state` **{Object}**: The `compiler.state` object or custom state object.
647 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
648 | * `returns` **{Array}**: Returns the `state.inside` stack for the given type.
649 |
650 | **Example**
651 |
652 | ```js
653 | var state = { inside: {}};
654 | var node = new Node({type: 'brace'});
655 | utils.addType(state, node);
656 | console.log(state.inside);
657 | //=> { brace: [{type: 'brace'}] }
658 | utils.removeType(state, node);
659 | //=> { brace: [] }
660 | ```
661 |
662 | ### [.isEmpty](index.js#L880)
663 |
664 | Returns true if `node.value` is an empty string, or `node.nodes` does not contain any non-empty text nodes.
665 |
666 | **Params**
667 |
668 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
669 | * `fn` **{Function}**
670 | * `returns` **{Boolean}**
671 |
672 | **Example**
673 |
674 | ```js
675 | var node = new Node({type: 'text'});
676 | utils.isEmpty(node); //=> true
677 | node.value = 'foo';
678 | utils.isEmpty(node); //=> false
679 | ```
680 |
681 | ### [.isInsideType](index.js#L922)
682 |
683 | Returns true if the `state.inside` stack for the given type exists and has one or more nodes on it.
684 |
685 | **Params**
686 |
687 | * `state` **{Object}**
688 | * `type` **{String}**
689 | * `returns` **{Boolean}**
690 |
691 | **Example**
692 |
693 | ```js
694 | var state = { inside: {}};
695 | var node = new Node({type: 'brace'});
696 | console.log(utils.isInsideType(state, 'brace')); //=> false
697 | utils.addType(state, node);
698 | console.log(utils.isInsideType(state, 'brace')); //=> true
699 | utils.removeType(state, node);
700 | console.log(utils.isInsideType(state, 'brace')); //=> false
701 | ```
702 |
703 | ### [.isInside](index.js#L956)
704 |
705 | Returns true if `node` is either a child or grand-child of the given `type`, or `state.inside[type]` is a non-empty array.
706 |
707 | **Params**
708 |
709 | * `state` **{Object}**: Either the `compiler.state` object, if it exists, or a user-supplied state object.
710 | * `node` **{Object}**: Instance of [snapdragon-node](https://github.com/jonschlinkert/snapdragon-node)
711 | * `type` **{String}**: The `node.type` to check for.
712 | * `returns` **{Boolean}**
713 |
714 | **Example**
715 |
716 | ```js
717 | var state = { inside: {}};
718 | var node = new Node({type: 'brace'});
719 | var open = new Node({type: 'brace.open'});
720 | console.log(utils.isInside(state, open, 'brace')); //=> false
721 | utils.pushNode(node, open);
722 | console.log(utils.isInside(state, open, 'brace')); //=> true
723 | ```
724 |
725 | ### [.last](index.js#L1004)
726 |
727 | Get the last `n` element from the given `array`. Used for getting
728 | a node from `node.nodes.`
729 |
730 | **Params**
731 |
732 | * `array` **{Array}**
733 | * `n` **{Number}**
734 | * `returns` **{undefined}**
735 |
736 | ### [.arrayify](index.js#L1028)
737 |
738 | Cast the given `value` to an array.
739 |
740 | **Params**
741 |
742 | * `value` **{any}**
743 | * `returns` **{Array}**
744 |
745 | **Example**
746 |
747 | ```js
748 | console.log(utils.arrayify(''));
749 | //=> []
750 | console.log(utils.arrayify('foo'));
751 | //=> ['foo']
752 | console.log(utils.arrayify(['foo']));
753 | //=> ['foo']
754 | ```
755 |
756 | ### [.stringify](index.js#L1047)
757 |
758 | Convert the given `value` to a string by joining with `,`. Useful
759 | for creating a cheerio/CSS/DOM-style selector from a list of strings.
760 |
761 | **Params**
762 |
763 | * `value` **{any}**
764 | * `returns` **{Array}**
765 |
766 | ### [.trim](index.js#L1060)
767 |
768 | Ensure that the given value is a string and call `.trim()` on it,
769 | or return an empty string.
770 |
771 | **Params**
772 |
773 | * `str` **{String}**
774 | * `returns` **{String}**
775 |
776 | ## About
777 |
778 |
779 | Contributing
780 |
781 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
782 |
783 | Please read the [contributing guide](.github/contributing.md) for advice on opening issues, pull requests, and coding standards.
784 |
785 |
786 |
787 |
788 | Running Tests
789 |
790 | 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:
791 |
792 | ```sh
793 | $ npm install && npm test
794 | ```
795 |
796 |
797 |
798 | Building docs
799 |
800 | _(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.)_
801 |
802 | To generate the readme, run the following command:
803 |
804 | ```sh
805 | $ npm install -g verbose/verb#dev verb-generate-readme && verb
806 | ```
807 |
808 |
809 |
810 | ### Related projects
811 |
812 | You might also be interested in these projects:
813 |
814 | * [snapdragon-node](https://www.npmjs.com/package/snapdragon-node): Snapdragon utility for creating a new AST node in custom code, such as plugins. | [homepage](https://github.com/jonschlinkert/snapdragon-node "Snapdragon utility for creating a new AST node in custom code, such as plugins.")
815 | * [snapdragon-position](https://www.npmjs.com/package/snapdragon-position): Snapdragon util and plugin for patching the position on an AST node. | [homepage](https://github.com/here-be/snapdragon-position "Snapdragon util and plugin for patching the position on an AST node.")
816 | * [snapdragon-token](https://www.npmjs.com/package/snapdragon-token): Create a snapdragon token. Used by the snapdragon lexer, but can also be used by… [more](https://github.com/here-be/snapdragon-token) | [homepage](https://github.com/here-be/snapdragon-token "Create a snapdragon token. Used by the snapdragon lexer, but can also be used by plugins.")
817 |
818 | ### Contributors
819 |
820 | | **Commits** | **Contributor** |
821 | | --- | --- |
822 | | 43 | [jonschlinkert](https://github.com/jonschlinkert) |
823 | | 2 | [realityking](https://github.com/realityking) |
824 |
825 | ### Author
826 |
827 | **Jon Schlinkert**
828 |
829 | * [linkedin/in/jonschlinkert](https://linkedin.com/in/jonschlinkert)
830 | * [github/jonschlinkert](https://github.com/jonschlinkert)
831 | * [twitter/jonschlinkert](https://twitter.com/jonschlinkert)
832 |
833 | ### License
834 |
835 | Copyright © 2018, [Jon Schlinkert](https://github.com/jonschlinkert).
836 | Released under the [MIT License](LICENSE).
837 |
838 | ***
839 |
840 | _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on January 11, 2018._
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var typeOf = require('kind-of');
4 | var utils = module.exports;
5 |
6 | /**
7 | * Returns true if the given value is a node.
8 | *
9 | * ```js
10 | * var Node = require('snapdragon-node');
11 | * var node = new Node({type: 'foo'});
12 | * console.log(utils.isNode(node)); //=> true
13 | * console.log(utils.isNode({})); //=> false
14 | * ```
15 | * @param {Object} `node` Instance of [snapdragon-node][]
16 | * @returns {Boolean}
17 | * @api public
18 | */
19 |
20 | utils.isNode = function(node) {
21 | return typeOf(node) === 'object' && node.isNode === true;
22 | };
23 |
24 | /**
25 | * Emit an empty string for the given `node`.
26 | *
27 | * ```js
28 | * // do nothing for beginning-of-string
29 | * snapdragon.compiler.set('bos', utils.noop);
30 | * ```
31 | * @param {Object} `node` Instance of [snapdragon-node][]
32 | * @returns {undefined}
33 | * @api public
34 | */
35 |
36 | utils.noop = function(node) {
37 | append(this, '', node);
38 | };
39 |
40 | /**
41 | * Returns `node.value` or `node.val`.
42 | *
43 | * ```js
44 | * const star = new Node({type: 'star', value: '*'});
45 | * const slash = new Node({type: 'slash', val: '/'});
46 | * console.log(utils.value(star)) //=> '*'
47 | * console.log(utils.value(slash)) //=> '/'
48 | * ```
49 | * @param {Object} `node` Instance of [snapdragon-node][]
50 | * @returns {String} returns
51 | * @api public
52 | */
53 |
54 | utils.value = function(node) {
55 | if (typeof node.value === 'string') {
56 | return node.value;
57 | }
58 | return node.val;
59 | };
60 |
61 | /**
62 | * Append `node.value` to `compiler.output`.
63 | *
64 | * ```js
65 | * snapdragon.compiler.set('text', utils.identity);
66 | * ```
67 | * @param {Object} `node` Instance of [snapdragon-node][]
68 | * @returns {undefined}
69 | * @api public
70 | */
71 |
72 | utils.identity = function(node) {
73 | append(this, utils.value(node), node);
74 | };
75 |
76 | /**
77 | * Previously named `.emit`, this method appends the given `value`
78 | * to `compiler.output` for the given node. Useful when you know
79 | * what value should be appended advance, regardless of the actual
80 | * value of `node.value`.
81 | *
82 | * ```js
83 | * snapdragon.compiler
84 | * .set('i', function(node) {
85 | * this.mapVisit(node);
86 | * })
87 | * .set('i.open', utils.append(''))
88 | * .set('i.close', utils.append(''))
89 | * ```
90 | * @param {Object} `node` Instance of [snapdragon-node][]
91 | * @returns {Function} Returns a compiler middleware function.
92 | * @api public
93 | */
94 |
95 | utils.append = function(value) {
96 | return function(node) {
97 | append(this, value, node);
98 | };
99 | };
100 |
101 | /**
102 | * Used in compiler middleware, this onverts an AST node into
103 | * an empty `text` node and deletes `node.nodes` if it exists.
104 | * The advantage of this method is that, as opposed to completely
105 | * removing the node, indices will not need to be re-calculated
106 | * in sibling nodes, and nothing is appended to the output.
107 | *
108 | * ```js
109 | * utils.toNoop(node);
110 | * // convert `node.nodes` to the given value instead of deleting it
111 | * utils.toNoop(node, []);
112 | * ```
113 | * @param {Object} `node` Instance of [snapdragon-node][]
114 | * @param {Array} `nodes` Optionally pass a new `nodes` value, to replace the existing `node.nodes` array.
115 | * @api public
116 | */
117 |
118 | utils.toNoop = function(node, nodes) {
119 | if (nodes) {
120 | node.nodes = nodes;
121 | } else {
122 | delete node.nodes;
123 | node.type = 'text';
124 | node.value = '';
125 | }
126 | };
127 |
128 | /**
129 | * Visit `node` with the given `fn`. The built-in `.visit` method in snapdragon
130 | * automatically calls registered compilers, this allows you to pass a visitor
131 | * function.
132 | *
133 | * ```js
134 | * snapdragon.compiler.set('i', function(node) {
135 | * utils.visit(node, function(childNode) {
136 | * // do stuff with "childNode"
137 | * return childNode;
138 | * });
139 | * });
140 | * ```
141 | * @param {Object} `node` Instance of [snapdragon-node][]
142 | * @param {Function} `fn`
143 | * @return {Object} returns the node after recursively visiting all child nodes.
144 | * @api public
145 | */
146 |
147 | utils.visit = function(node, fn) {
148 | assert(isFunction(fn), 'expected a visitor function');
149 | expect(node, 'node');
150 | fn(node);
151 | return node.nodes ? utils.mapVisit(node, fn) : node;
152 | };
153 |
154 | /**
155 | * Map [visit](#visit) the given `fn` over `node.nodes`. This is called by
156 | * [visit](#visit), use this method if you do not want `fn` to be called on
157 | * the first node.
158 | *
159 | * ```js
160 | * snapdragon.compiler.set('i', function(node) {
161 | * utils.mapVisit(node, function(childNode) {
162 | * // do stuff with "childNode"
163 | * return childNode;
164 | * });
165 | * });
166 | * ```
167 | * @param {Object} `node` Instance of [snapdragon-node][]
168 | * @param {Object} `options`
169 | * @param {Function} `fn`
170 | * @return {Object} returns the node
171 | * @api public
172 | */
173 |
174 | utils.mapVisit = function(node, fn) {
175 | assert(isFunction(fn), 'expected a visitor function');
176 | expect(node, 'node');
177 | assert(isArray(node.nodes), 'expected node.nodes to be an array');
178 |
179 | for (var i = 0; i < node.nodes.length; i++) {
180 | utils.visit(node.nodes[i], fn);
181 | }
182 | return node;
183 | };
184 |
185 | /**
186 | * Unshift an `*.open` node onto `node.nodes`.
187 | *
188 | * ```js
189 | * var Node = require('snapdragon-node');
190 | * snapdragon.parser.set('brace', function(node) {
191 | * var match = this.match(/^{/);
192 | * if (match) {
193 | * var parent = new Node({type: 'brace'});
194 | * utils.addOpen(parent, Node);
195 | * console.log(parent.nodes[0]):
196 | * // { type: 'brace.open', value: '' };
197 | *
198 | * // push the parent "brace" node onto the stack
199 | * this.push(parent);
200 | *
201 | * // return the parent node, so it's also added to the AST
202 | * return brace;
203 | * }
204 | * });
205 | * ```
206 | * @param {Object} `node` Instance of [snapdragon-node][]
207 | * @param {Function} `Node` (required) Node constructor function from [snapdragon-node][].
208 | * @param {Function} `filter` Optionaly specify a filter function to exclude the node.
209 | * @return {Object} Returns the created opening node.
210 | * @api public
211 | */
212 |
213 | utils.addOpen = function(node, Node, value, filter) {
214 | expect(node, 'node');
215 | assert(isFunction(Node), 'expected Node to be a constructor function');
216 |
217 | if (typeof value === 'function') {
218 | filter = value;
219 | value = '';
220 | }
221 |
222 | if (typeof filter === 'function' && !filter(node)) return;
223 | var open = new Node({ type: node.type + '.open', value: value});
224 | var unshift = node.unshift || node.unshiftNode;
225 | if (typeof unshift === 'function') {
226 | unshift.call(node, open);
227 | } else {
228 | utils.unshiftNode(node, open);
229 | }
230 | return open;
231 | };
232 |
233 | /**
234 | * Push a `*.close` node onto `node.nodes`.
235 | *
236 | * ```js
237 | * var Node = require('snapdragon-node');
238 | * snapdragon.parser.set('brace', function(node) {
239 | * var match = this.match(/^}/);
240 | * if (match) {
241 | * var parent = this.parent();
242 | * if (parent.type !== 'brace') {
243 | * throw new Error('missing opening: ' + '}');
244 | * }
245 | *
246 | * utils.addClose(parent, Node);
247 | * console.log(parent.nodes[parent.nodes.length - 1]):
248 | * // { type: 'brace.close', value: '' };
249 | *
250 | * // no need to return a node, since the parent
251 | * // was already added to the AST
252 | * return;
253 | * }
254 | * });
255 | * ```
256 | * @param {Object} `node` Instance of [snapdragon-node][]
257 | * @param {Function} `Node` (required) Node constructor function from [snapdragon-node][].
258 | * @param {Function} `filter` Optionaly specify a filter function to exclude the node.
259 | * @return {Object} Returns the created closing node.
260 | * @api public
261 | */
262 |
263 | utils.addClose = function(node, Node, value, filter) {
264 | assert(isFunction(Node), 'expected Node to be a constructor function');
265 | expect(node, 'node', Node);
266 |
267 | if (typeof value === 'function') {
268 | filter = value;
269 | value = '';
270 | }
271 |
272 | if (typeof filter === 'function' && !filter(node)) return;
273 | var close = new Node({ type: node.type + '.close', value: value});
274 | var push = node.push || node.pushNode;
275 | if (typeof push === 'function') {
276 | push.call(node, close);
277 | } else {
278 | utils.pushNode(node, close);
279 | }
280 | return close;
281 | };
282 |
283 | /**
284 | * Wraps the given `node` with `*.open` and `*.close` nodes.
285 | *
286 | * @param {Object} `node` Instance of [snapdragon-node][]
287 | * @param {Function} `Node` (required) Node constructor function from [snapdragon-node][].
288 | * @param {Function} `filter` Optionaly specify a filter function to exclude the node.
289 | * @return {Object} Returns the node
290 | * @api public
291 | */
292 |
293 | utils.wrapNodes = function(node, Node, filter) {
294 | assert(utils.isNode(node), 'expected node to be an instance of Node');
295 | assert(isFunction(Node), 'expected Node to be a constructor function');
296 |
297 | utils.addOpen(node, Node, filter);
298 | utils.addClose(node, Node, filter);
299 | return node;
300 | };
301 |
302 | /**
303 | * Push the given `node` onto `parent.nodes`, and set `parent` as `node.parent.
304 | *
305 | * ```js
306 | * var parent = new Node({type: 'foo'});
307 | * var node = new Node({type: 'bar'});
308 | * utils.pushNode(parent, node);
309 | * console.log(parent.nodes[0].type) // 'bar'
310 | * console.log(node.parent.type) // 'foo'
311 | * ```
312 | * @param {Object} `parent`
313 | * @param {Object} `node` Instance of [snapdragon-node][]
314 | * @return {Object} Returns the child node
315 | * @api public
316 | */
317 |
318 | utils.pushNode = function(parent, node) {
319 | assert(utils.isNode(parent), 'expected parent node to be an instance of Node');
320 | if (!node) return;
321 |
322 | if (typeof parent.push === 'function') {
323 | return parent.push(node);
324 | }
325 |
326 | node.define('parent', parent);
327 | parent.nodes = parent.nodes || [];
328 | parent.nodes.push(node);
329 | return node;
330 | };
331 |
332 | /**
333 | * Unshift `node` onto `parent.nodes`, and set `parent` as `node.parent.
334 | *
335 | * ```js
336 | * var parent = new Node({type: 'foo'});
337 | * var node = new Node({type: 'bar'});
338 | * utils.unshiftNode(parent, node);
339 | * console.log(parent.nodes[0].type) // 'bar'
340 | * console.log(node.parent.type) // 'foo'
341 | * ```
342 | * @param {Object} `parent`
343 | * @param {Object} `node` Instance of [snapdragon-node][]
344 | * @return {undefined}
345 | * @api public
346 | */
347 |
348 | utils.unshiftNode = function(parent, node) {
349 | assert(utils.isNode(parent), 'expected parent node to be an instance of Node');
350 | if (!node) return;
351 |
352 | if (typeof parent.unshift === 'function') {
353 | return parent.unshift(node);
354 | }
355 |
356 | node.define('parent', parent);
357 | parent.nodes = parent.nodes || [];
358 | parent.nodes.unshift(node);
359 | };
360 |
361 | /**
362 | * Pop the last `node` off of `parent.nodes`. The advantage of
363 | * using this method is that it checks for `node.nodes` and works
364 | * with any version of `snapdragon-node`.
365 | *
366 | * ```js
367 | * var parent = new Node({type: 'foo'});
368 | * utils.pushNode(parent, new Node({type: 'foo'}));
369 | * utils.pushNode(parent, new Node({type: 'bar'}));
370 | * utils.pushNode(parent, new Node({type: 'baz'}));
371 | * console.log(parent.nodes.length); //=> 3
372 | * utils.popNode(parent);
373 | * console.log(parent.nodes.length); //=> 2
374 | * ```
375 | * @param {Object} `parent`
376 | * @param {Object} `node` Instance of [snapdragon-node][]
377 | * @return {Number|Undefined} Returns the length of `node.nodes` or undefined.
378 | * @api public
379 | */
380 |
381 | utils.popNode = function(node) {
382 | assert(utils.isNode(node), 'expected node to be an instance of Node');
383 | if (typeof node.pop === 'function') {
384 | return node.pop();
385 | }
386 | return node.nodes && node.nodes.pop();
387 | };
388 |
389 | /**
390 | * Shift the first `node` off of `parent.nodes`. The advantage of
391 | * using this method is that it checks for `node.nodes` and works
392 | * with any version of `snapdragon-node`.
393 | *
394 | * ```js
395 | * var parent = new Node({type: 'foo'});
396 | * utils.pushNode(parent, new Node({type: 'foo'}));
397 | * utils.pushNode(parent, new Node({type: 'bar'}));
398 | * utils.pushNode(parent, new Node({type: 'baz'}));
399 | * console.log(parent.nodes.length); //=> 3
400 | * utils.shiftNode(parent);
401 | * console.log(parent.nodes.length); //=> 2
402 | * ```
403 | * @param {Object} `parent`
404 | * @param {Object} `node` Instance of [snapdragon-node][]
405 | * @return {Number|Undefined} Returns the length of `node.nodes` or undefined.
406 | * @api public
407 | */
408 |
409 | utils.shiftNode = function(node) {
410 | assert(utils.isNode(node), 'expected node to be an instance of Node');
411 | if (typeof node.shift === 'function') {
412 | return node.shift();
413 | }
414 | return node.nodes && node.nodes.shift();
415 | };
416 |
417 | /**
418 | * Remove the specified `node` from `parent.nodes`.
419 | *
420 | * ```js
421 | * var parent = new Node({type: 'abc'});
422 | * var foo = new Node({type: 'foo'});
423 | * utils.pushNode(parent, foo);
424 | * utils.pushNode(parent, new Node({type: 'bar'}));
425 | * utils.pushNode(parent, new Node({type: 'baz'}));
426 | * console.log(parent.nodes.length); //=> 3
427 | * utils.removeNode(parent, foo);
428 | * console.log(parent.nodes.length); //=> 2
429 | * ```
430 | * @param {Object} `parent`
431 | * @param {Object} `node` Instance of [snapdragon-node][]
432 | * @return {Object|undefined} Returns the removed node, if successful, or undefined if it does not exist on `parent.nodes`.
433 | * @api public
434 | */
435 |
436 | utils.removeNode = function(parent, node) {
437 | assert(utils.isNode(parent), 'expected parent to be an instance of Node');
438 | if (!parent.nodes) return;
439 | if (!node) return;
440 |
441 | if (typeof parent.remove === 'function') {
442 | return parent.remove(node);
443 | }
444 |
445 | var idx = parent.nodes.indexOf(node);
446 | if (idx !== -1) {
447 | return parent.nodes.splice(idx, 1);
448 | }
449 | };
450 |
451 | /**
452 | * Returns true if `node.type` matches the given `type`. Throws a
453 | * `TypeError` if `node` is not an instance of `Node`.
454 | *
455 | * ```js
456 | * var Node = require('snapdragon-node');
457 | * var node = new Node({type: 'foo'});
458 | * console.log(utils.isType(node, 'foo')); // false
459 | * console.log(utils.isType(node, 'bar')); // true
460 | * ```
461 | * @param {Object} `node` Instance of [snapdragon-node][]
462 | * @param {String} `type`
463 | * @return {Boolean}
464 | * @api public
465 | */
466 |
467 | utils.isType = function(node, type) {
468 | if (!utils.isNode(node)) return false;
469 | switch (typeOf(type)) {
470 | case 'string':
471 | return node.type === type;
472 | case 'regexp':
473 | return type.test(node.type);
474 | case 'array':
475 | for (const key of type.slice()) {
476 | if (utils.isType(node, key)) {
477 | return true;
478 | }
479 | }
480 | return false;
481 | default: {
482 | throw new TypeError('expected "type" to be an array, string or regexp');
483 | }
484 | }
485 | };
486 |
487 | /**
488 | * Returns true if the given `node` has the given `type` in `node.nodes`.
489 | * Throws a `TypeError` if `node` is not an instance of `Node`.
490 | *
491 | * ```js
492 | * var Node = require('snapdragon-node');
493 | * var node = new Node({
494 | * type: 'foo',
495 | * nodes: [
496 | * new Node({type: 'bar'}),
497 | * new Node({type: 'baz'})
498 | * ]
499 | * });
500 | * console.log(utils.hasType(node, 'xyz')); // false
501 | * console.log(utils.hasType(node, 'baz')); // true
502 | * ```
503 | * @param {Object} `node` Instance of [snapdragon-node][]
504 | * @param {String} `type`
505 | * @return {Boolean}
506 | * @api public
507 | */
508 |
509 | utils.hasType = function(node, type) {
510 | if (!utils.isNode(node)) return false;
511 | if (!Array.isArray(node.nodes)) return false;
512 | for (const child of node.nodes) {
513 | if (utils.isType(child, type)) {
514 | return true;
515 | }
516 | }
517 | return false;
518 | };
519 |
520 | /**
521 | * Returns the first node from `node.nodes` of the given `type`
522 | *
523 | * ```js
524 | * var node = new Node({
525 | * type: 'foo',
526 | * nodes: [
527 | * new Node({type: 'text', value: 'abc'}),
528 | * new Node({type: 'text', value: 'xyz'})
529 | * ]
530 | * });
531 | *
532 | * var textNode = utils.firstOfType(node.nodes, 'text');
533 | * console.log(textNode.value);
534 | * //=> 'abc'
535 | * ```
536 | * @param {Array} `nodes`
537 | * @param {String} `type`
538 | * @return {Object|undefined} Returns the first matching node or undefined.
539 | * @api public
540 | */
541 |
542 | utils.firstOfType = function(nodes, type) {
543 | for (const node of nodes) {
544 | if (utils.isType(node, type)) {
545 | return node;
546 | }
547 | }
548 | };
549 |
550 | /**
551 | * Returns the node at the specified index, or the first node of the
552 | * given `type` from `node.nodes`.
553 | *
554 | * ```js
555 | * var node = new Node({
556 | * type: 'foo',
557 | * nodes: [
558 | * new Node({type: 'text', value: 'abc'}),
559 | * new Node({type: 'text', value: 'xyz'})
560 | * ]
561 | * });
562 | *
563 | * var nodeOne = utils.findNode(node.nodes, 'text');
564 | * console.log(nodeOne.value);
565 | * //=> 'abc'
566 | *
567 | * var nodeTwo = utils.findNode(node.nodes, 1);
568 | * console.log(nodeTwo.value);
569 | * //=> 'xyz'
570 | * ```
571 | *
572 | * @param {Array} `nodes`
573 | * @param {String|Number} `type` Node type or index.
574 | * @return {Object} Returns a node or undefined.
575 | * @api public
576 | */
577 |
578 | utils.findNode = function(nodes, type) {
579 | if (!Array.isArray(nodes)) return null;
580 | if (typeof type === 'number') return nodes[type];
581 | return utils.firstOfType(nodes, type);
582 | };
583 |
584 | /**
585 | * Returns true if the given node is an "*.open" node.
586 | *
587 | * ```js
588 | * var Node = require('snapdragon-node');
589 | * var brace = new Node({type: 'brace'});
590 | * var open = new Node({type: 'brace.open'});
591 | * var close = new Node({type: 'brace.close'});
592 | *
593 | * console.log(utils.isOpen(brace)); // false
594 | * console.log(utils.isOpen(open)); // true
595 | * console.log(utils.isOpen(close)); // false
596 | * ```
597 | * @param {Object} `node` Instance of [snapdragon-node][]
598 | * @return {Boolean}
599 | * @api public
600 | */
601 |
602 | utils.isOpen = function(node) {
603 | if (!node) return false;
604 | if (node.parent && typeof node.parent.isOpen === 'function') {
605 | return node.parent.isOpen(node);
606 | }
607 | if (node && typeof node.isOpen === 'function') {
608 | return node.isOpen(node);
609 | }
610 | return node.type ? node.type.slice(-5) === '.open' : false;
611 | };
612 |
613 | /**
614 | * Returns true if the given node is a "*.close" node.
615 | *
616 | * ```js
617 | * var Node = require('snapdragon-node');
618 | * var brace = new Node({type: 'brace'});
619 | * var open = new Node({type: 'brace.open'});
620 | * var close = new Node({type: 'brace.close'});
621 | *
622 | * console.log(utils.isClose(brace)); // false
623 | * console.log(utils.isClose(open)); // false
624 | * console.log(utils.isClose(close)); // true
625 | * ```
626 | * @param {Object} `node` Instance of [snapdragon-node][]
627 | * @return {Boolean}
628 | * @api public
629 | */
630 |
631 | utils.isClose = function(node) {
632 | if (!node) return false;
633 | if (node.parent && typeof node.parent.isClose === 'function') {
634 | return node.parent.isClose(node);
635 | }
636 | if (node && typeof node.isClose === 'function') {
637 | return node.isClose(node);
638 | }
639 | return node.type ? node.type.slice(-6) === '.close' : false;
640 | };
641 |
642 | /**
643 | * Returns true if the given node is an "*.open" node.
644 | *
645 | * ```js
646 | * var Node = require('snapdragon-node');
647 | * var brace = new Node({type: 'brace'});
648 | * var open = new Node({type: 'brace.open', value: '{'});
649 | * var inner = new Node({type: 'text', value: 'a,b,c'});
650 | * var close = new Node({type: 'brace.close', value: '}'});
651 | * brace.push(open);
652 | * brace.push(inner);
653 | * brace.push(close);
654 | *
655 | * console.log(utils.isBlock(brace)); // true
656 | * ```
657 | * @param {Object} `node` Instance of [snapdragon-node][]
658 | * @return {Boolean}
659 | * @api public
660 | */
661 |
662 | utils.isBlock = function(node) {
663 | if (!node || !utils.isNode(node)) return false;
664 | if (!Array.isArray(node.nodes)) {
665 | return false;
666 | }
667 | if (node.parent && typeof node.parent.isBlock === 'function') {
668 | return node.parent.isBlock(node);
669 | }
670 | if (typeof node.isBlock === 'function') {
671 | return node.isBlock(node);
672 | }
673 | return utils.hasOpenAndClose(node);
674 | };
675 |
676 | /**
677 | * Returns true if `parent.nodes` has the given `node`.
678 | *
679 | * ```js
680 | * const foo = new Node({type: 'foo'});
681 | * const bar = new Node({type: 'bar'});
682 | * cosole.log(util.hasNode(foo, bar)); // false
683 | * foo.push(bar);
684 | * cosole.log(util.hasNode(foo, bar)); // true
685 | * ```
686 | * @param {String} `type`
687 | * @return {Boolean}
688 | * @api public
689 | */
690 |
691 | utils.hasNode = function(node, child) {
692 | if (!utils.isNode(node)) return false;
693 | if (typeof node.has === 'function') {
694 | return node.has(child);
695 | }
696 | if (node.nodes) {
697 | return node.nodes.indexOf(child) !== -1;
698 | }
699 | return false;
700 | };
701 |
702 | /**
703 | * Returns true if `node.nodes` **has** an `.open` node
704 | *
705 | * ```js
706 | * var Node = require('snapdragon-node');
707 | * var brace = new Node({
708 | * type: 'brace',
709 | * nodes: []
710 | * });
711 | *
712 | * var open = new Node({type: 'brace.open'});
713 | * console.log(utils.hasOpen(brace)); // false
714 | *
715 | * brace.pushNode(open);
716 | * console.log(utils.hasOpen(brace)); // true
717 | * ```
718 | * @param {Object} `node` Instance of [snapdragon-node][]
719 | * @return {Boolean}
720 | * @api public
721 | */
722 |
723 | utils.hasOpen = function(node) {
724 | assert(utils.isNode(node), 'expected node to be an instance of Node');
725 | var first = node.first || node.nodes ? node.nodes[0] : null;
726 | if (!utils.isNode(first)) return false;
727 | if (node.isOpen === 'function') {
728 | return node.isOpen(first);
729 | }
730 | return first.type === node.type + '.open';
731 | };
732 |
733 | /**
734 | * Returns true if `node.nodes` **has** a `.close` node
735 | *
736 | * ```js
737 | * var Node = require('snapdragon-node');
738 | * var brace = new Node({
739 | * type: 'brace',
740 | * nodes: []
741 | * });
742 | *
743 | * var close = new Node({type: 'brace.close'});
744 | * console.log(utils.hasClose(brace)); // false
745 | *
746 | * brace.pushNode(close);
747 | * console.log(utils.hasClose(brace)); // true
748 | * ```
749 | * @param {Object} `node` Instance of [snapdragon-node][]
750 | * @return {Boolean}
751 | * @api public
752 | */
753 |
754 | utils.hasClose = function(node) {
755 | assert(utils.isNode(node), 'expected node to be an instance of Node');
756 | var last = node.last || node.nodes ? node.nodes[node.nodes.length - 1] : null;
757 | if (!utils.isNode(last)) return false;
758 | if (typeof node.isClose === 'function') {
759 | return node.isClose(last);
760 | }
761 | return last.type === node.type + '.close';
762 | };
763 |
764 | /**
765 | * Returns true if `node.nodes` has both `.open` and `.close` nodes
766 | *
767 | * ```js
768 | * var Node = require('snapdragon-node');
769 | * var brace = new Node({
770 | * type: 'brace',
771 | * nodes: []
772 | * });
773 | *
774 | * var open = new Node({type: 'brace.open'});
775 | * var close = new Node({type: 'brace.close'});
776 | * console.log(utils.hasOpen(brace)); // false
777 | * console.log(utils.hasClose(brace)); // false
778 | *
779 | * brace.pushNode(open);
780 | * brace.pushNode(close);
781 | * console.log(utils.hasOpen(brace)); // true
782 | * console.log(utils.hasClose(brace)); // true
783 | * ```
784 | * @param {Object} `node` Instance of [snapdragon-node][]
785 | * @return {Boolean}
786 | * @api public
787 | */
788 |
789 | utils.hasOpenAndClose = function(node) {
790 | return utils.hasOpen(node) && utils.hasClose(node);
791 | };
792 |
793 | /**
794 | * Push the given `node` onto the `state.inside` array for the
795 | * given type. This array is used as a specialized "stack" for
796 | * only the given `node.type`.
797 | *
798 | * ```js
799 | * var state = { inside: {}};
800 | * var node = new Node({type: 'brace'});
801 | * utils.addType(state, node);
802 | * console.log(state.inside);
803 | * //=> { brace: [{type: 'brace'}] }
804 | * ```
805 | * @param {Object} `state` The `compiler.state` object or custom state object.
806 | * @param {Object} `node` Instance of [snapdragon-node][]
807 | * @return {Array} Returns the `state.inside` stack for the given type.
808 | * @api public
809 | */
810 |
811 | utils.addType = function(state, node) {
812 | assert(utils.isNode(node), 'expected node to be an instance of Node');
813 | assert(isObject(state), 'expected state to be an object');
814 |
815 | var type = node.parent
816 | ? node.parent.type
817 | : node.type.replace(/\.open$/, '');
818 |
819 | if (!state.hasOwnProperty('inside')) {
820 | state.inside = {};
821 | }
822 | if (!state.inside.hasOwnProperty(type)) {
823 | state.inside[type] = [];
824 | }
825 |
826 | var arr = state.inside[type];
827 | arr.push(node);
828 | return arr;
829 | };
830 |
831 | /**
832 | * Remove the given `node` from the `state.inside` array for the
833 | * given type. This array is used as a specialized "stack" for
834 | * only the given `node.type`.
835 | *
836 | * ```js
837 | * var state = { inside: {}};
838 | * var node = new Node({type: 'brace'});
839 | * utils.addType(state, node);
840 | * console.log(state.inside);
841 | * //=> { brace: [{type: 'brace'}] }
842 | * utils.removeType(state, node);
843 | * //=> { brace: [] }
844 | * ```
845 | * @param {Object} `state` The `compiler.state` object or custom state object.
846 | * @param {Object} `node` Instance of [snapdragon-node][]
847 | * @return {Array} Returns the `state.inside` stack for the given type.
848 | * @api public
849 | */
850 |
851 | utils.removeType = function(state, node) {
852 | assert(utils.isNode(node), 'expected node to be an instance of Node');
853 | assert(isObject(state), 'expected state to be an object');
854 |
855 | var type = node.parent
856 | ? node.parent.type
857 | : node.type.replace(/\.close$/, '');
858 |
859 | if (state.inside.hasOwnProperty(type)) {
860 | return state.inside[type].pop();
861 | }
862 | };
863 |
864 | /**
865 | * Returns true if `node.value` is an empty string, or `node.nodes` does
866 | * not contain any non-empty text nodes.
867 | *
868 | * ```js
869 | * var node = new Node({type: 'text'});
870 | * utils.isEmpty(node); //=> true
871 | * node.value = 'foo';
872 | * utils.isEmpty(node); //=> false
873 | * ```
874 | * @param {Object} `node` Instance of [snapdragon-node][]
875 | * @param {Function} `fn`
876 | * @return {Boolean}
877 | * @api public
878 | */
879 |
880 | utils.isEmpty = function(node, fn) {
881 | assert(utils.isNode(node), 'expected node to be an instance of Node');
882 |
883 | if (!Array.isArray(node.nodes)) {
884 | if (typeof fn === 'function') {
885 | return fn(node);
886 | }
887 | return !utils.value(node);
888 | }
889 |
890 | if (node.nodes.length === 0) {
891 | return true;
892 | }
893 |
894 | for (const child of node.nodes) {
895 | if (!utils.isEmpty(child, fn)) {
896 | return false;
897 | }
898 | }
899 |
900 | return true;
901 | };
902 |
903 | /**
904 | * Returns true if the `state.inside` stack for the given type exists
905 | * and has one or more nodes on it.
906 | *
907 | * ```js
908 | * var state = { inside: {}};
909 | * var node = new Node({type: 'brace'});
910 | * console.log(utils.isInsideType(state, 'brace')); //=> false
911 | * utils.addType(state, node);
912 | * console.log(utils.isInsideType(state, 'brace')); //=> true
913 | * utils.removeType(state, node);
914 | * console.log(utils.isInsideType(state, 'brace')); //=> false
915 | * ```
916 | * @param {Object} `state`
917 | * @param {String} `type`
918 | * @return {Boolean}
919 | * @api public
920 | */
921 |
922 | utils.isInsideType = function(state, type) {
923 | assert(isObject(state), 'expected state to be an object');
924 | assert(isString(type), 'expected type to be a string');
925 |
926 | if (!state.hasOwnProperty('inside')) {
927 | return false;
928 | }
929 |
930 | if (!state.inside.hasOwnProperty(type)) {
931 | return false;
932 | }
933 |
934 | return state.inside[type].length > 0;
935 | };
936 |
937 | /**
938 | * Returns true if `node` is either a child or grand-child of the given `type`,
939 | * or `state.inside[type]` is a non-empty array.
940 | *
941 | * ```js
942 | * var state = { inside: {}};
943 | * var node = new Node({type: 'brace'});
944 | * var open = new Node({type: 'brace.open'});
945 | * console.log(utils.isInside(state, open, 'brace')); //=> false
946 | * utils.pushNode(node, open);
947 | * console.log(utils.isInside(state, open, 'brace')); //=> true
948 | * ```
949 | * @param {Object} `state` Either the `compiler.state` object, if it exists, or a user-supplied state object.
950 | * @param {Object} `node` Instance of [snapdragon-node][]
951 | * @param {String} `type` The `node.type` to check for.
952 | * @return {Boolean}
953 | * @api public
954 | */
955 |
956 | utils.isInside = function(state, node, type) {
957 | assert(utils.isNode(node), 'expected node to be an instance of Node');
958 | assert(isObject(state), 'expected state to be an object');
959 |
960 | if (Array.isArray(type)) {
961 | for (var i = 0; i < type.length; i++) {
962 | if (utils.isInside(state, node, type[i])) {
963 | return true;
964 | }
965 | }
966 | return false;
967 | }
968 |
969 | var parent = node.parent;
970 | if (typeof type === 'string') {
971 | return (parent && parent.type === type) || utils.isInsideType(state, type);
972 | }
973 |
974 | if (typeOf(type) === 'regexp') {
975 | if (parent && parent.type && type.test(parent.type)) {
976 | return true;
977 | }
978 |
979 | var keys = Object.keys(state.inside);
980 | var len = keys.length;
981 | var idx = -1;
982 | while (++idx < len) {
983 | var key = keys[idx];
984 | var value = state.inside[key];
985 |
986 | if (Array.isArray(value) && value.length !== 0 && type.test(key)) {
987 | return true;
988 | }
989 | }
990 | }
991 | return false;
992 | };
993 |
994 | /**
995 | * Get the last `n` element from the given `array`. Used for getting
996 | * a node from `node.nodes.`
997 | *
998 | * @param {Array} `array`
999 | * @param {Number} `n`
1000 | * @return {undefined}
1001 | * @api public
1002 | */
1003 |
1004 | utils.last = function(arr, n) {
1005 | return Array.isArray(arr) ? arr[arr.length - (n || 1)] : null;
1006 | };
1007 |
1008 | utils.lastNode = function(node) {
1009 | return Array.isArray(node.nodes) ? utils.last(node.nodes) : null;
1010 | };
1011 |
1012 | /**
1013 | * Cast the given `value` to an array.
1014 | *
1015 | * ```js
1016 | * console.log(utils.arrayify(''));
1017 | * //=> []
1018 | * console.log(utils.arrayify('foo'));
1019 | * //=> ['foo']
1020 | * console.log(utils.arrayify(['foo']));
1021 | * //=> ['foo']
1022 | * ```
1023 | * @param {any} `value`
1024 | * @return {Array}
1025 | * @api public
1026 | */
1027 |
1028 | utils.arrayify = function(value) {
1029 | if (typeof value === 'string' && value !== '') {
1030 | return [value];
1031 | }
1032 | if (!Array.isArray(value)) {
1033 | return [];
1034 | }
1035 | return value;
1036 | };
1037 |
1038 | /**
1039 | * Convert the given `value` to a string by joining with `,`. Useful
1040 | * for creating a cheerio/CSS/DOM-style selector from a list of strings.
1041 | *
1042 | * @param {any} `value`
1043 | * @return {Array}
1044 | * @api public
1045 | */
1046 |
1047 | utils.stringify = function(value) {
1048 | return utils.arrayify(value).join(',');
1049 | };
1050 |
1051 | /**
1052 | * Ensure that the given value is a string and call `.trim()` on it,
1053 | * or return an empty string.
1054 | *
1055 | * @param {String} `str`
1056 | * @return {String}
1057 | * @api public
1058 | */
1059 |
1060 | utils.trim = function(str) {
1061 | return typeof str === 'string' ? str.trim() : '';
1062 | };
1063 |
1064 | /**
1065 | * Return true if value is an object
1066 | */
1067 |
1068 | function isObject(value) {
1069 | return typeOf(value) === 'object';
1070 | }
1071 |
1072 | /**
1073 | * Return true if value is a string
1074 | */
1075 |
1076 | function isString(value) {
1077 | return typeof value === 'string';
1078 | }
1079 |
1080 | /**
1081 | * Return true if value is a function
1082 | */
1083 |
1084 | function isFunction(value) {
1085 | return typeof value === 'function';
1086 | }
1087 |
1088 | /**
1089 | * Return true if value is an array
1090 | */
1091 |
1092 | function isArray(value) {
1093 | return Array.isArray(value);
1094 | }
1095 |
1096 | /**
1097 | * Shim to ensure the `.append` methods work with any version of snapdragon
1098 | */
1099 |
1100 | function append(compiler, value, node) {
1101 | if (typeof compiler.append !== 'function') {
1102 | return compiler.emit(value, node);
1103 | }
1104 | return compiler.append(value, node);
1105 | }
1106 |
1107 | /**
1108 | * Simplified assertion. Throws an error is `value` is falsey.
1109 | */
1110 |
1111 | function assert(value, message) {
1112 | if (!value) throw new Error(message);
1113 | }
1114 | function expect(node, name, Node) {
1115 | const isNode = (Node && Node.isNode) ? Node.isNode : utils.isNode;
1116 | assert(isNode(node), `expected ${name} to be an instance of Node`);
1117 | }
1118 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | var assert = require('assert');
5 | var Parser = require('snapdragon/lib/parser');
6 | var Compiler = require('snapdragon/lib/compiler');
7 | var decorate = require('./support');
8 | var utils = require('..');
9 | var parser;
10 | var ast;
11 |
12 | class Node {
13 | constructor(node) {
14 | this.define(this, 'parent', null);
15 | this.isNode = true;
16 | this.type = node.type;
17 | this.value = node.value;
18 | if (node.nodes) {
19 | this.nodes = node.nodes;
20 | }
21 | }
22 | define(key, value) {
23 | Object.defineProperty(this, key, { value: value });
24 | return this;
25 | }
26 | get siblings() {
27 | return this.parent ? this.parent.nodes : null;
28 | }
29 | get last() {
30 | if (this.nodes && this.nodes.length) {
31 | return this.nodes[this.nodes.length - 1];
32 | }
33 | }
34 | }
35 |
36 | describe('snapdragon-node', function() {
37 | beforeEach(function() {
38 | parser = new Parser({Node: Node})
39 | .set('text', function() {
40 | var match = this.match(/^[a-z]+/);
41 | if (match) {
42 | return this.node(match[0]);
43 | }
44 | })
45 | .set('slash', function() {
46 | var match = this.match(/^\//);
47 | if (match) {
48 | return this.node(match[0]);
49 | }
50 | })
51 | .set('star', function() {
52 | var match = this.match(/^\*/);
53 | if (match) {
54 | return this.node(match[0]);
55 | }
56 | })
57 |
58 | ast = new Node(parser.parse('a/*/c'));
59 |
60 | // console.log(ast)
61 | });
62 |
63 | describe('.arrayify', function() {
64 | it('should cast a string to an array', function() {
65 | assert.deepEqual(utils.arrayify('foo'), ['foo']);
66 | });
67 |
68 | it('should return an array', function() {
69 | assert.deepEqual(utils.arrayify(['foo']), ['foo']);
70 | });
71 |
72 | it('should return an empty array when not a string or array', function() {
73 | assert.deepEqual(utils.arrayify(), []);
74 | });
75 | });
76 |
77 | describe('.stringify', function() {
78 | it('should return a string', function() {
79 | assert.equal(utils.stringify('foo'), 'foo');
80 | });
81 |
82 | it('should stringify an array', function() {
83 | assert.equal(utils.stringify(['foo', 'bar']), 'foo,bar');
84 | });
85 | });
86 |
87 | describe('.identity', function() {
88 | it('should return node.value as it was created by the parser', function() {
89 | var res = new Compiler()
90 | .set('star', utils.identity)
91 | .set('slash', utils.identity)
92 | .set('text', utils.identity)
93 | .compile(ast);
94 |
95 | assert.equal(res.output, 'a/*/c');
96 | });
97 | });
98 |
99 | describe('.noop', function() {
100 | it('should make a node an empty text node', function() {
101 | var res = new Compiler()
102 | .set('star', utils.noop)
103 | .set('slash', utils.identity)
104 | .set('text', utils.identity)
105 | .compile(ast);
106 |
107 | assert.equal(res.output, 'a//c');
108 | });
109 | });
110 |
111 | describe('.append', function() {
112 | it('should append the specified text', function() {
113 | var res = new Compiler()
114 | .set('star', utils.append('@'))
115 | .set('slash', utils.append('\\'))
116 | .set('text', utils.identity)
117 | .compile(ast);
118 |
119 | assert.equal(res.output, 'a\\@\\c');
120 | });
121 |
122 | it('should use compiler.append method when it exists', function() {
123 | var compiler = new Compiler()
124 | compiler.append = compiler.emit.bind(compiler);
125 |
126 | var res = compiler.set('star', utils.append('@'))
127 | .set('slash', utils.append('\\'))
128 | .set('text', utils.identity)
129 | .compile(ast);
130 |
131 | assert.equal(res.output, 'a\\@\\c');
132 | });
133 | });
134 |
135 | describe('.toNoop', function() {
136 | it('should throw an error when node is not a node', function() {
137 | assert.throws(function() {
138 | utils.toNoop();
139 | });
140 | });
141 |
142 | it('should convert a node to a noop node', function() {
143 | utils.toNoop(ast);
144 | assert(!ast.nodes);
145 | });
146 |
147 | it('should convert a node to a noop with the given nodes value', function() {
148 | utils.toNoop(ast, []);
149 | assert.equal(ast.nodes.length, 0);
150 | });
151 | });
152 |
153 | describe('.visit', function() {
154 | it('should throw an error when not a node', function() {
155 | assert.throws(function() {
156 | utils.visit();
157 | });
158 | });
159 |
160 | it('should throw an error when node.nodes is not an array', function() {
161 | assert.throws(function() {
162 | utils.visit(new Node({type: 'foo', value: ''}));
163 | });
164 | });
165 |
166 | it('should visit a node with the given function', function() {
167 | var type = null;
168 | utils.visit(ast, function(node) {
169 | if (type === null) {
170 | type = node.type;
171 | }
172 | });
173 | assert.equal(type, 'root');
174 | });
175 | });
176 |
177 | describe('.mapVisit', function() {
178 | it('should throw an error when not a node', function() {
179 | assert.throws(function() {
180 | utils.mapVisit();
181 | });
182 | });
183 |
184 | it('should throw an error when node.nodes is not an array', function() {
185 | assert.throws(function() {
186 | utils.mapVisit(new Node({type: 'foo', value: ''}));
187 | });
188 | });
189 |
190 | it('should map "visit" over node.nodes', function() {
191 | var type = null;
192 | utils.mapVisit(ast, function(node) {
193 | if (type === null && node.parent && node.parent.type === 'root') {
194 | type = node.type;
195 | }
196 | });
197 | assert.equal(type, 'bos');
198 | });
199 | });
200 |
201 | describe('.pushNode', function() {
202 | it('should throw an error when not a node', function() {
203 | assert.throws(function() {
204 | utils.pushNode();
205 | });
206 | });
207 |
208 | it('should add a node to the end of node.nodes', function() {
209 | var node = new Node({type: 'brace'});
210 | var a = new Node({type: 'a', value: 'foo'});
211 | var b = new Node({type: 'b', value: 'foo'});
212 | utils.pushNode(node, a);
213 | utils.pushNode(node, b);
214 | assert.equal(node.nodes[0].type, 'a');
215 | assert.equal(node.nodes[1].type, 'b');
216 | });
217 |
218 | it('should work when node.push is not a function', function() {
219 | var node = new Node({type: 'brace'});
220 | var a = new Node({type: 'a', value: 'foo'});
221 | var b = new Node({type: 'b', value: 'foo'});
222 |
223 | node.pushNode = null;
224 | node.push = null;
225 |
226 | utils.pushNode(node, a);
227 | utils.pushNode(node, b);
228 | assert.equal(node.nodes[0].type, 'a');
229 | assert.equal(node.nodes[1].type, 'b');
230 | });
231 | });
232 |
233 | describe('.unshiftNode', function() {
234 | it('should throw an error when parent is not a node', function() {
235 | assert.throws(function() {
236 | utils.unshiftNode();
237 | });
238 | });
239 |
240 | it('should add a node to the beginning of node.nodes', function() {
241 | var node = new Node({type: 'brace'});
242 | var a = new Node({type: 'a', value: 'foo'});
243 | var b = new Node({type: 'b', value: 'foo'});
244 | utils.unshiftNode(node, a);
245 | utils.unshiftNode(node, b);
246 | assert.equal(node.nodes[1].type, 'a');
247 | assert.equal(node.nodes[0].type, 'b');
248 | });
249 |
250 | it('should work when node.unshift is not a function', function() {
251 | var node = new Node({type: 'brace'});
252 | var a = new Node({type: 'a', value: 'foo'});
253 | var b = new Node({type: 'b', value: 'foo'});
254 |
255 | node.unshiftNode = null;
256 | node.unshift = null;
257 |
258 | utils.unshiftNode(node, a);
259 | utils.unshiftNode(node, b);
260 | assert.equal(node.nodes[1].type, 'a');
261 | assert.equal(node.nodes[0].type, 'b');
262 | });
263 | });
264 |
265 | describe('.popNode', function() {
266 | it('should throw an error when not a node', function() {
267 | assert.throws(function() {
268 | utils.popNode();
269 | });
270 | });
271 |
272 | it('should pop a node from node.nodes', function() {
273 | var node = new Node({type: 'brace'});
274 | var a = new Node({type: 'a', value: 'foo'});
275 | var b = new Node({type: 'b', value: 'foo'});
276 | utils.pushNode(node, a);
277 | utils.pushNode(node, b);
278 | assert.equal(node.nodes[0].type, 'a');
279 | assert.equal(node.nodes[1].type, 'b');
280 |
281 | utils.popNode(node);
282 | utils.popNode(node);
283 | assert.equal(node.nodes.length, 0);
284 | });
285 |
286 | it('should work when node.pop is not a function', function() {
287 | var node = new Node({type: 'brace'});
288 | var a = new Node({type: 'a', value: 'foo'});
289 | var b = new Node({type: 'b', value: 'foo'});
290 |
291 | node.popNode = null;
292 | node.pop = null;
293 |
294 | utils.pushNode(node, a);
295 | utils.pushNode(node, b);
296 | assert.equal(node.nodes[0].type, 'a');
297 | assert.equal(node.nodes[1].type, 'b');
298 |
299 | utils.popNode(node);
300 | utils.popNode(node);
301 | assert.equal(node.nodes.length, 0);
302 | });
303 |
304 | it('should work when node.pop is a function', function() {
305 | var node = new Node({type: 'brace'});
306 | var a = new Node({type: 'a', value: 'foo'});
307 | var b = new Node({type: 'b', value: 'foo'});
308 |
309 | decorate(node);
310 |
311 | utils.pushNode(node, a);
312 | utils.pushNode(node, b);
313 | assert.equal(node.nodes[0].type, 'a');
314 | assert.equal(node.nodes[1].type, 'b');
315 |
316 | utils.popNode(node);
317 | utils.popNode(node);
318 | assert.equal(node.nodes.length, 0);
319 | });
320 | });
321 |
322 | describe('.shiftNode', function() {
323 | it('should throw an error when not a node', function() {
324 | assert.throws(function() {
325 | utils.shiftNode();
326 | });
327 | });
328 |
329 | it('should shift a node from node.nodes', function() {
330 | var node = new Node({type: 'brace'});
331 | var a = new Node({type: 'a', value: 'foo'});
332 | var b = new Node({type: 'b', value: 'foo'});
333 | utils.pushNode(node, a);
334 | utils.pushNode(node, b);
335 | assert.equal(node.nodes[0].type, 'a');
336 | assert.equal(node.nodes[1].type, 'b');
337 |
338 | utils.shiftNode(node);
339 | utils.shiftNode(node);
340 | assert.equal(node.nodes.length, 0);
341 | });
342 |
343 | it('should work when node.shift is not a function', function() {
344 | var node = new Node({type: 'brace'});
345 | var a = new Node({type: 'a', value: 'foo'});
346 | var b = new Node({type: 'b', value: 'foo'});
347 |
348 | node.shiftNode = null;
349 | node.shift = null;
350 |
351 | utils.pushNode(node, a);
352 | utils.pushNode(node, b);
353 | assert.equal(node.nodes[0].type, 'a');
354 | assert.equal(node.nodes[1].type, 'b');
355 |
356 | utils.shiftNode(node);
357 | utils.shiftNode(node);
358 | assert.equal(node.nodes.length, 0);
359 | });
360 |
361 | it('should work when node.shift is a function', function() {
362 | var node = new Node({type: 'brace'});
363 | var a = new Node({type: 'a', value: 'foo'});
364 | var b = new Node({type: 'b', value: 'foo'});
365 |
366 | decorate(node);
367 |
368 | utils.pushNode(node, a);
369 | utils.pushNode(node, b);
370 | assert.equal(node.nodes[0].type, 'a');
371 | assert.equal(node.nodes[1].type, 'b');
372 |
373 | utils.shiftNode(node);
374 | utils.shiftNode(node);
375 | assert.equal(node.nodes.length, 0);
376 | });
377 | });
378 |
379 | describe('.removeNode', function() {
380 | it('should throw an error when not a node', function() {
381 | assert.throws(function() {
382 | utils.removeNode();
383 | });
384 | });
385 |
386 | it('should remove a node from node.nodes', function() {
387 | var node = new Node({type: 'brace'});
388 | var a = new Node({type: 'a', value: 'foo'});
389 | var b = new Node({type: 'b', value: 'foo'});
390 | utils.pushNode(node, a);
391 | utils.pushNode(node, b);
392 | assert.equal(node.nodes[0].type, 'a');
393 | assert.equal(node.nodes[1].type, 'b');
394 |
395 | utils.removeNode(node, a);
396 | assert.equal(node.nodes.length, 1);
397 |
398 | utils.removeNode(node, b);
399 | assert.equal(node.nodes.length, 0);
400 | });
401 |
402 | it('should work when node.remove is not a function', function() {
403 | var node = new Node({type: 'brace'});
404 | var a = new Node({type: 'a', value: 'foo'});
405 | var b = new Node({type: 'b', value: 'foo'});
406 |
407 | node.removeNode = null;
408 | node.remove = null;
409 |
410 | utils.pushNode(node, a);
411 | utils.pushNode(node, b);
412 | assert.equal(node.nodes[0].type, 'a');
413 | assert.equal(node.nodes[1].type, 'b');
414 |
415 | utils.removeNode(node, a);
416 | assert.equal(node.nodes.length, 1);
417 |
418 | utils.removeNode(node, b);
419 | assert.equal(node.nodes.length, 0);
420 | });
421 |
422 | it('should work when node.remove is a function', function() {
423 | var node = new Node({type: 'brace'});
424 | var a = new Node({type: 'a', value: 'foo'});
425 | var b = new Node({type: 'b', value: 'foo'});
426 |
427 | decorate(node);
428 |
429 | utils.pushNode(node, a);
430 | utils.pushNode(node, b);
431 | assert.equal(node.nodes[0].type, 'a');
432 | assert.equal(node.nodes[1].type, 'b');
433 |
434 | utils.removeNode(node, a);
435 | utils.removeNode(node, b);
436 | assert.equal(node.nodes.length, 0);
437 | });
438 |
439 | it('should return when node.nodes does not exist', function() {
440 | assert.doesNotThrow(function() {
441 | var node = new Node({type: 'brace'});
442 | utils.removeNode(node, node);
443 | });
444 |
445 | assert.doesNotThrow(function() {
446 | var node = new Node({type: 'brace'});
447 | node.removeNode = null;
448 | node.remove = null;
449 | utils.removeNode(node, node);
450 | });
451 | });
452 |
453 | it('should return when the given node is not in node.nodes', function() {
454 | assert.doesNotThrow(function() {
455 | var node = new Node({type: 'brace'});
456 | var foo = new Node({type: 'foo'});
457 | var bar = new Node({type: 'bar'});
458 | utils.pushNode(node, bar);
459 | utils.removeNode(node, foo);
460 | });
461 |
462 | assert.doesNotThrow(function() {
463 | var node = new Node({type: 'brace'});
464 | var foo = new Node({type: 'foo'});
465 | var bar = new Node({type: 'bar'});
466 | node.removeNode = null;
467 | node.remove = null;
468 | utils.pushNode(node, bar);
469 | utils.removeNode(node, foo);
470 | });
471 | });
472 | });
473 |
474 | describe('.addOpen', function() {
475 | it('should throw an error when not a node', function() {
476 | assert.throws(function() {
477 | utils.addOpen();
478 | });
479 | });
480 |
481 | it('should add an open node', function() {
482 | var node = new Node({type: 'brace'});
483 | var text = new Node({type: 'text', value: 'foo'});
484 | utils.addOpen(node, Node);
485 | assert.equal(node.nodes[0].type, 'brace.open');
486 | });
487 |
488 | it('should work when node.unshift is a function', function() {
489 | var node = new Node({type: 'brace'});
490 | var text = new Node({type: 'text', value: 'foo'});
491 | decorate(node);
492 | utils.addOpen(node, Node);
493 | assert.equal(node.nodes[0].type, 'brace.open');
494 | });
495 |
496 | it('should work when node.unshift is not a function', function() {
497 | var node = new Node({type: 'brace'});
498 | var text = new Node({type: 'text', value: 'foo'});
499 | node.unshiftNode = null;
500 | node.unshift = null;
501 | utils.addOpen(node, Node);
502 | assert.equal(node.nodes[0].type, 'brace.open');
503 | });
504 |
505 | it('should take a filter function', function() {
506 | var node = new Node({type: 'brace'});
507 | var text = new Node({type: 'text', value: 'foo'});
508 | utils.addOpen(node, Node, function(node) {
509 | return node.type !== 'brace';
510 | });
511 | assert(!node.nodes);
512 | });
513 |
514 | it('should use the given value on the open node', function() {
515 | var node = new Node({type: 'brace'});
516 | var text = new Node({type: 'text', value: 'foo'});
517 | utils.addOpen(node, Node, '{');
518 | assert.equal(node.nodes[0].value, '{');
519 | });
520 | });
521 |
522 | describe('.addClose', function() {
523 | it('should throw an error when not a node', function() {
524 | assert.throws(function() {
525 | utils.addClose();
526 | });
527 | });
528 |
529 | it('should add a close node', function() {
530 | var node = new Node({type: 'brace'});
531 | var text = new Node({type: 'text', value: 'foo'});
532 | utils.pushNode(node, text);
533 | utils.addClose(node, Node);
534 |
535 | assert.equal(node.nodes[0].type, 'text');
536 | assert.equal(node.nodes[1].type, 'brace.close');
537 | });
538 |
539 | it('should work when node.push is not a function', function() {
540 | var node = new Node({type: 'brace'});
541 | var text = new Node({type: 'text', value: 'foo'});
542 | node.pushNode = null;
543 | node.push = null;
544 |
545 | utils.pushNode(node, text);
546 | utils.addClose(node, Node);
547 |
548 | assert.equal(node.nodes[0].type, 'text');
549 | assert.equal(node.nodes[1].type, 'brace.close');
550 | });
551 |
552 | it('should work when node.push is a function', function() {
553 | var node = new Node({type: 'brace'});
554 | var text = new Node({type: 'text', value: 'foo'});
555 | decorate(node);
556 |
557 | utils.pushNode(node, text);
558 | utils.addClose(node, Node);
559 |
560 | assert.equal(node.nodes[0].type, 'text');
561 | assert.equal(node.nodes[1].type, 'brace.close');
562 | });
563 |
564 | it('should take a filter function', function() {
565 | var node = new Node({type: 'brace'});
566 | var text = new Node({type: 'text', value: 'foo'});
567 | utils.addClose(node, Node, function(node) {
568 | return node.type !== 'brace';
569 | });
570 | assert(!node.nodes);
571 | });
572 |
573 | it('should use the given value on the close node', function() {
574 | var node = new Node({type: 'brace'});
575 | var text = new Node({type: 'text', value: 'foo'});
576 | utils.addClose(node, Node, '}');
577 | assert.equal(node.nodes[0].value, '}');
578 | });
579 | });
580 |
581 | describe('.wrapNodes', function() {
582 | it('should throw an error when not a node', function() {
583 | assert.throws(function() {
584 | utils.wrapNodes();
585 | });
586 | });
587 |
588 | it('should add an open node', function() {
589 | var node = new Node({type: 'brace'});
590 | var text = new Node({type: 'text', value: 'foo'});
591 | utils.wrapNodes(node, Node);
592 |
593 | assert.equal(node.nodes[0].type, 'brace.open');
594 | });
595 |
596 | it('should add a close node', function() {
597 | var node = new Node({type: 'brace'});
598 | var text = new Node({type: 'text', value: 'foo'});
599 | utils.pushNode(node, text);
600 | utils.wrapNodes(node, Node);
601 |
602 | assert.equal(node.nodes[0].type, 'brace.open');
603 | assert.equal(node.nodes[1].type, 'text');
604 | assert.equal(node.nodes[2].type, 'brace.close');
605 | });
606 | });
607 |
608 | describe('.isEmpty', function() {
609 | it('should throw an error when not a node', function() {
610 | assert.throws(function() {
611 | utils.isEmpty();
612 | });
613 | });
614 |
615 | it('should return true node.value is an empty string', function() {
616 | assert(utils.isEmpty(new Node({type: 'text', value: ''})));
617 | });
618 |
619 | it('should return true node.value is undefined', function() {
620 | assert(utils.isEmpty(new Node({type: 'text'})));
621 | });
622 |
623 | it('should return true when node.nodes is empty', function() {
624 | var foo = new Node({type: 'foo'});
625 | var bar = new Node({type: 'text', value: 'bar'});
626 | utils.pushNode(foo, bar);
627 | assert(!utils.isEmpty(foo));
628 | utils.shiftNode(foo);
629 | assert(utils.isEmpty(foo));
630 | });
631 |
632 | it('should return true when node.nodes is all non-text nodes', function() {
633 | var node = new Node({type: 'parent'});
634 | var foo = new Node({type: 'foo'});
635 | var bar = new Node({type: 'bar'});
636 | var baz = new Node({type: 'baz'});
637 | utils.pushNode(node, foo);
638 | utils.pushNode(node, bar);
639 | utils.pushNode(node, baz);
640 | assert(utils.isEmpty(foo));
641 | });
642 |
643 | it('should return call a custom function if only one node exists', function() {
644 | var foo = new Node({type: 'foo'});
645 | var text = new Node({type: 'text', value: ''});
646 | utils.pushNode(foo, text);
647 | assert(utils.isEmpty(foo, node => !node.value));
648 | });
649 |
650 | it('should return true when only open and close nodes exist', function() {
651 | var brace = new Node({type: 'brace'});
652 | var open = new Node({type: 'brace.open'});
653 | var close = new Node({type: 'brace.close'});
654 | utils.pushNode(brace, open);
655 | utils.pushNode(brace, close);
656 | assert(utils.isEmpty(brace));
657 | });
658 |
659 | it('should call a custom function on "middle" nodes (1)', function() {
660 | var brace = new Node({type: 'brace'});
661 | var open = new Node({type: 'brace.open'});
662 | var text = new Node({type: 'text', value: ''});
663 | var close = new Node({type: 'brace.close'});
664 | utils.pushNode(brace, open);
665 | utils.pushNode(brace, text);
666 | utils.pushNode(brace, text);
667 | utils.pushNode(brace, text);
668 | utils.pushNode(brace, close);
669 | assert(utils.isEmpty(brace, function(node) {
670 | if (node.nodes && node.nodes.length === 0) {
671 | return true;
672 | }
673 | return !utils.value(node);
674 | }));
675 | });
676 |
677 | it('should call a custom function on "middle" nodes (2)', function() {
678 | var brace = new Node({type: 'brace'});
679 | var open = new Node({type: 'brace.open'});
680 | var text = new Node({type: 'text', value: ''});
681 | var close = new Node({type: 'brace.close'});
682 | utils.pushNode(brace, open);
683 | utils.pushNode(brace, text);
684 | utils.pushNode(brace, text);
685 | utils.pushNode(brace, text);
686 | utils.pushNode(brace, close);
687 | assert(!utils.isEmpty(brace, function(node) {
688 | return node.parent.nodes.length === 0;
689 | }));
690 | });
691 |
692 | it('should call a custom function on "middle" nodes (3)', function() {
693 | var brace = new Node({type: 'brace'});
694 | var open = new Node({type: 'brace.open'});
695 | var text = new Node({type: 'text', value: 'foo'});
696 | var close = new Node({type: 'brace.close'});
697 | utils.pushNode(brace, open);
698 | utils.pushNode(brace, text);
699 | utils.pushNode(brace, close);
700 | assert(!utils.isEmpty(brace, function(node) {
701 | if (node.type !== 'text') {
702 | return false;
703 | }
704 | return node.value.trim() === '';
705 | }));
706 | });
707 |
708 | it('should call a custom function on "middle" nodes (4)', function() {
709 | var brace = new Node({type: 'brace'});
710 | var open = new Node({type: 'brace.open'});
711 | var empty = new Node({type: 'text', value: ''});
712 | var text = new Node({type: 'text', value: 'foo'});
713 | var close = new Node({type: 'brace.close'});
714 | utils.pushNode(brace, open);
715 | utils.pushNode(brace, empty);
716 | utils.pushNode(brace, empty);
717 | utils.pushNode(brace, empty);
718 | utils.pushNode(brace, empty);
719 | utils.pushNode(brace, text);
720 | utils.pushNode(brace, close);
721 | assert(!utils.isEmpty(brace, function(node) {
722 | if (node.type !== 'text') {
723 | return false;
724 | }
725 | return node.value.trim() === '';
726 | }));
727 | });
728 | });
729 |
730 | describe('.isType', function() {
731 | it('should throw an error when matcher is invalid', function() {
732 | assert.throws(function() {
733 | utils.isType(new Node({type: 'foo'}));
734 | });
735 | });
736 |
737 | it('should return false if the node is not the given type', function() {
738 | assert(!utils.isType());
739 | assert(!utils.isType({}, 'root'));
740 | });
741 |
742 | it('should return true if the node is the given type', function() {
743 | assert(utils.isType(ast, 'root'));
744 | assert(utils.isType(ast.last, 'eos'));
745 | });
746 | });
747 |
748 | describe('.isInsideType', function() {
749 | it('should throw an error when parent is not a node', function() {
750 | assert.throws(function() {
751 | utils.isInsideType();
752 | });
753 | });
754 |
755 | it('should throw an error when child not a node', function() {
756 | assert.throws(function() {
757 | utils.isInsideType(new Node({type: 'foo'}));
758 | });
759 | });
760 |
761 | it('should return false when state.inside is not an object', function() {
762 | var state = {};
763 | var node = new Node({type: 'brace'});
764 | assert(!utils.isInsideType(state, 'brace'));
765 | });
766 |
767 | it('should return false when state.inside[type] is not an object', function() {
768 | var state = {inside: {}};
769 | var node = new Node({type: 'brace'});
770 | assert(!utils.isInsideType(state, 'brace'));
771 | });
772 |
773 | it('should return true when state has the given type', function() {
774 | var state = { inside: {}};
775 | var node = new Node({type: 'brace'});
776 | utils.addType(state, node);
777 | assert(utils.isInsideType(state, 'brace'));
778 | });
779 |
780 | it('should return false when state does not have the given type', function() {
781 | var state = { inside: {}};
782 | var node = new Node({type: 'brace'});
783 |
784 | utils.addType(state, node);
785 | assert(utils.isInsideType(state, 'brace'));
786 |
787 | utils.removeType(state, node);
788 | assert(!utils.isInsideType(state, 'brace'));
789 | });
790 | });
791 |
792 | describe('.isInside', function() {
793 | it('should throw an error when parent is not a node', function() {
794 | assert.throws(function() {
795 | utils.isInside();
796 | });
797 | });
798 |
799 | it('should throw an error when child not a node', function() {
800 | assert.throws(function() {
801 | utils.isInside(new Node({type: 'foo'}));
802 | });
803 | });
804 |
805 | it('should return false when state.inside is not an object', function() {
806 | var state = {};
807 | var node = new Node({type: 'brace'});
808 | assert(!utils.isInside(state, node, 'brace'));
809 | });
810 |
811 | it('should return false when state.inside[type] is not an object', function() {
812 | var state = {inside: {}};
813 | var node = new Node({type: 'brace'});
814 | assert(!utils.isInside(state, node, 'brace'));
815 | });
816 |
817 | it('should return true when state has the given type', function() {
818 | var state = { inside: {}};
819 | var node = new Node({type: 'brace'});
820 | utils.addType(state, node);
821 | assert(utils.isInside(state, node, 'brace'));
822 | });
823 |
824 | it('should return true when state has one of the given types', function() {
825 | var state = { inside: {}};
826 | var node = new Node({type: 'brace'});
827 | utils.addType(state, node);
828 | assert(utils.isInside(state, node, ['foo', 'brace']));
829 | });
830 |
831 | it('should return false when state does not have one of the given types', function() {
832 | var state = { inside: {}};
833 | var node = new Node({type: 'brace'});
834 | utils.addType(state, node);
835 | assert(!utils.isInside(state, node, ['foo', 'bar']));
836 | });
837 |
838 | it('should return true when a regex matches a type', function() {
839 | var state = { inside: {}};
840 | var node = new Node({type: 'brace'});
841 | utils.addType(state, node);
842 | assert(utils.isInside(state, node, /(foo|brace)/));
843 | });
844 |
845 | it('should return true when the type matches parent.type', function() {
846 | var state = {};
847 | var brace = new Node({type: 'brace'});
848 | var node = new Node({type: 'brace.open'});
849 | utils.pushNode(brace, node);
850 | assert(utils.isInside(state, node, 'brace'));
851 | });
852 |
853 | it('should return true when regex matches parent.type', function() {
854 | var state = {};
855 | var brace = new Node({type: 'brace'});
856 | var node = new Node({type: 'brace.open'});
857 | utils.pushNode(brace, node);
858 | assert(utils.isInside(state, node, /(foo|brace)/));
859 | });
860 |
861 | it('should return false when a regex does not match a type', function() {
862 | var state = { inside: {}};
863 | var node = new Node({type: 'brace'});
864 | utils.addType(state, node);
865 | assert(!utils.isInside(state, node, /(foo|bar)/));
866 | });
867 |
868 | it('should return false when type is invalie', function() {
869 | var state = { inside: {}};
870 | var node = new Node({type: 'brace'});
871 | utils.addType(state, node);
872 | assert(!utils.isInside(state, node, null));
873 | });
874 |
875 | it('should return false when state does not have the given type', function() {
876 | var state = { inside: {}};
877 | var node = new Node({type: 'brace'});
878 |
879 | utils.addType(state, node);
880 | assert(utils.isInside(state, node, 'brace'));
881 |
882 | utils.removeType(state, node);
883 | assert(!utils.isInside(state, node, 'brace'));
884 | });
885 | });
886 |
887 | describe('.hasType', function() {
888 | it('should return true if node.nodes has the given type', function() {
889 | assert(utils.hasType(ast, 'text'));
890 | assert(!utils.hasType(ast, 'foo'));
891 | });
892 |
893 | it('should return false when node.nodes does not exist', function() {
894 | assert(!utils.hasType(new Node({type: 'foo'})));
895 | });
896 | });
897 |
898 | describe('.firstOfType', function() {
899 | it('should throw an error when not a node', function() {
900 | assert.throws(function() {
901 | utils.firstOfType();
902 | });
903 | });
904 |
905 | it('should get the first node of the given type', function() {
906 | var node = utils.firstOfType(ast.nodes, 'text');
907 | assert.equal(node.type, 'text');
908 | });
909 | });
910 |
911 | describe('.last', function() {
912 | it('should get the last node', function() {
913 | assert.equal(utils.last(ast.nodes).type, 'eos');
914 | });
915 | });
916 |
917 | describe('.findNode', function() {
918 | it('should get the node with the given type', function() {
919 | var text = utils.findNode(ast.nodes, 'text');
920 | assert.equal(text.type, 'text');
921 | });
922 |
923 | it('should get the node matching the given regex', function() {
924 | var text = utils.findNode(ast.nodes, /text/);
925 | assert.equal(text.type, 'text');
926 | });
927 |
928 | it('should get the first matching node', function() {
929 | var node = utils.findNode(ast.nodes, [/text/, 'bos']);
930 | assert.equal(node.type, 'bos');
931 |
932 | node = utils.findNode(ast.nodes, [/text/]);
933 | assert.equal(node.type, 'text');
934 | });
935 |
936 | it('should get the node at the given index', function() {
937 | var bos = utils.findNode(ast.nodes, 0);
938 | assert.equal(bos.type, 'bos');
939 |
940 | var text = utils.findNode(ast.nodes, 1);
941 | assert.equal(text.type, 'text');
942 | });
943 |
944 | it('should return null when node does not exist', function() {
945 | assert.equal(utils.findNode(new Node({type: 'foo'})), null);
946 | });
947 | });
948 |
949 | describe('.removeNode', function() {
950 | it('should throw an error when parent is not a node', function() {
951 | assert.throws(function() {
952 | utils.removeNode();
953 | });
954 | });
955 |
956 | it('should remove a node from parent.nodes', function() {
957 | var brace = new Node({type: 'brace'});
958 | var open = new Node({type: 'brace.open'});
959 | var foo = new Node({type: 'foo'});
960 | var bar = new Node({type: 'bar'});
961 | var baz = new Node({type: 'baz'});
962 | var qux = new Node({type: 'qux'});
963 | var close = new Node({type: 'brace.close'});
964 | utils.pushNode(brace, open);
965 | utils.pushNode(brace, foo);
966 | utils.pushNode(brace, bar);
967 | utils.pushNode(brace, baz);
968 | utils.pushNode(brace, qux);
969 | utils.pushNode(brace, close);
970 |
971 | assert.equal(brace.nodes.length, 6);
972 | assert.equal(brace.nodes[0].type, 'brace.open');
973 | assert.equal(brace.nodes[1].type, 'foo');
974 | assert.equal(brace.nodes[2].type, 'bar');
975 | assert.equal(brace.nodes[3].type, 'baz');
976 | assert.equal(brace.nodes[4].type, 'qux');
977 | assert.equal(brace.nodes[5].type, 'brace.close');
978 |
979 | // remove node
980 | utils.removeNode(brace, bar);
981 | assert.equal(brace.nodes.length, 5);
982 | assert.equal(brace.nodes[0].type, 'brace.open');
983 | assert.equal(brace.nodes[1].type, 'foo');
984 | assert.equal(brace.nodes[2].type, 'baz');
985 | assert.equal(brace.nodes[3].type, 'qux');
986 | assert.equal(brace.nodes[4].type, 'brace.close');
987 | });
988 | });
989 |
990 | describe('.isOpen', function() {
991 | it('should be true if node is an ".open" node', function() {
992 | var node = new Node({type: 'foo.open'});
993 | assert(utils.isOpen(node));
994 | });
995 |
996 | it('should be false if node is not an ".open" node', function() {
997 | var node = new Node({type: 'foo'});
998 | assert(!utils.isOpen(node));
999 | });
1000 | });
1001 |
1002 | describe('.isClose', function() {
1003 | it('should be true if node is a ".close" node', function() {
1004 | var node = new Node({type: 'foo.close'});
1005 | assert(utils.isClose(node));
1006 | });
1007 |
1008 | it('should be false if node is not a ".close" node', function() {
1009 | var node = new Node({type: 'foo'});
1010 | assert(!utils.isClose(node));
1011 | });
1012 | });
1013 |
1014 | describe('.hasOpen', function() {
1015 | it('should throw an error when not a node', function() {
1016 | assert.throws(function() {
1017 | utils.hasOpen();
1018 | });
1019 | });
1020 |
1021 | it('should be true if node has an ".open" node', function() {
1022 | var parent = new Node({type: 'foo'});
1023 | var node = new Node({type: 'foo.open'});
1024 | utils.pushNode(parent, node);
1025 | assert(utils.hasOpen(parent));
1026 | });
1027 |
1028 | it('should be false if does not have an ".open" node', function() {
1029 | var parent = new Node({type: 'foo'});
1030 | assert(!utils.hasOpen(parent));
1031 | });
1032 | });
1033 |
1034 | describe('.hasClose', function() {
1035 | it('should throw an error when not a node', function() {
1036 | assert.throws(function() {
1037 | utils.hasClose();
1038 | });
1039 | });
1040 |
1041 | it('should be true if node has a ".close" node', function() {
1042 | var parent = new Node({type: 'foo'});
1043 | var open = new Node({type: 'foo.open'});
1044 | var close = new Node({type: 'foo.close'});
1045 | utils.pushNode(parent, open);
1046 | utils.pushNode(parent, close);
1047 | assert(utils.hasClose(parent));
1048 | });
1049 |
1050 | it('should be false if does not have a ".close" node', function() {
1051 | var parent = new Node({type: 'foo'});
1052 | assert(!utils.hasClose(parent));
1053 | });
1054 | });
1055 |
1056 | describe('.hasOpenAndClose', function() {
1057 | it('should throw an error when not a node', function() {
1058 | assert.throws(function() {
1059 | utils.hasOpenAndClose();
1060 | });
1061 | });
1062 |
1063 | it('should be true if node has ".open" and ".close" nodes', function() {
1064 | var parent = new Node({type: 'foo'});
1065 | var open = new Node({type: 'foo.open'});
1066 | var close = new Node({type: 'foo.close'});
1067 | utils.pushNode(parent, open);
1068 | utils.pushNode(parent, close);
1069 | assert(utils.hasOpenAndClose(parent));
1070 | });
1071 |
1072 | it('should be false if does not have a ".close" node', function() {
1073 | var parent = new Node({type: 'foo'});
1074 | var open = new Node({type: 'foo.open'});
1075 | utils.pushNode(parent, open);
1076 | assert(!utils.hasOpenAndClose(parent));
1077 | });
1078 |
1079 | it('should be false if does not have an ".open" node', function() {
1080 | var parent = new Node({type: 'foo'});
1081 | var close = new Node({type: 'foo.close'});
1082 | utils.pushNode(parent, close);
1083 | assert(!utils.hasOpenAndClose(parent));
1084 | });
1085 | });
1086 |
1087 | describe('.pushNode', function() {
1088 | it('should throw an error when parent is not a node', function() {
1089 | assert.throws(function() {
1090 | utils.pushNode();
1091 | });
1092 | });
1093 |
1094 | it('should add a node to `node.nodes`', function() {
1095 | var node = new Node({type: 'foo'});
1096 | utils.pushNode(ast, node);
1097 | assert.equal(ast.last.type, 'foo');
1098 | });
1099 |
1100 | it('should set the parent on the given node', function() {
1101 | var node = new Node({type: 'foo'});
1102 | utils.pushNode(ast, node);
1103 | assert.equal(node.parent.type, 'root');
1104 | });
1105 |
1106 | it('should set the parent.nodes as node.siblings', function() {
1107 | var node = new Node({type: 'foo'});
1108 | assert.equal(node.siblings, null);
1109 | utils.pushNode(ast, node);
1110 | assert.equal(node.siblings.length, 8);
1111 | });
1112 | });
1113 |
1114 | describe('.addType', function() {
1115 | it('should throw an error when state is not given', function() {
1116 | assert.throws(function() {
1117 | utils.addType();
1118 | });
1119 | });
1120 |
1121 | it('should throw an error when a node is not passed', function() {
1122 | assert.throws(function() {
1123 | utils.addType({});
1124 | });
1125 | });
1126 |
1127 | it('should add the type to the state.inside array', function() {
1128 | var state = {};
1129 | var node = new Node({type: 'brace'});
1130 | utils.addType(state, node);
1131 | assert(state.inside);
1132 | assert(state.inside.brace);
1133 | assert.equal(state.inside.brace.length, 1);
1134 | });
1135 |
1136 | it('should add the type based on parent.type', function() {
1137 | var state = {};
1138 | var parent = new Node({type: 'brace'});
1139 | var node = new Node({type: 'brace.open'});
1140 | utils.pushNode(parent, node);
1141 | utils.addType(state, node);
1142 | assert(state.inside);
1143 | assert(state.inside.brace);
1144 | assert.equal(state.inside.brace.length, 1);
1145 | });
1146 | });
1147 |
1148 | describe('.removeType', function() {
1149 | it('should throw an error when state is not given', function() {
1150 | assert.throws(function() {
1151 | utils.removeType();
1152 | });
1153 | });
1154 |
1155 | it('should throw an error when a node is not passed', function() {
1156 | assert.throws(function() {
1157 | utils.removeType({});
1158 | });
1159 | });
1160 |
1161 | it('should add a state.inside object', function() {
1162 | var state = {};
1163 | var node = new Node({type: 'brace'});
1164 | utils.addType(state, node);
1165 | assert(state.inside);
1166 | });
1167 |
1168 | it('should add a type array to the state.inside object', function() {
1169 | var state = {};
1170 | var node = new Node({type: 'brace'});
1171 | utils.addType(state, node);
1172 | assert(state.inside);
1173 | assert(Array.isArray(state.inside.brace));
1174 | });
1175 |
1176 | it('should add the node to the state.inside type array', function() {
1177 | var state = {};
1178 | var node = new Node({type: 'brace'});
1179 | utils.addType(state, node);
1180 | assert(state.inside);
1181 | assert(state.inside.brace);
1182 | assert.equal(state.inside.brace.length, 1);
1183 | utils.removeType(state, node);
1184 | assert.equal(state.inside.brace.length, 0);
1185 | });
1186 |
1187 | it('should use a type array if it already exists', function() {
1188 | var state = { inside: { brace: [new Node({type: 'brace.open'})] }};
1189 | var node = new Node({type: 'brace'});
1190 | utils.addType(state, node);
1191 | assert(state.inside);
1192 | assert(state.inside.brace);
1193 | assert.equal(state.inside.brace.length, 2);
1194 | utils.removeType(state, node);
1195 | assert.equal(state.inside.brace.length, 1);
1196 | });
1197 |
1198 | it('should remove the type based on parent.type', function() {
1199 | var state = { inside: { brace: [new Node({type: 'brace.open'})] }};
1200 | var parent = new Node({type: 'brace'});
1201 | var node = new Node({type: 'brace.open'});
1202 | utils.pushNode(parent, node);
1203 | utils.addType(state, node);
1204 | assert(state.inside);
1205 | assert(state.inside.brace);
1206 | assert.equal(state.inside.brace.length, 2);
1207 | utils.removeType(state, node);
1208 | assert.equal(state.inside.brace.length, 1);
1209 | });
1210 |
1211 | it('should throw an error when state.inside does not exist', function() {
1212 | var state = {};
1213 | var node = new Node({type: 'brace'});
1214 | assert.throws(function() {
1215 | utils.removeType(state, node);
1216 | });
1217 | });
1218 |
1219 | it('should just return when state.inside type does not exist', function() {
1220 | var state = {inside: {}};
1221 | var node = new Node({type: 'brace'});
1222 | utils.removeType(state, node);
1223 | });
1224 | });
1225 | });
1226 |
--------------------------------------------------------------------------------