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