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