├── .circleci └── config.yml ├── .codeclimate.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .helarc.json ├── .npmrc ├── .nycrc.json ├── .prettierrc ├── .verb.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── README.md └── _config.yml ├── package.json ├── renovate.json ├── src ├── index.js └── lib │ ├── plugins │ ├── body.js │ ├── initial.js │ ├── params.js │ └── props.js │ └── utils.js ├── test └── index.js └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | defaults: &defaults 4 | working_directory: ~/parse-function 5 | docker: 6 | - image: circleci/node:8@sha256:cf63f9a9a1eed0a978072293a4a4e35a53f0a77655c64a0ad51ca332e138c82a 7 | 8 | restore_modules_cache: &restore_modules_cache 9 | restore_cache: 10 | keys: 11 | - parse-function-{{ checksum "yarn.lock" }} 12 | # fallback to using the latest cache if no exact match is found 13 | - parse-function- 14 | 15 | 16 | jobs: 17 | install: 18 | <<: *defaults 19 | steps: 20 | - checkout 21 | - *restore_modules_cache 22 | - run: 23 | name: Installing Dependencies 24 | command: yarn install 25 | - save_cache: 26 | key: parse-function-{{ checksum "yarn.lock" }} 27 | paths: node_modules 28 | - run: 29 | name: Remove node_modules to cleanup workspace 30 | command: rm -rf node_modules 31 | 32 | test: 33 | <<: *defaults 34 | steps: 35 | - checkout 36 | - *restore_modules_cache 37 | - run: 38 | name: Running tests and checks 39 | command: yarn test 40 | - run: 41 | name: Sending test coverage to CodeCov 42 | command: bash <(curl -s https://codecov.io/bash) 43 | 44 | release: 45 | <<: *defaults 46 | steps: 47 | - checkout 48 | - *restore_modules_cache 49 | - run: 50 | name: Building dist files 51 | command: yarn build 52 | - run: 53 | name: Releasing and publishing 54 | command: yarn run release 55 | 56 | workflows: 57 | version: 2 58 | automated: 59 | jobs: 60 | - install 61 | - test: 62 | requires: 63 | - install 64 | - release: 65 | requires: 66 | - test 67 | filters: 68 | branches: 69 | only: master 70 | context: org-global 71 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | eslint: 3 | enabled: true 4 | duplication: 5 | enabled: true 6 | config: 7 | languages: 8 | - javascript 9 | checks: 10 | Similar code: 11 | enabled: false 12 | 13 | ratings: 14 | paths: 15 | - src/**/*.js 16 | 17 | exclude_paths: 18 | - src/cli.js 19 | - test/**/*.js 20 | - config/**/*.js 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Always-ignore dirs # 2 | # #################### 3 | _gh_pages 4 | node_modules 5 | jspm_packages 6 | bower_components 7 | vendor 8 | build 9 | dest 10 | dist 11 | lib-cov 12 | coverage 13 | .nyc_output 14 | nbproject 15 | cache 16 | temp 17 | tmp 18 | 19 | # npm >=5 lock file, we use Yarn! 20 | package-lock.json 21 | 22 | # Typescript v1 declaration files 23 | typings/ 24 | 25 | # Optional npm cache directory 26 | .npm 27 | 28 | # Packages # 29 | # ########## 30 | *.7z 31 | *.dmg 32 | *.iso 33 | *.jar 34 | *.rar 35 | *.tar 36 | *.zip 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # OS, Logs and databases # 42 | # ######################### 43 | logs 44 | *.log 45 | npm-debug.log* 46 | npm-debug.log* 47 | yarn-debug.log* 48 | yarn-error.log* 49 | *.dat 50 | *.sql 51 | *.sqlite 52 | *~ 53 | ~* 54 | 55 | # Runtime data 56 | pids 57 | *.pid 58 | *.seed 59 | *.pid.lock 60 | 61 | # Editors 62 | *.idea 63 | 64 | # Another files # 65 | # ############### 66 | Icon? 67 | .DS_Store* 68 | Thumbs.db 69 | ehthumbs.db 70 | Desktop.ini 71 | .directory 72 | ._* 73 | lcov.info 74 | 75 | # Runtime data 76 | pids 77 | *.pid 78 | *.seed 79 | *.pid.lock 80 | 81 | # Optional npm cache directory 82 | .npm 83 | 84 | # Optional REPL history 85 | .node_repl_history 86 | 87 | # Optional eslint cache 88 | .eslintcache 89 | 90 | # Yarn Integrity file 91 | .yarn-integrity 92 | 93 | # dotenv environment variables file 94 | .env 95 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "standard-tunnckocore" 4 | ], 5 | "rules": { 6 | "react/react-in-jsx-scope": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce Unix newlines 2 | * text eol=lf 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: tunnckoCore 2 | patreon: tunnckoCore 3 | tidelift: npm/parse-function 4 | custom: https://paypal.me/tunnckoCore/5 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Always-ignore dirs # 2 | # #################### 3 | _gh_pages 4 | node_modules 5 | jspm_packages 6 | bower_components 7 | vendor 8 | build 9 | dest 10 | dist 11 | lib-cov 12 | coverage 13 | .nyc_output 14 | nbproject 15 | cache 16 | temp 17 | tmp 18 | 19 | # npm >=5 lock file, we use Yarn! 20 | package-lock.json 21 | 22 | # Typescript v1 declaration files 23 | typings/ 24 | 25 | # Optional npm cache directory 26 | .npm 27 | 28 | # Packages # 29 | # ########## 30 | *.7z 31 | *.dmg 32 | *.iso 33 | *.jar 34 | *.rar 35 | *.tar 36 | *.zip 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # OS, Logs and databases # 42 | # ######################### 43 | logs 44 | *.log 45 | npm-debug.log* 46 | npm-debug.log* 47 | yarn-debug.log* 48 | yarn-error.log* 49 | *.dat 50 | *.sql 51 | *.sqlite 52 | *~ 53 | ~* 54 | 55 | # Runtime data 56 | pids 57 | *.pid 58 | *.seed 59 | *.pid.lock 60 | 61 | # Editors 62 | *.idea 63 | 64 | # Another files # 65 | # ############### 66 | Icon? 67 | .DS_Store* 68 | Thumbs.db 69 | ehthumbs.db 70 | Desktop.ini 71 | .directory 72 | ._* 73 | lcov.info 74 | 75 | # Runtime data 76 | pids 77 | *.pid 78 | *.seed 79 | *.pid.lock 80 | 81 | # Optional npm cache directory 82 | .npm 83 | 84 | # Optional REPL history 85 | .node_repl_history 86 | 87 | # Optional eslint cache 88 | .eslintcache 89 | 90 | # Yarn Integrity file 91 | .yarn-integrity 92 | 93 | # dotenv environment variables file 94 | .env 95 | -------------------------------------------------------------------------------- /.helarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": "tunnckocore", 3 | "tasks": { 4 | "build": [ 5 | "hela clean", 6 | "hela build:node" 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://registry.npmjs.org/ 2 | save-exact=true 3 | save=true 4 | username=tunnckocore 5 | -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "statements": 100, 3 | "functions": 100, 4 | "branches": 100, 5 | "lines": 100, 6 | "exclude": [ 7 | "dist" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "printWidth": 80, 4 | "semi": false, 5 | "useTabs": false, 6 | "singleQuote": true, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": true, 10 | "parser": "babylon" 11 | } 12 | -------------------------------------------------------------------------------- /.verb.md: -------------------------------------------------------------------------------- 1 |

2 | Parse a function 3 |

4 | 5 | # {%= name %} [![npm version][npmv-img]][npmv-url] [![github release][github-release-img]][github-release-url] [![License][license-img]][license-url] 6 | 7 | > {%= description %} 8 | 9 |
10 | 11 | {%= include('highlight') %} 12 | 13 | ## Quality Assurance :100: 14 | 15 | [![Code Style Standard][standard-img]][standard-url] 16 | [![Linux Build][circleci-img]][circleci-url] 17 | [![Code Coverage][codecov-img]][codecov-url] 18 | [![Dependencies Status][dependencies-img]][dependencies-url] 19 | [![Renovate App Status][renovate-img]][renovate-url] 20 | 21 | If you have any _how-to_ kind of questions, please read [Code of Conduct](./CODE_OF_CONDUCT.md) and **join the chat** room or [open an issue][open-issue-url]. 22 | You may also read the [Contributing Guide](./CONTRIBUTING.md). There, beside _"How to contribute?"_, we describe everything **_stated_** by the badges. 23 | 24 | [![Make A Pull Request][prs-welcome-img]][prs-welcome-url] 25 | [![Code Format Prettier][prettier-img]][prettier-url] 26 | [![Node Security Status][nodesecurity-img]][nodesecurity-url] 27 | [![Conventional Commits][ccommits-img]][ccommits-url] 28 | [![Semantically Released][new-release-img]][new-release-url] 29 | [![Renovate App Status][renovate-img]][renovate-url] 30 | 31 | Project is [semantically](https://semver.org) & automatically released on [CircleCI][codecov-url] with [new-release][] and its [New Release](https://github.com/apps/new-release) Github Bot. 32 | 33 | [![All Contributors Spec][all-contributors-img]](#contributors) 34 | [![Newsletter Subscribe][tinyletter-img]][tinyletter-url] 35 | [![Give thanks][give-donate-img]][give-donate-url] 36 | [![Share Love Tweet][share-love-img]][share-love-url] 37 | [![NPM Downloads Weekly][downloads-weekly-img]][npmv-url] 38 | [![NPM Downloads Monthly][downloads-monthly-img]][npmv-url] 39 | [![NPM Downloads Total][downloads-total-img]][npmv-url] 40 | 41 | ## Features 42 | 43 | - **Always up-to-date:** auto-publish new version when new version of dependency is out, [Renovate](https://renovateapp.com) 44 | - **Standard:** using StandardJS, Prettier, SemVer, Semantic Release and conventional commits 45 | - **Smart Plugins:** for extending the core API or the end [Result](#result), see [.use](#use) method and [Plugins Architecture](#plugins-architecture) 46 | - **Extensible:** using plugins for working directly on AST nodes, see the [Plugins Architecture](#plugins-architecture) 47 | - **ES2017 Ready:** by using `.parseExpression` method of the [babylon][] `v7.x` parser 48 | - **Customization:** allows switching the parser, through `options.parse` 49 | - **Support for:** arrow functions, default parameters, generators and async/await functions 50 | - **Stable:** battle-tested in production and against all parsers - [espree][], [acorn][], [babylon][] 51 | - **Tested:** with [450+ tests](./test.js) for _200%_ coverage 52 | 53 | ## Table of Contents 54 | 55 | 56 | ## Install 57 | 58 | This project requires [**Node.js**][nodeversion-url] **v{%= engines.node.slice(2) %}** and above. Use [**yarn**](https://yarnpkg.com) **v{%= engines.yarn.slice(2) %}** / [**npm**](https://npmjs.com) **v{%= engines.npm.slice(2) %}** or above to install it. 59 | 60 | ``` 61 | $ yarn add {%= name %} 62 | ``` 63 | 64 | ## Which version to use? 65 | 66 | There's no breaking changes between the `v2.x` version. The only breaking is `v2.1` which also is not 67 | working properly, so no use it. 68 | 69 | **Use v2.0.x** 70 | 71 | When you don't need support for `arrow functions` and `es6 default params`. This version 72 | uses a RegExp expression to work. 73 | 74 | **Use v2.2.x** 75 | 76 | Only when you need a _basic_ support for `es6 features` like arrow functions. This version 77 | uses a RegExp expression to work. 78 | 79 | **Use v2.3.x** 80 | 81 | When you want _full*_ support for `arrow functions` and `es6 default params`. Where this "full", 82 | means "almost full", because it has bugs. This version also uses (`acorn.parse`) real parser 83 | to do the parsing. 84 | 85 | **Use v3.x** 86 | 87 | When you want to use different parser instead of the default `babylon.parse`, by passing custom 88 | parse function to the `options.parse` option. **From this version we require `node >= 4`**. 89 | 90 | **Use v4.x** 91 | 92 | When you want full customization and most stable support for old and modern features. This version 93 | uses `babylon.parseExpression` for parsing and provides a [Plugins API](#plugins-architecture). 94 | See the [Features](#features) section for more info. 95 | 96 | **Use v5.x** 97 | 98 | It is basically the same as `v4`, but requires Node 6 & npm 5. Another is boilerplate stuff. 99 | 100 | **[back to top](#thetop)** 101 | 102 | ## Notes 103 | 104 | ### Throws in one specific case 105 | 106 | > _see: [issue #3](https://github.com/tunnckoCore/parse-function/issues/3) and [test/index.js#L229-L235](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L229-L235)_ 107 | 108 | It may throw in one specific case, otherwise it won't throw, so you should 109 | relay on the `result.isValid` for sure. 110 | 111 | ### Function named _"anonymous"_ 112 | 113 | > _see: [test/index.js#L319-L324](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L319-L324) and [Result](#result) section_ 114 | 115 | If you pass a function which is named _"anonymous"_ the `result.name` will be `'anonymous'`, 116 | but the `result.isAnonymous` will be `false` and `result.isNamed` will be `true`, because 117 | in fact it's a named function. 118 | 119 | ### Real anonymous function 120 | 121 | > _see: [test/index.js#L326-L331](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L326-L331) and [Result](#result) section_ 122 | 123 | Only if you pass really an anonymous function you will get `result.name` equal to `null`, 124 | `result.isAnonymous` equal to `true` and `result.isNamed` equal to `false`. 125 | 126 | **[back to top](#thetop)** 127 | 128 | ### Plugins Architecture 129 | 130 | > _see: the [.use](#use) method, [test/index.js#L305-L317](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L305-L317) and [test/index.js#L396-L414](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L396-L414)_ 131 | 132 | A more human description of the plugin mechanism. Plugins are **synchronous** - no support 133 | and no need for **async** plugins here, but notice that you can do that manually, because 134 | that exact architecture. 135 | 136 | The first function that is passed to the [.use](#use) method is used for extending the core API, 137 | for example adding a new method to the `app` instance. That function is immediately invoked. 138 | 139 | ```js 140 | const parseFunction = require('parse-function') 141 | const app = parseFunction() 142 | 143 | app.use((self) => { 144 | // self is same as `app` 145 | console.log(self.use) 146 | console.log(self.parse) 147 | console.log(self.define) 148 | 149 | self.define(self, 'foo', (bar) => bar + 1) 150 | }) 151 | 152 | console.log(app.foo(2)) // => 3 153 | ``` 154 | 155 | On the other side, if you want to access the AST of the parser, you should return a function 156 | from that plugin, which function is passed with `(node, result)` signature. 157 | 158 | This function is lazy plugin, it is called only when the [.parse](#parse) method is called. 159 | 160 | ```js 161 | const parseFunction = require('parse-function') 162 | const app = parseFunction() 163 | 164 | app.use((self) => { 165 | console.log('immediately called') 166 | 167 | return (node, result) => { 168 | console.log('called only when .parse is invoked') 169 | console.log(node) 170 | console.log(result) 171 | } 172 | }) 173 | ``` 174 | 175 | Where **1)** the `node` argument is an object - actual and real AST Node coming from the parser 176 | and **2)** the `result` is an object too - the end [Result](#result), on which 177 | you can add more properties if you want. 178 | 179 | **[back to top](#thetop)** 180 | 181 | ## API 182 | Review carefully the provided examples and the working [tests](./test/index.js). 183 | 184 | {%= apidocs('src/index.js') %} 185 | 186 | **[back to top](#thetop)** 187 | 188 | ### Result 189 | > In the result object you have `name`, `args`, `params`, `body` and few hidden properties 190 | that can be useful to determine what the function is - arrow, regular, async/await or generator. 191 | 192 | * `name` **{String|null}**: name of the passed function or `null` if anonymous 193 | * `args` **{Array}**: arguments of the function 194 | * `params` **{String}**: comma-separated list representing the `args` 195 | * `defaults` **{Object}**: key/value pairs, useful when use ES2015 default arguments 196 | * `body` **{String}**: actual body of the function, respects trailing newlines and whitespaces 197 | * `isValid` **{Boolean}**: is the given value valid or not, that's because it never throws! 198 | * `isAsync` **{Boolean}**: `true` if function is ES2015 async/await function 199 | * `isArrow` **{Boolean}**: `true` if the function is arrow function 200 | * `isNamed` **{Boolean}**: `true` if function has name, or `false` if is anonymous 201 | * `isGenerator` **{Boolean}**: `true` if the function is ES2015 generator function 202 | * `isAnonymous` **{Boolean}**: `true` if the function don't have name 203 | 204 | **[back to top](#thetop)** 205 | 206 | {% if (verb.related && verb.related.list && verb.related.list.length) { %} 207 | ## Related 208 | {%= related(verb.related.list, { words: 12 }) %} 209 | {% } %} 210 | 211 | ## Contributing 212 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue][open-issue-url]. 213 | Please read the [Contributing Guide](./CONTRIBUTING.md) and [Code of Conduct](./CODE_OF_CONDUCT.md) documents for advices. 214 | 215 | ## Author 216 | - [github/tunnckoCore](https://github.com/tunnckoCore) 217 | - [twitter/tunnckoCore](https://twitter.com/tunnckoCore) 218 | - [codementor/tunnckoCore](https://codementor.io/tunnckoCore) 219 | 220 | ## License 221 | {%= copyright({ start: licenseStart, linkify: true, prefix: 'Copyright', symbol: '©' }) %} {%= licenseStatement %} 222 | 223 | *** 224 | 225 | {%= include('footer') %} 226 | _Project automation and management with [hela][] task framework._ 227 | 228 | {%= reflinks(verb.reflinks) %} 229 | 230 | 231 | [npmv-url]: https://www.npmjs.com/package/{%= name %} 232 | [npmv-img]: https://img.shields.io/npm/v/{%= name %}.svg?label=npm%20version 233 | 234 | [github-release-url]: https://github.com/{%= repository %}/releases/latest 235 | [github-release-img]: https://img.shields.io/github/release/{%= repository %}.svg?label=github%20release 236 | 237 | [license-url]: https://github.com/{%= repository %}/blob/master/LICENSE 238 | [license-img]: https://img.shields.io/badge/license-{%= license.replace('-', '%20') %}-blue.svg 239 | 240 | 241 | 242 | [bithound-score-url]: https://www.bithound.io/github/{%= repository %} 243 | [bithound-score-img]: https://www.bithound.io/github/{%= repository %}/badges/score.svg 244 | 245 | [bithound-code-url]: https://www.bithound.io/github/{%= repository %} 246 | [bithound-code-img]: https://www.bithound.io/github/{%= repository %}/badges/code.svg 247 | 248 | [standard-url]: https://github.com/airbnb/javascript 249 | [standard-img]: https://img.shields.io/badge/code_style-airbnb-brightgreen.svg 250 | 251 | [circleci-url]: https://circleci.com/gh/{%= repository %}/tree/master 252 | [circleci-img]: https://img.shields.io/circleci/project/github/{%= repository %}/master.svg 253 | 254 | [codecov-url]: https://codecov.io/gh/{%= repository %} 255 | [codecov-img]: https://img.shields.io/codecov/c/github/{%= repository %}/master.svg 256 | 257 | [bithound-deps-url]: https://www.bithound.io/github/{%= repository %}/dependencies/npm 258 | [bithound-deps-img]: https://www.bithound.io/github/{%= repository %}/badges/dependencies.svg 259 | 260 | [dependencies-url]: https://david-dm.org/{%= repository %} 261 | [dependencies-img]: https://img.shields.io/david/{%= repository %}.svg 262 | 263 | 264 | [prs-welcome-img]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg 265 | [prs-welcome-url]: http://makeapullrequest.com 266 | 267 | [prettier-url]: https://github.com/prettier/prettier 268 | [prettier-img]: https://img.shields.io/badge/styled_with-prettier-f952a5.svg 269 | 270 | [nodesecurity-url]: https://nodesecurity.io/orgs/tunnckocore/projects/{%= nspId %}/master 271 | [nodesecurity-img]: https://nodesecurity.io/orgs/tunnckocore/projects/{%= nspId %}/badge 272 | 274 | 275 | [ccommits-url]: https://conventionalcommits.org/ 276 | [ccommits-img]: https://img.shields.io/badge/conventional_commits-1.0.0-yellow.svg 277 | 278 | [new-release-url]: https://github.com/tunnckoCore/new-release 279 | [new-release-img]: https://img.shields.io/badge/semantically-released-05C5FF.svg 280 | 281 | [nodeversion-url]: https://nodejs.org/en/download 282 | [nodeversion-img]: https://img.shields.io/node/v/{%= name %}.svg 283 | 284 | [renovate-url]: https://renovateapp.com 285 | [renovate-img]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg 286 | 287 | 288 | 289 | [all-contributors-img]: https://img.shields.io/github/contributors/{%= repository %}.svg?label=all%20contributors&colorB=ffa500 290 | 291 | [tinyletter-url]: https://tinyletter.com/tunnckoCore 292 | [tinyletter-img]: https://img.shields.io/badge/join-newsletter-9caaf8.svg 293 | 297 | [give-donate-url]: https://paypal.me/tunnckoCore/10 298 | [give-donate-img]: https://img.shields.io/badge/give-donation-f47721.svg 299 | 300 | [downloads-weekly-img]: https://img.shields.io/npm/dw/{%= name %}.svg 301 | [downloads-monthly-img]: https://img.shields.io/npm/dm/{%= name %}.svg 302 | [downloads-total-img]: https://img.shields.io/npm/dt/{%= name %}.svg 303 | 304 | 305 | 306 | [share-love-url]: https://twitter.com/intent/tweet?text={%= encodeURI(homepage) %}&via=tunnckoCore 307 | [share-love-img]: https://img.shields.io/badge/tweet-about-1da1f2.svg 308 | 309 | [open-issue-url]: https://github.com/{%= repository %}/issues/new 310 | [highlighted-link]: https://ghub.now.sh/{%= verb.related.highlight %} 311 | [author-link]: https://i.am.charlike.online 312 | 313 | [standard-url]: https://github.com/standard/standard 314 | [standard-img]: https://img.shields.io/badge/code_style-standard-brightgreen.svg -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at `open.source.charlike@gmail.com` mail address. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide :100: 2 | 3 | > _Hello stranger! :sparkles: Please, read the [Code Of Conduct](./CODE_OF_CONDUCT.md) and the full guide at 4 | [tunnckoCore/contributing](https://github.com/tunnckoCore/contributing)! 5 | > Even if you are an experienced developer or active open source maintainer, it worth look over there._ 6 | 7 | ![welcome-teal](https://cloud.githubusercontent.com/assets/194400/22215755/76cb4dbc-e194-11e6-95ed-7def95e68f14.png) 8 | 9 | > “_Every thought, **every word**, and **every action** 10 | that **adds to** the **positive** is a **contribution to peace**.
11 | Each and **every one** of us is **capable** of making such a **contribution**_.” 12 | ~ [Aung San Suu Kyi](https://en.wikipedia.org/wiki/Aung_San_Suu_Kyi) 13 | 14 | Firstly, a ***heartfelt thank you*** for making time to contribute to this project!
15 | 16 | 17 | 18 | ## Are you new to Open Source? 19 | 20 | If you’re a **new** open source contributor, the process can be intimidating. 21 | _What if you don’t know how to code?_ What if something goes wrong? **Don't worry!** 22 | 23 | **You don’t have to contribute code!** A common misconception about contributing to open source is that you need 24 | to _contribute code_. In fact, it’s often the other parts of a project that are most neglected or overlooked. 25 | You’ll do the project a _huge favor_ by offering to pitch in with these types of **contributions**! 26 | 27 | **Even if you like to write code**, other types of contributions are a great way to get involved with a project 28 | and meet other community members. Building those relationships will give you opportunities to work on other parts 29 | of the project. 30 | 31 | - **Yes:** [Step to the full guide](https://github.com/tunnckoCore/contributing) if you are _new_, **super 32 | curious** _OR_ if you're **really really new** and need more depth. 33 | - **No:** Then continue reading, if you **know** for _what is all that_ or you're **familiar** with 34 | [@tunnckoCore](https://github.com/tunnckoCore) projects. 35 | 36 | 37 | 38 | ## Opening an issue 39 | 40 | You should usually open an issue in the following situations: 41 | 42 | - Report an error you can't solve yourself 43 | - Discuss a high-level topic or idea (ex. community, vision, policies) 44 | - Propose a new feature or other project idea 45 | 46 | ### Tips for communicating on issues: 47 | 48 | - **If you see an open issue that you want to tackle,** comment on the issue to let people know you're on it. 49 | That way, people are less likely to duplicate your work. 50 | - **If an issue was opened a while ago,** it's possible that it's being addressed somewhere else, or has already 51 | been resolved, so comment to ask for confirmation before starting work. 52 | - **If you opened an issue, but figured out the answer later on your own,** comment on the issue to let people 53 | know, then close the issue. Even documenting that outcome is a contribution to the project. 54 | - **Please be patient** and _wait_ for a response from the maintainer or somebody else. Check out [_"What to do 55 | next?"_](https://github.com/tunnckoCore/contributing#what-can-i-do-while-im-waiting). 56 | 57 | ### Include Any/All _Relevant_ Information in the _Issue Description_ 58 | 59 | - Please _include_ as much ***relevant information*** as you can, such as versions and operating system. 60 | - A _good_ issue _describes_ the idea in a _**concise** and **user-focused**_ way. 61 | - ***Never*** leave the issue _description_ blank even when you are in a "rush" - the point of an issue is to 62 | _communicate_. 63 | 64 | **Why can't the description be empty?** You _wouldn't_ send a _blank email_ to hundreds of your friends (_unless 65 | you wanted to freak them out!_), right? Submitting _blank issues_ is doing **exactly** that! It sends a ["_I have 66 | **no idea** what I'm doing_"](https://www.google.com/search?q=i+have+no+idea+what+i%27m+doing&tbm=isch) 67 | **message** to your _peers_. 68 | 69 | 70 | 71 | ## Opening a pull request 72 | 73 | > _If this is your first pull request, check out [Make a Pull Request](http://makeapullrequest.com/) by 74 | [**@kentcdodds**](https://github.com/kentcdodds)._ 75 | 76 | ![get-it-done](https://cloud.githubusercontent.com/assets/194400/22265743/44a2ca72-e275-11e6-819d-2c5a1958ea11.png) 77 | 78 | You should usually open a pull request in the following situations: 79 | 80 | - Submit trivial fixes (ex. a typo, broken link, or obvious error) 81 | - Start work on a contribution that was already asked for, or that you've already discussed, in an issue 82 | 83 | A pull request doesn't have to represent finished work. It's usually better to open a pull request early on, so 84 | others can watch or give feedback on your progress. Just mark it as a "WIP" (Work in Progress) in the subject 85 | line. You can always add more commits later. 86 | 87 | ### Pro Tips to follow 88 | 89 | - **Don't worry about the style** because we use [StandardJS](https://github.com/standard/standard), 90 | [ESLint](https://github.com/eslint/eslint) and [Prettier](https://github.com/prettier/prettier). Use the `npm run 91 | lint` command. 92 | - **Don't change the markdown files**, because the README is generated (it isn't hand written) and the API 93 | section is from JSDoc code comments. Leave this step to us when, _and if_, the pull request is merged. 94 | - **Don't comment out tests**, instead use `test.skip`. They'll still be shown in the output, but are never run. 95 | 96 | ### How to submit a pull request 97 | 98 | There are just **8 easy steps** you should do. _**Please**_, follow them in _that exact_ order. 99 | 100 | 1. **[Fork the repository](https://guides.github.com/activities/forking/)** and clone it locally. 101 | 2. **[Create a branch](https://guides.github.com/introduction/flow/)** for your edits. 102 | 3. **Install dependencies** by running the `npm install` command. 103 | 4. **Test if everything is working** before you start _doing anything_ with the `npm test` command. If something 104 | is wrong, please report it first and don't continue if you can't skip the problem easily. 105 | 5. **Reference any relevant issues**, supporting documentation or information in your PR (ex. "Closes #37.") 106 | 6. **Test again or add new ones!** Run `npm test` again to _make sure_ your changes don't break existing tests. 107 | 7. **Commit your changes** by running `npm run commit`. It _will lead you_ through what the commit message 108 | _should look like_ and will run more tasks **to ensure** that code style and tests are okey. 109 | 8. **Wait response!** What to do in that time? Check out [_**"What to do 110 | next?"**_](https://github.com/tunnckoCore/contributing#what-can-i-do-while-im-waiting). 111 | 112 | :star: **You did it**! :star: _Congratulations on becoming one of the [Open Source](https://opensource.guide) 113 | contributors!_ 114 | 115 | ![thank-you-green-large](https://cloud.githubusercontent.com/assets/194400/22229077/5b0695ee-e1cd-11e6-9001-e5ff53afce36.jpg) 116 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-present Charlike Mike Reagent 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Parse a function 3 |

4 | 5 | # parse-function [![npm version][npmv-img]][npmv-url] [![github release][github-release-img]][github-release-url] [![License][license-img]][license-url] 6 | 7 | > Parse a function into an object using espree, acorn or babylon parsers. Extensible through Smart Plugins 8 | 9 |
10 | 11 | You might also be interested in [hela](https://github.com/tunnckoCore/hela#readme). 12 | 13 | ## Quality Assurance :100: 14 | 15 | [![Code Style Standard][standard-img]][standard-url] 16 | [![Linux Build][circleci-img]][circleci-url] 17 | [![Code Coverage][codecov-img]][codecov-url] 18 | [![Dependencies Status][dependencies-img]][dependencies-url] 19 | [![Renovate App Status][renovate-img]][renovate-url] 20 | 21 | If you have any _how-to_ kind of questions, please read [Code of Conduct](./CODE_OF_CONDUCT.md) and **join the chat** room or [open an issue][open-issue-url]. 22 | You may also read the [Contributing Guide](./CONTRIBUTING.md). There, beside _"How to contribute?"_, we describe everything **_stated_** by the badges. 23 | 24 | [![Make A Pull Request][prs-welcome-img]][prs-welcome-url] 25 | [![Code Format Prettier][prettier-img]][prettier-url] 26 | [![Conventional Commits][ccommits-img]][ccommits-url] 27 | [![Semantically Released][new-release-img]][new-release-url] 28 | [![Renovate App Status][renovate-img]][renovate-url] 29 | 30 | Project is [semantically](https://semver.org) & automatically released on [CircleCI][codecov-url] with [new-release][] and its [New Release](https://github.com/apps/new-release) Github Bot. 31 | 32 | [![All Contributors Spec][all-contributors-img]](#contributors) 33 | [![Newsletter Subscribe][tinyletter-img]][tinyletter-url] 34 | [![Give thanks][give-donate-img]][give-donate-url] 35 | [![Share Love Tweet][share-love-img]][share-love-url] 36 | [![NPM Downloads Weekly][downloads-weekly-img]][npmv-url] 37 | [![NPM Downloads Monthly][downloads-monthly-img]][npmv-url] 38 | [![NPM Downloads Total][downloads-total-img]][npmv-url] 39 | 40 | ## Features 41 | 42 | - **Always up-to-date:** auto-publish new version when new version of dependency is out, [Renovate](https://renovateapp.com) 43 | - **Standard:** using StandardJS, Prettier, SemVer, Semantic Release and conventional commits 44 | - **Smart Plugins:** for extending the core API or the end [Result](#result), see [.use](#use) method and [Plugins Architecture](#plugins-architecture) 45 | - **Extensible:** using plugins for working directly on AST nodes, see the [Plugins Architecture](#plugins-architecture) 46 | - **ES2017 Ready:** by using `.parseExpression` method of the [babylon][] `v7.x` parser 47 | - **Customization:** allows switching the parser, through `options.parse` 48 | - **Support for:** arrow functions, default parameters, generators and async/await functions 49 | - **Stable:** battle-tested in production and against all parsers - [espree][], [acorn][], [babylon][] 50 | - **Tested:** with [450+ tests](./test.js) for _200%_ coverage 51 | 52 | ## Table of Contents 53 | - [Install](#install) 54 | - [Which version to use?](#which-version-to-use) 55 | - [Notes](#notes) 56 | * [Throws in one specific case](#throws-in-one-specific-case) 57 | * [Function named _"anonymous"_](#function-named-_anonymous_) 58 | * [Real anonymous function](#real-anonymous-function) 59 | * [Plugins Architecture](#plugins-architecture) 60 | - [API](#api) 61 | * [parseFunction](#parsefunction) 62 | * [.parse](#parse) 63 | * [.use](#use) 64 | * [.define](#define) 65 | * [Result](#result) 66 | - [Related](#related) 67 | - [Contributing](#contributing) 68 | - [Author](#author) 69 | - [License](#license) 70 | 71 | _(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_ 72 | 73 | ## Install 74 | 75 | This project requires [**Node.js**][nodeversion-url] **v6** and above. Use [**yarn**](https://yarnpkg.com) **v1** / [**npm**](https://npmjs.com) **v5** or above to install it. 76 | 77 | ``` 78 | $ yarn add parse-function 79 | ``` 80 | 81 | ## Which version to use? 82 | 83 | There's no breaking changes between the `v2.x` version. The only breaking is `v2.1` which also is not 84 | working properly, so no use it. 85 | 86 | **Use v2.0.x** 87 | 88 | When you don't need support for `arrow functions` and `es6 default params`. This version 89 | uses a RegExp expression to work. 90 | 91 | **Use v2.2.x** 92 | 93 | Only when you need a _basic_ support for `es6 features` like arrow functions. This version 94 | uses a RegExp expression to work. 95 | 96 | **Use v2.3.x** 97 | 98 | When you want _full*_ support for `arrow functions` and `es6 default params`. Where this "full", 99 | means "almost full", because it has bugs. This version also uses (`acorn.parse`) real parser 100 | to do the parsing. 101 | 102 | **Use v3.x** 103 | 104 | When you want to use different parser instead of the default `babylon.parse`, by passing custom 105 | parse function to the `options.parse` option. **From this version we require `node >= 4`**. 106 | 107 | **Use v4.x** 108 | 109 | When you want full customization and most stable support for old and modern features. This version 110 | uses `babylon.parseExpression` for parsing and provides a [Plugins API](#plugins-architecture). 111 | See the [Features](#features) section for more info. 112 | 113 | **Use v5.x** 114 | 115 | It is basically the same as `v4`, but requires Node 6 & npm 5. Another is boilerplate stuff. 116 | 117 | **[back to top](#thetop)** 118 | 119 | ## Notes 120 | 121 | ### Throws in one specific case 122 | 123 | > _see: [issue #3](https://github.com/tunnckoCore/parse-function/issues/3) and [test/index.js#L229-L235](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L229-L235)_ 124 | 125 | It may throw in one specific case, otherwise it won't throw, so you should 126 | relay on the `result.isValid` for sure. 127 | 128 | ### Function named _"anonymous"_ 129 | 130 | > _see: [test/index.js#L319-L324](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L319-L324) and [Result](#result) section_ 131 | 132 | If you pass a function which is named _"anonymous"_ the `result.name` will be `'anonymous'`, 133 | but the `result.isAnonymous` will be `false` and `result.isNamed` will be `true`, because 134 | in fact it's a named function. 135 | 136 | ### Real anonymous function 137 | 138 | > _see: [test/index.js#L326-L331](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L326-L331) and [Result](#result) section_ 139 | 140 | Only if you pass really an anonymous function you will get `result.name` equal to `null`, 141 | `result.isAnonymous` equal to `true` and `result.isNamed` equal to `false`. 142 | 143 | **[back to top](#thetop)** 144 | 145 | ### Plugins Architecture 146 | 147 | > _see: the [.use](#use) method, [test/index.js#L305-L317](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L305-L317) and [test/index.js#L396-L414](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L396-L414)_ 148 | 149 | A more human description of the plugin mechanism. Plugins are **synchronous** - no support 150 | and no need for **async** plugins here, but notice that you can do that manually, because 151 | that exact architecture. 152 | 153 | The first function that is passed to the [.use](#use) method is used for extending the core API, 154 | for example adding a new method to the `app` instance. That function is immediately invoked. 155 | 156 | ```js 157 | const parseFunction = require('parse-function') 158 | const app = parseFunction() 159 | 160 | app.use((self) => { 161 | // self is same as `app` 162 | console.log(self.use) 163 | console.log(self.parse) 164 | console.log(self.define) 165 | 166 | self.define(self, 'foo', (bar) => bar + 1) 167 | }) 168 | 169 | console.log(app.foo(2)) // => 3 170 | ``` 171 | 172 | On the other side, if you want to access the AST of the parser, you should return a function 173 | from that plugin, which function is passed with `(node, result)` signature. 174 | 175 | This function is lazy plugin, it is called only when the [.parse](#parse) method is called. 176 | 177 | ```js 178 | const parseFunction = require('parse-function') 179 | const app = parseFunction() 180 | 181 | app.use((self) => { 182 | console.log('immediately called') 183 | 184 | return (node, result) => { 185 | console.log('called only when .parse is invoked') 186 | console.log(node) 187 | console.log(result) 188 | } 189 | }) 190 | ``` 191 | 192 | Where **1)** the `node` argument is an object - actual and real AST Node coming from the parser 193 | and **2)** the `result` is an object too - the end [Result](#result), on which 194 | you can add more properties if you want. 195 | 196 | **[back to top](#thetop)** 197 | 198 | ## API 199 | Review carefully the provided examples and the working [tests](./test/index.js). 200 | 201 | ### [parseFunction](src/index.js#L60) 202 | > Initializes with optional `opts` object which is passed directly to the desired parser and returns an object with `.use` and `.parse` methods. The default parse which is used is [babylon][]'s `.parseExpression` method from `v7`. 203 | 204 | **Params** 205 | 206 | * `opts` **{Object}**: optional, merged with options passed to `.parse` method 207 | * `returns` **{Object}** `app`: object with `.use` and `.parse` methods 208 | 209 | **Example** 210 | 211 | ```js 212 | const parseFunction = require('parse-function') 213 | 214 | const app = parseFunction({ 215 | ecmaVersion: 2017 216 | }) 217 | 218 | const fixtureFn = (a, b, c) => { 219 | a = b + c 220 | return a + 2 221 | } 222 | 223 | const result = app.parse(fixtureFn) 224 | console.log(result) 225 | 226 | // see more 227 | console.log(result.name) // => null 228 | console.log(result.isNamed) // => false 229 | console.log(result.isArrow) // => true 230 | console.log(result.isAnonymous) // => true 231 | 232 | // array of names of the arguments 233 | console.log(result.args) // => ['a', 'b', 'c'] 234 | 235 | // comma-separated names of the arguments 236 | console.log(result.params) // => 'a, b, c' 237 | ``` 238 | 239 | ### [.parse](src/index.js#L101) 240 | > Parse a given `code` and returns a `result` object with useful properties - such as `name`, `body` and `args`. By default it uses Babylon parser, but you can switch it by passing `options.parse` - for example `options.parse: acorn.parse`. In the below example will show how to use `acorn` parser, instead of the default one. 241 | 242 | **Params** 243 | 244 | * `code` **{Function|String}**: any kind of function or string to be parsed 245 | * `options` **{Object}**: directly passed to the parser - babylon, acorn, espree 246 | * `options.parse` **{Function}**: by default `babylon.parseExpression`, all `options` are passed as second argument to that provided function 247 | * `returns` **{Object}** `result`: see [result section](#result) for more info 248 | 249 | **Example** 250 | 251 | ```js 252 | const acorn = require('acorn') 253 | const parseFn = require('parse-function') 254 | const app = parseFn() 255 | 256 | const fn = function foo (bar, baz) { return bar * baz } 257 | const result = app.parse(fn, { 258 | parse: acorn.parse, 259 | ecmaVersion: 2017 260 | }) 261 | 262 | console.log(result.name) // => 'foo' 263 | console.log(result.args) // => ['bar', 'baz'] 264 | console.log(result.body) // => ' return bar * baz ' 265 | console.log(result.isNamed) // => true 266 | console.log(result.isArrow) // => false 267 | console.log(result.isAnonymous) // => false 268 | console.log(result.isGenerator) // => false 269 | ``` 270 | 271 | ### [.use](src/index.js#L173) 272 | > Add a plugin `fn` function for extending the API or working on the AST nodes. The `fn` is immediately invoked and passed with `app` argument which is instance of `parseFunction()` call. That `fn` may return another function that accepts `(node, result)` signature, where `node` is an AST node and `result` is an object which will be returned [result](#result) from the `.parse` method. This retuned function is called on each node only when `.parse` method is called. 273 | 274 | _See [Plugins Architecture](#plugins-architecture) section._ 275 | 276 | **Params** 277 | 278 | * `fn` **{Function}**: plugin to be called 279 | * `returns` **{Object}** `app`: instance for chaining 280 | 281 | **Example** 282 | 283 | ```js 284 | // plugin extending the `app` 285 | app.use((app) => { 286 | app.define(app, 'hello', (place) => `Hello ${place}!`) 287 | }) 288 | 289 | const hi = app.hello('World') 290 | console.log(hi) // => 'Hello World!' 291 | 292 | // or plugin that works on AST nodes 293 | app.use((app) => (node, result) => { 294 | if (node.type === 'ArrowFunctionExpression') { 295 | result.thatIsArrow = true 296 | } 297 | return result 298 | }) 299 | 300 | const result = app.parse((a, b) => (a + b + 123)) 301 | console.log(result.name) // => null 302 | console.log(result.isArrow) // => true 303 | console.log(result.thatIsArrow) // => true 304 | 305 | const result = app.parse(function foo () { return 123 }) 306 | console.log(result.name) // => 'foo' 307 | console.log(result.isArrow) // => false 308 | console.log(result.thatIsArrow) // => undefined 309 | ``` 310 | 311 | ### [.define](src/index.js#L234) 312 | > Define a non-enumerable property on an object. Just a convenience mirror of the [define-property][] library, so check out its docs. Useful to be used in plugins. 313 | 314 | **Params** 315 | 316 | * `obj` **{Object}**: the object on which to define the property 317 | * `prop` **{String}**: the name of the property to be defined or modified 318 | * `val` **{Any}**: the descriptor for the property being defined or modified 319 | * `returns` **{Object}** `obj`: the passed object, but modified 320 | 321 | **Example** 322 | 323 | ```js 324 | const parseFunction = require('parse-function') 325 | const app = parseFunction() 326 | 327 | // use it like `define-property` lib 328 | const obj = {} 329 | app.define(obj, 'hi', 'world') 330 | console.log(obj) // => { hi: 'world' } 331 | 332 | // or define a custom plugin that adds `.foo` property 333 | // to the end result, returned from `app.parse` 334 | app.use((app) => { 335 | return (node, result) => { 336 | // this function is called 337 | // only when `.parse` is called 338 | 339 | app.define(result, 'foo', 123) 340 | 341 | return result 342 | } 343 | }) 344 | 345 | // fixture function to be parsed 346 | const asyncFn = async (qux) => { 347 | const bar = await Promise.resolve(qux) 348 | return bar 349 | } 350 | 351 | const result = app.parse(asyncFn) 352 | 353 | console.log(result.name) // => null 354 | console.log(result.foo) // => 123 355 | console.log(result.args) // => ['qux'] 356 | 357 | console.log(result.isAsync) // => true 358 | console.log(result.isArrow) // => true 359 | console.log(result.isNamed) // => false 360 | console.log(result.isAnonymous) // => true 361 | ``` 362 | 363 | **[back to top](#thetop)** 364 | 365 | ### Result 366 | > In the result object you have `name`, `args`, `params`, `body` and few hidden properties 367 | that can be useful to determine what the function is - arrow, regular, async/await or generator. 368 | 369 | * `name` **{String|null}**: name of the passed function or `null` if anonymous 370 | * `args` **{Array}**: arguments of the function 371 | * `params` **{String}**: comma-separated list representing the `args` 372 | * `defaults` **{Object}**: key/value pairs, useful when use ES2015 default arguments 373 | * `body` **{String}**: actual body of the function, respects trailing newlines and whitespaces 374 | * `isValid` **{Boolean}**: is the given value valid or not, that's because it never throws! 375 | * `isAsync` **{Boolean}**: `true` if function is ES2015 async/await function 376 | * `isArrow` **{Boolean}**: `true` if the function is arrow function 377 | * `isNamed` **{Boolean}**: `true` if function has name, or `false` if is anonymous 378 | * `isGenerator` **{Boolean}**: `true` if the function is ES2015 generator function 379 | * `isAnonymous` **{Boolean}**: `true` if the function don't have name 380 | 381 | **[back to top](#thetop)** 382 | 383 | ## Related 384 | - [acorn](https://www.npmjs.com/package/acorn): ECMAScript parser | [homepage](https://github.com/acornjs/acorn "ECMAScript parser") 385 | - [babylon](https://www.npmjs.com/package/babylon): A JavaScript parser | [homepage](https://babeljs.io/ "A JavaScript parser") 386 | - [charlike-cli](https://www.npmjs.com/package/charlike-cli): Command line interface for the [charlike][] project scaffolder. | [homepage](https://github.com/tunnckoCore/charlike-cli#readme "Command line interface for the [charlike][] project scaffolder.") 387 | - [espree](https://www.npmjs.com/package/espree): An Esprima-compatible JavaScript parser built on Acorn | [homepage](https://github.com/eslint/espree "An Esprima-compatible JavaScript parser built on Acorn") 388 | - [hela](https://www.npmjs.com/package/hela): Powerful & flexible task runner framework in 80 lines, based on [execa… [more](https://github.com/tunnckoCore/hela#readme) | [homepage](https://github.com/tunnckoCore/hela#readme "Powerful & flexible task runner framework in 80 lines, based on [execa][]. Supports shareable configs, a la ESLint") 389 | - [parse-semver](https://www.npmjs.com/package/parse-semver): Parse, normalize and validate given semver shorthand (e.g. gulp@v3.8.10) to object. | [homepage](https://github.com/tunnckocore/parse-semver#readme "Parse, normalize and validate given semver shorthand (e.g. gulp@v3.8.10) to object.") 390 | 391 | ## Contributing 392 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue][open-issue-url]. 393 | Please read the [Contributing Guide](./CONTRIBUTING.md) and [Code of Conduct](./CODE_OF_CONDUCT.md) documents for advices. 394 | 395 | ## Author 396 | - [github/tunnckoCore](https://github.com/tunnckoCore) 397 | - [twitter/tunnckoCore](https://twitter.com/tunnckoCore) 398 | - [codementor/tunnckoCore](https://codementor.io/tunnckoCore) 399 | 400 | ## License 401 | Copyright © 2016, 2018, [Charlike Mike Reagent](https://i.am.charlike.online). Released under the [MIT License](LICENSE). 402 | 403 | *** 404 | 405 | _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on March 05, 2018._ 406 | _Project automation and management with [hela][] task framework._ 407 | 408 | [acorn]: https://github.com/acornjs/acorn 409 | [babylon]: https://babeljs.io/ 410 | [charlike-cli]: https://github.com/tunnckoCore/charlike-cli 411 | [charlike]: https://github.com/tunnckoCore/charlike 412 | [define-property]: https://github.com/jonschlinkert/define-property 413 | [espree]: https://github.com/eslint/espree 414 | [execa]: https://github.com/sindresorhus/execa 415 | [function-arguments]: https://github.com/tunnckocore/function-arguments 416 | [hela]: https://github.com/tunnckoCore/hela 417 | [new-release]: https://github.com/tunnckoCore/new-release 418 | 419 | 420 | [npmv-url]: https://www.npmjs.com/package/parse-function 421 | [npmv-img]: https://img.shields.io/npm/v/parse-function.svg?label=npm%20version 422 | 423 | [github-release-url]: https://github.com/tunnckoCore/parse-function/releases/latest 424 | [github-release-img]: https://img.shields.io/github/release/tunnckoCore/parse-function.svg?label=github%20release 425 | 426 | [license-url]: https://github.com/tunnckoCore/parse-function/blob/master/LICENSE 427 | [license-img]: https://img.shields.io/badge/license-MIT-blue.svg 428 | 429 | 430 | 431 | [bithound-score-url]: https://www.bithound.io/github/tunnckoCore/parse-function 432 | [bithound-score-img]: https://www.bithound.io/github/tunnckoCore/parse-function/badges/score.svg 433 | 434 | [bithound-code-url]: https://www.bithound.io/github/tunnckoCore/parse-function 435 | [bithound-code-img]: https://www.bithound.io/github/tunnckoCore/parse-function/badges/code.svg 436 | 437 | [standard-url]: https://github.com/airbnb/javascript 438 | [standard-img]: https://img.shields.io/badge/code_style-airbnb-brightgreen.svg 439 | 440 | [circleci-url]: https://circleci.com/gh/tunnckoCoreLabs/parse-function/tree/master 441 | [circleci-img]: https://img.shields.io/circleci/project/github/tunnckoCoreLabs/parse-function/master.svg 442 | 443 | [codecov-url]: https://codecov.io/gh/tunnckoCoreLabs/parse-function 444 | [codecov-img]: https://img.shields.io/codecov/c/github/tunnckoCoreLabs/parse-function/master.svg 445 | 446 | [bithound-deps-url]: https://www.bithound.io/github/tunnckoCore/parse-function/dependencies/npm 447 | [bithound-deps-img]: https://www.bithound.io/github/tunnckoCore/parse-function/badges/dependencies.svg 448 | 449 | [dependencies-url]: https://david-dm.org/tunnckoCore/parse-function 450 | [dependencies-img]: https://img.shields.io/david/tunnckoCore/parse-function.svg 451 | 452 | 453 | [prs-welcome-img]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg 454 | [prs-welcome-url]: http://makeapullrequest.com 455 | 456 | [prettier-url]: https://github.com/prettier/prettier 457 | [prettier-img]: https://img.shields.io/badge/styled_with-prettier-f952a5.svg 458 | 459 | [nodesecurity-url]: https://nodesecurity.io/orgs/tunnckocore/projects/42a5e14a-70da-49ee-86e7-d1f39ed08603/master 460 | [nodesecurity-img]: https://nodesecurity.io/orgs/tunnckocore/projects/42a5e14a-70da-49ee-86e7-d1f39ed08603/badge 461 | 463 | 464 | [ccommits-url]: https://conventionalcommits.org/ 465 | [ccommits-img]: https://img.shields.io/badge/conventional_commits-1.0.0-yellow.svg 466 | 467 | [new-release-url]: https://github.com/tunnckoCore/new-release 468 | [new-release-img]: https://img.shields.io/badge/semantically-released-05C5FF.svg 469 | 470 | [nodeversion-url]: https://nodejs.org/en/download 471 | [nodeversion-img]: https://img.shields.io/node/v/parse-function.svg 472 | 473 | [renovate-url]: https://renovateapp.com 474 | [renovate-img]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg 475 | 476 | 477 | [all-contributors-img]: https://img.shields.io/github/contributors/tunnckoCore/parse-function.svg?label=all%20contributors&colorB=ffa500 478 | 479 | [tinyletter-url]: https://tinyletter.com/tunnckoCore 480 | [tinyletter-img]: https://img.shields.io/badge/join-newsletter-9caaf8.svg 481 | 485 | [give-donate-url]: https://paypal.me/tunnckoCore/10 486 | [give-donate-img]: https://img.shields.io/badge/give-donation-f47721.svg 487 | 488 | [downloads-weekly-img]: https://img.shields.io/npm/dw/parse-function.svg 489 | [downloads-monthly-img]: https://img.shields.io/npm/dm/parse-function.svg 490 | [downloads-total-img]: https://img.shields.io/npm/dt/parse-function.svg 491 | 492 | 493 | [share-love-url]: https://twitter.com/intent/tweet?text=https://github.com/tunnckoCore/parse-function&via=tunnckoCore 494 | [share-love-img]: https://img.shields.io/badge/tweet-about-1da1f2.svg 495 | 496 | [open-issue-url]: https://github.com/tunnckoCore/parse-function/issues/new 497 | [highlighted-link]: https://ghub.now.sh/hela 498 | [author-link]: https://i.am.charlike.online 499 | 500 | [standard-url]: https://github.com/standard/standard 501 | [standard-img]: https://img.shields.io/badge/code_style-standard-brightgreen.svg 502 | 503 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |

2 | Parse 
  3 | a function 4 |

5 | 6 | # parse-function [![npm version][npmv-img]][npmv-url] [![github release][github-release-img]][github-release-url] 7 | [![mit License][license-img]][license-url] [![NPM Downloads Weekly][downloads-weekly-img]][downloads-weekly-url] [![NPM 8 | Downloads Total][downloads-total-img]][downloads-total-url] 9 | 10 | > Parse a function into an object using espree, acorn or babylon parsers. Extensible through Smart Plugins 11 | 12 |
13 | 14 | You might also be interested in [hela](https://github.com/tunnckoCore/hela#readme). 15 | 16 | ## Quality Assurance :100: 17 | 18 | [![Code Climate][codeclimate-img]][codeclimate-url] 19 | [![Code Style Standard][standard-img]][standard-url] 20 | [![Linux Build][travis-img]][travis-url] 21 | [![Code Coverage][codecov-img]][codecov-url] 22 | [![Dependencies Status][dependencies-img]][dependencies-url] 23 | [![Renovate App Status][renovate-img]][renovate-url] 24 | 25 | If you have any _how-to_ kind of questions, please read [Code of Conduct](./CODE_OF_CONDUCT.md) and **join the chat** 26 | room or [open an issue][open-issue-url]. 27 | You may also read the [Contributing Guide](./CONTRIBUTING.md). There, beside _"How to contribute?"_, we describe 28 | everything **_stated_** by the badges. 29 | 30 | [![tunnckoCore support][gitterchat-img]][gitterchat-url] 31 | [![Code Format Prettier][prettier-img]][prettier-url] 32 | [![node security status][nodesecurity-img]][nodesecurity-url] 33 | [![conventional Commits][ccommits-img]][ccommits-url] 34 | [![semantic release][semantic-release-img]][semantic-release-url] 35 | [![Node Version Required][nodeversion-img]][nodeversion-url] 36 | 37 | ## Features 38 | 39 | - **Always up-to-date:** auto-publish new version when new version of dependency is out, 40 | [Renovate](https://renovateapp.com) 41 | - **Standard:** using StandardJS, Prettier, SemVer, Semantic Release and conventional commits 42 | - **Smart Plugins:** for extending the core API or the end [Result](#result), see [.use](#use) method and [Plugins 43 | Architecture](#plugins-architecture) 44 | - **Extensible:** using plugins for working directly on AST nodes, see the [Plugins 45 | Architecture](#plugins-architecture) 46 | - **ES2017 Ready:** by using `.parseExpression` method of the [babylon][] `v7.x` parser 47 | - **Customization:** allows switching the parser, through `options.parse` 48 | - **Support for:** arrow functions, default parameters, generators and async/await functions 49 | - **Stable:** battle-tested in production and against all parsers - [espree][], [acorn][], [babylon][] 50 | - **Tested:** with [275+ tests](./test.js) for _200%_ coverage 51 | 52 | ## Table of Contents 53 | - [Install](#install) 54 | - [Which version to use?](#which-version-to-use) 55 | - [Notes](#notes) 56 | * [Throws in one specific case](#throws-in-one-specific-case) 57 | * [Function named _"anonymous"_](#function-named-_anonymous_) 58 | * [Real anonymous function](#real-anonymous-function) 59 | * [Plugins Architecture](#plugins-architecture) 60 | - [API](#api) 61 | * [parseFunction](#parsefunction) 62 | * [.parse](#parse) 63 | * [.use](#use) 64 | * [.define](#define) 65 | * [Result](#result) 66 | - [Related](#related) 67 | - [Contributing](#contributing) 68 | - [Author](#author) 69 | - [License](#license) 70 | 71 | _(TOC generated by [verb](https://github.com/verbose/verb) using 72 | [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_ 73 | 74 | ## Install 75 | 76 | This project requires [**Node.js**][nodeversion-url] **v6** and above. Use [**yarn**](https://yarnpkg.com) **v1** / 77 | [**npm**](https://npmjs.com) **v5** or above to install it. 78 | 79 | ``` 80 | $ yarn add parse-function 81 | ``` 82 | 83 | ## Which version to use? 84 | 85 | There's no breaking changes between the `v2.x` version. The only breaking is `v2.1` which also is not 86 | working properly, so no use it. 87 | 88 | **Use v2.0.x** 89 | 90 | When you don't need support for `arrow functions` and `es6 default params`. This version 91 | uses a RegExp expression to work. 92 | 93 | **Use v2.2.x** 94 | 95 | Only when you need a _basic_ support for `es6 features` like arrow functions. This version 96 | uses a RegExp expression to work. 97 | 98 | **Use v2.3.x** 99 | 100 | When you want _full*_ support for `arrow functions` and `es6 default params`. Where this "full", 101 | means "almost full", because it has bugs. This version also uses (`acorn.parse`) real parser 102 | to do the parsing. 103 | 104 | **Use v3.x** 105 | 106 | When you want to use different parser instead of the default `babylon.parse`, by passing custom 107 | parse function to the `options.parse` option. **From this version we require `node >= 4`**. 108 | 109 | **Use v4.x** 110 | 111 | When you want full customization and most stable support for old and modern features. This version 112 | uses `babylon.parseExpression` for parsing and provides a [Plugins API](#plugins-architecture). 113 | See the [Features](#features) section for more info. 114 | 115 | **Use v5.x** 116 | 117 | It is basically the same as `v4`, but requires Node 6 & npm 5. Another is boilerplate stuff. 118 | 119 | **[back to top](#thetop)** 120 | 121 | ## Notes 122 | 123 | ### Throws in one specific case 124 | 125 | > _see: [issue #3](https://github.com/tunnckoCore/parse-function/issues/3) and 126 | [test/index.js#L229-L235](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L229-L235)_ 127 | 128 | It may throw in one specific case, otherwise it won't throw, so you should 129 | relay on the `result.isValid` for sure. 130 | 131 | ### Function named _"anonymous"_ 132 | 133 | > _see: [test/index.js#L319-L324](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L319-L324) 134 | and [Result](#result) section_ 135 | 136 | If you pass a function which is named _"anonymous"_ the `result.name` will be `'anonymous'`, 137 | but the `result.isAnonymous` will be `false` and `result.isNamed` will be `true`, because 138 | in fact it's a named function. 139 | 140 | ### Real anonymous function 141 | 142 | > _see: [test/index.js#L326-L331](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L326-L331) 143 | and [Result](#result) section_ 144 | 145 | Only if you pass really an anonymous function you will get `result.name` equal to `null`, 146 | `result.isAnonymous` equal to `true` and `result.isNamed` equal to `false`. 147 | 148 | **[back to top](#thetop)** 149 | 150 | ### Plugins Architecture 151 | 152 | > _see: the [.use](#use) method, 153 | [test/index.js#L305-L317](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L305-L317) and 154 | [test/index.js#L396-L414](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L396-L414)_ 155 | 156 | A more human description of the plugin mechanism. Plugins are **synchronous** - no support 157 | and no need for **async** plugins here, but notice that you can do that manually, because 158 | that exact architecture. 159 | 160 | The first function that is passed to the [.use](#use) method is used for extending the core API, 161 | for example adding a new method to the `app` instance. That function is immediately invoked. 162 | 163 | ```js 164 | const parseFunction = require('parse-function') 165 | const app = parseFunction() 166 | 167 | app.use((self) => { 168 | // self is same as `app` 169 | console.log(self.use) 170 | console.log(self.parse) 171 | console.log(self.define) 172 | 173 | self.define(self, 'foo', (bar) => bar + 1) 174 | }) 175 | 176 | console.log(app.foo(2)) // => 3 177 | ``` 178 | 179 | On the other side, if you want to access the AST of the parser, you should return a function 180 | from that plugin, which function is passed with `(node, result)` signature. 181 | 182 | This function is lazy plugin, it is called only when the [.parse](#parse) method is called. 183 | 184 | ```js 185 | const parseFunction = require('parse-function') 186 | const app = parseFunction() 187 | 188 | app.use((self) => { 189 | console.log('immediately called') 190 | 191 | return (node, result) => { 192 | console.log('called only when .parse is invoked') 193 | console.log(node) 194 | console.log(result) 195 | } 196 | }) 197 | ``` 198 | 199 | Where **1)** the `node` argument is an object - actual and real AST Node coming from the parser 200 | and **2)** the `result` is an object too - the end [Result](#result), on which 201 | you can add more properties if you want. 202 | 203 | **[back to top](#thetop)** 204 | 205 | ## API 206 | Review carefully the provided examples and the working [tests](./test/index.js). 207 | 208 | ### [parseFunction](src/index.js#L59) 209 | 210 | > Initializes with optional `opts` object which is passed directly 211 | to the desired parser and returns an object 212 | with `.use` and `.parse` methods. The default parse which 213 | is used is [babylon][]'s `.parseExpression` method from `v7`. 214 | 215 | **Params** 216 | 217 | * `opts` **{Object}**: optional, merged with options passed to `.parse` method 218 | * `returns` **{Object}**: `app` object with `.use` and `.parse` methods 219 | 220 | **Example** 221 | 222 | ```jsx 223 | const parseFunction = require('parse-function') 224 | 225 | const app = parseFunction({ 226 | ecmaVersion: 2017 227 | }) 228 | 229 | const fixtureFn = (a, b, c) => { 230 | a = b + c 231 | return a + 2 232 | } 233 | 234 | const result = app.parse(fixtureFn) 235 | console.log(result) 236 | 237 | // see more 238 | console.log(result.name) // => null 239 | console.log(result.isNamed) // => false 240 | console.log(result.isArrow) // => true 241 | console.log(result.isAnonymous) // => true 242 | 243 | // array of names of the arguments 244 | console.log(result.args) // => ['a', 'b', 'c'] 245 | 246 | // comma-separated names of the arguments 247 | console.log(result.params) // => 'a, b, c' 248 | ``` 249 | 250 | ### [.parse](src/index.js#L98) 251 | 252 | > Parse a given `code` and returns a `result` object 253 | with useful properties - such as `name`, `body` and `args`. 254 | By default it uses Babylon parser, but you can switch it by 255 | passing `options.parse` - for example `options.parse: acorn.parse`. 256 | In the below example will show how to use `acorn` parser, instead 257 | of the default one. 258 | 259 | **Params** 260 | 261 | * `code` **{Function|String}**: any kind of function or string to be parsed 262 | * `options` **{Object}**: directly passed to the parser - babylon, acorn, espree 263 | * `options.parse` **{Function}**: by default `babylon.parseExpression`, all `options` are passed as second argument to 264 | that provided function 265 | * `returns` **{Object}**: `result` see [result section](#result) for more info 266 | 267 | **Example** 268 | 269 | ```jsx 270 | const acorn = require('acorn') 271 | const parseFn = require('parse-function') 272 | const app = parseFn() 273 | 274 | const fn = function foo (bar, baz) { return bar * baz } 275 | const result = app.parse(fn, { 276 | parse: acorn.parse, 277 | ecmaVersion: 2017 278 | }) 279 | 280 | console.log(result.name) // => 'foo' 281 | console.log(result.args) // => ['bar', 'baz'] 282 | console.log(result.body) // => ' return bar * baz ' 283 | console.log(result.isNamed) // => true 284 | console.log(result.isArrow) // => false 285 | console.log(result.isAnonymous) // => false 286 | console.log(result.isGenerator) // => false 287 | ``` 288 | 289 | ### [.use](src/index.js#L168) 290 | > Add a plugin `fn` function for extending the API or working on the AST nodes. The `fn` is immediately invoked and 291 | passed with `app` argument which is instance of `parseFunction()` call. That `fn` may return another function that 292 | accepts `(node, result)` signature, where `node` is an AST node and `result` is an object which will be returned 293 | [result](#result) from the `.parse` method. This retuned function is called on each node only when `.parse` method is 294 | called. 295 | 296 | _See [Plugins Architecture](#plugins-architecture) section._ 297 | 298 | **Params** 299 | 300 | * `fn` **{Function}**: plugin to be called 301 | * `returns` **{Object}**: `app` instance for chaining 302 | 303 | **Example** 304 | 305 | ```jsx 306 | // plugin extending the `app` 307 | app.use((app) => { 308 | app.define(app, 'hello', (place) => `Hello ${place}!`) 309 | }) 310 | 311 | const hi = app.hello('World') 312 | console.log(hi) // => 'Hello World!' 313 | 314 | // or plugin that works on AST nodes 315 | app.use((app) => (node, result) => { 316 | if (node.type === 'ArrowFunctionExpression') { 317 | result.thatIsArrow = true 318 | } 319 | return result 320 | }) 321 | 322 | const result = app.parse((a, b) => (a + b + 123)) 323 | console.log(result.name) // => null 324 | console.log(result.isArrow) // => true 325 | console.log(result.thatIsArrow) // => true 326 | 327 | const result = app.parse(function foo () { return 123 }) 328 | console.log(result.name) // => 'foo' 329 | console.log(result.isArrow) // => false 330 | console.log(result.thatIsArrow) // => undefined 331 | ``` 332 | 333 | ### [.define](src/index.js#L227) 334 | 335 | > Define a non-enumerable property on an object. Just 336 | a convenience mirror of the [define-property][] library, 337 | so check out its docs. Useful to be used in plugins. 338 | 339 | **Params** 340 | 341 | * `obj` **{Object}**: the object on which to define the property 342 | * `prop` **{String}**: the name of the property to be defined or modified 343 | * `val` **{Any}**: the descriptor for the property being defined or modified 344 | * `returns` **{Object}**: `obj` the passed object, but modified 345 | 346 | **Example** 347 | 348 | ```jsx 349 | const parseFunction = require('parse-function') 350 | const app = parseFunction() 351 | 352 | // use it like `define-property` lib 353 | const obj = {} 354 | app.define(obj, 'hi', 'world') 355 | console.log(obj) // => { hi: 'world' } 356 | 357 | // or define a custom plugin that adds `.foo` property 358 | // to the end result, returned from `app.parse` 359 | app.use((app) => { 360 | return (node, result) => { 361 | // this function is called 362 | // only when `.parse` is called 363 | 364 | app.define(result, 'foo', 123) 365 | 366 | return result 367 | } 368 | }) 369 | 370 | // fixture function to be parsed 371 | const asyncFn = async (qux) => { 372 | const bar = await Promise.resolve(qux) 373 | return bar 374 | } 375 | 376 | const result = app.parse(asyncFn) 377 | 378 | console.log(result.name) // => null 379 | console.log(result.foo) // => 123 380 | console.log(result.args) // => ['qux'] 381 | 382 | console.log(result.isAsync) // => true 383 | console.log(result.isArrow) // => true 384 | console.log(result.isNamed) // => false 385 | console.log(result.isAnonymous) // => true 386 | ``` 387 | 388 | **[back to top](#thetop)** 389 | 390 | ### Result 391 | > In the result object you have `name`, `args`, `params`, `body` and few hidden properties 392 | that can be useful to determine what the function is - arrow, regular, async/await or generator. 393 | 394 | * `name` **{String|null}**: name of the passed function or `null` if anonymous 395 | * `args` **{Array}**: arguments of the function 396 | * `params` **{String}**: comma-separated list representing the `args` 397 | * `defaults` **{Object}**: key/value pairs, useful when use ES2015 default arguments 398 | * `body` **{String}**: actual body of the function, respects trailing newlines and whitespaces 399 | * `isValid` **{Boolean}**: is the given value valid or not, that's because it never throws! 400 | * `isAsync` **{Boolean}**: `true` if function is ES2015 async/await function 401 | * `isArrow` **{Boolean}**: `true` if the function is arrow function 402 | * `isNamed` **{Boolean}**: `true` if function has name, or `false` if is anonymous 403 | * `isGenerator` **{Boolean}**: `true` if the function is ES2015 generator function 404 | * `isAnonymous` **{Boolean}**: `true` if the function don't have name 405 | 406 | **[back to top](#thetop)** 407 | 408 | ## Related 409 | - [acorn](https://www.npmjs.com/package/acorn): ECMAScript parser | [homepage](https://github.com/ternjs/acorn 410 | "ECMAScript parser") 411 | - [babylon](https://www.npmjs.com/package/babylon): A JavaScript parser | [homepage](https://babeljs.io/ "A JavaScript 412 | parser") 413 | - [charlike-cli](https://www.npmjs.com/package/charlike-cli): Command line interface for the [charlike][] project 414 | scaffolder. | [homepage](https://github.com/tunnckoCore/charlike-cli#readme "Command line interface for the 415 | [charlike][] project scaffolder.") 416 | - [espree](https://www.npmjs.com/package/espree): An Esprima-compatible JavaScript parser built on Acorn | 417 | [homepage](https://github.com/eslint/espree "An Esprima-compatible JavaScript parser built on Acorn") 418 | - [hela](https://www.npmjs.com/package/hela): Powerful & flexible task runner framework in 80 lines, based on execa… 419 | [more](https://github.com/tunnckoCore/hela#readme) | [homepage](https://github.com/tunnckoCore/hela#readme "Powerful & 420 | flexible task runner framework in 80 lines, based on execa. Supports presets, a la ESLint but for tasks & npm scripts") 421 | - [parse-semver](https://www.npmjs.com/package/parse-semver): Parse, normalize and validate given semver shorthand 422 | (e.g. gulp@v3.8.10) to object. | [homepage](https://github.com/tunnckocore/parse-semver#readme "Parse, normalize and 423 | validate given semver shorthand (e.g. gulp@v3.8.10) to object.") 424 | 425 | ## Contributing 426 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue][open-issue-url]. 427 | Please read the [Contributing Guide](./CONTRIBUTING.md) and [Code of Conduct](./CODE_OF_CONDUCT.md) documents for 428 | advices. 429 | 430 | ## Author 431 | - [github/tunnckoCore](https://github.com/tunnckoCore) 432 | - [twitter/tunnckoCore](https://twitter.com/tunnckoCore) 433 | - [codementor/tunnckoCore](https://codementor.io/tunnckoCore) 434 | 435 | ## License 436 | Copyright © 2016-2017, [Charlike Mike Reagent](https://i.am.charlike.online). Released under the [MIT 437 | License](LICENSE). 438 | 439 | *** 440 | 441 | _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on October 442 | 09, 2017._ 443 | Project scaffolded and managed with [hela][]. 444 | 445 | [acorn]: https://github.com/ternjs/acorn 446 | [babylon]: https://babeljs.io/ 447 | [charlike-cli]: https://github.com/tunnckoCore/charlike-cli 448 | [charlike]: https://github.com/tunnckoCore/charlike 449 | [define-property]: https://github.com/jonschlinkert/define-property 450 | [espree]: https://github.com/eslint/espree 451 | [execa]: https://github.com/sindresorhus/execa 452 | [function-arguments]: https://github.com/tunnckocore/function-arguments 453 | 454 | 455 | [npmv-url]: https://www.npmjs.com/package/parse-function 456 | [npmv-img]: https://img.shields.io/npm/v/parse-function.svg 457 | 458 | [open-issue-url]: https://github.com/tunnckoCore/parse-function/issues/new 459 | [github-release-url]: https://github.com/tunnckoCore/parse-function/releases/latest 460 | [github-release-img]: https://img.shields.io/github/release/tunnckoCore/parse-function.svg 461 | 462 | [license-url]: https://github.com/tunnckoCore/parse-function/blob/master/LICENSE 463 | [license-img]: https://img.shields.io/npm/l/parse-function.svg 464 | 465 | [downloads-weekly-url]: https://www.npmjs.com/package/parse-function 466 | [downloads-weekly-img]: https://img.shields.io/npm/dw/parse-function.svg 467 | 468 | [downloads-total-url]: https://www.npmjs.com/package/parse-function 469 | [downloads-total-img]: https://img.shields.io/npm/dt/parse-function.svg 470 | 471 | 472 | [codeclimate-url]: https://codeclimate.com/github/tunnckoCore/parse-function 473 | [codeclimate-img]: https://img.shields.io/codeclimate/github/tunnckoCore/parse-function.svg 474 | 475 | [standard-url]: https://github.com/standard/standard 476 | [standard-img]: https://img.shields.io/badge/code_style-standard-brightgreen.svg 477 | 478 | [travis-url]: https://travis-ci.org/tunnckoCore/parse-function 479 | [travis-img]: https://img.shields.io/travis/tunnckoCore/parse-function/master.svg?label=linux 480 | 481 | [codecov-url]: https://codecov.io/gh/tunnckoCore/parse-function 482 | [codecov-img]: https://img.shields.io/codecov/c/github/tunnckoCore/parse-function/master.svg 483 | 484 | [dependencies-url]: https://david-dm.org/tunnckoCore/parse-function 485 | [dependencies-img]: https://img.shields.io/david/tunnckoCore/parse-function.svg 486 | 487 | [renovate-url]: https://renovateapp.com 488 | [renovate-img]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg 489 | 490 | 491 | 492 | [gitterchat-url]: https://gitter.im/tunnckoCore/support 493 | [gitterchat-img]: https://img.shields.io/gitter/room/tunnckoCore/support.svg 494 | 495 | [prettier-url]: https://github.com/prettier/prettier 496 | [prettier-img]: https://img.shields.io/badge/styled_with-prettier-f952a5.svg 497 | 498 | [nodesecurity-url]: https://nodesecurity.io/orgs/tunnckocore-dev/projects/5d75a388-acfe-4668-ad18-e98564e387e1 499 | [nodesecurity-img]: https://nodesecurity.io/orgs/tunnckocore-dev/projects/5d75a388-acfe-4668-ad18-e98564e387e1/badge 500 | 502 | 503 | [semantic-release-url]: https://github.com/semantic-release/semantic-release 504 | [semantic-release-img]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg 505 | 506 | [ccommits-url]: https://conventionalcommits.org/ 507 | [ccommits-img]: https://img.shields.io/badge/conventional_commits-1.0.0-yellow.svg 508 | 509 | [nodeversion-url]: https://nodejs.org/en/download 510 | [nodeversion-img]: https://img.shields.io/node/v/parse-function.svg 511 | 512 | [hela]: https://github.com/tunnckoCore/hela 513 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parse-function", 3 | "version": "0.0.0-semantically-released", 4 | "description": "Parse a function into an object using espree, acorn or babylon parsers. Extensible through Smart Plugins", 5 | "repository": "tunnckoCore/parse-function", 6 | "homepage": "https://github.com/tunnckoCore/parse-function", 7 | "author": "Charlike Mike Reagent <@tunnckoCore> (https://i.am.charlike.online)", 8 | "nspId": "42a5e14a-70da-49ee-86e7-d1f39ed08603", 9 | "src": "./src/**/*.js", 10 | "main": "./dist/index.js", 11 | "module": "./dist/index.es.js", 12 | "scripts": { 13 | "start": "hela", 14 | "test": "NODE_ENV=test yarn hela test", 15 | "build": "yarn hela build", 16 | "release": "yarn new-release", 17 | "precommit": "yarn hela precommit", 18 | "commit": "yarn hela commit" 19 | }, 20 | "license": "MIT", 21 | "licenseStart": 2016, 22 | "engines": { 23 | "node": ">=6", 24 | "npm": ">=5", 25 | "yarn": ">=1" 26 | }, 27 | "files": [ 28 | "dist/" 29 | ], 30 | "dependencies": { 31 | "arrify": "2.0.1", 32 | "babylon": "7.0.0-beta.47", 33 | "define-property": "2.0.2" 34 | }, 35 | "devDependencies": { 36 | "acorn": "5.7.3", 37 | "clone-deep": "4.0.1", 38 | "eslint-config-standard-tunnckocore": "1.0.10", 39 | "espree": "6.1.1", 40 | "for-in": "1.0.2", 41 | "hela": "1.1.3", 42 | "hela-preset-tunnckocore": "0.5.19", 43 | "husky": "3.0.9", 44 | "mukla": "0.4.9", 45 | "new-release": "5.0.4" 46 | }, 47 | "keywords": [ 48 | "args", 49 | "arguments", 50 | "arrow", 51 | "arrowfn", 52 | "async", 53 | "asyncawait", 54 | "await", 55 | "body", 56 | "fn", 57 | "fns", 58 | "func", 59 | "function", 60 | "gen", 61 | "generators", 62 | "name", 63 | "object", 64 | "param", 65 | "paramerters", 66 | "params", 67 | "parse", 68 | "parse-function", 69 | "parser", 70 | "prop", 71 | "regular", 72 | "string", 73 | "tostring", 74 | "util" 75 | ], 76 | "verb": { 77 | "run": true, 78 | "toc": { 79 | "render": true, 80 | "method": "preWrite", 81 | "maxdepth": 3 82 | }, 83 | "layout": "empty", 84 | "tasks": [ 85 | "readme" 86 | ], 87 | "related": { 88 | "list": [ 89 | "babylon", 90 | "acorn", 91 | "espree", 92 | "parse-semver", 93 | "charlike-cli", 94 | "hela" 95 | ], 96 | "highlight": "hela" 97 | }, 98 | "lint": { 99 | "reflinks": true 100 | }, 101 | "reflinks": [ 102 | "acorn", 103 | "babylon", 104 | "charlike", 105 | "charlike-cli", 106 | "define-property", 107 | "espree", 108 | "execa", 109 | "function-arguments", 110 | "hela", 111 | "new-release" 112 | ] 113 | }, 114 | "release": { 115 | "analyzeCommits": "simple-commit-message" 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tunnckocore" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * parse-function 3 | * 4 | * Copyright (c) 2017 Charlike Mike Reagent (https://i.am.charlike.online) 5 | * Released under the MIT license. 6 | */ 7 | 8 | /** 9 | * Utilities 10 | */ 11 | 12 | import utils from './lib/utils.js' 13 | 14 | /** 15 | * Core plugins 16 | */ 17 | 18 | import initial from './lib/plugins/initial.js' 19 | 20 | /** 21 | * > Initializes with optional `opts` object which is passed directly 22 | * to the desired parser and returns an object 23 | * with `.use` and `.parse` methods. The default parse which 24 | * is used is [babylon][]'s `.parseExpression` method from `v7`. 25 | * 26 | * ```js 27 | * const parseFunction = require('parse-function') 28 | * 29 | * const app = parseFunction({ 30 | * ecmaVersion: 2017 31 | * }) 32 | * 33 | * const fixtureFn = (a, b, c) => { 34 | * a = b + c 35 | * return a + 2 36 | * } 37 | * 38 | * const result = app.parse(fixtureFn) 39 | * console.log(result) 40 | * 41 | * // see more 42 | * console.log(result.name) // => null 43 | * console.log(result.isNamed) // => false 44 | * console.log(result.isArrow) // => true 45 | * console.log(result.isAnonymous) // => true 46 | * 47 | * // array of names of the arguments 48 | * console.log(result.args) // => ['a', 'b', 'c'] 49 | * 50 | * // comma-separated names of the arguments 51 | * console.log(result.params) // => 'a, b, c' 52 | * ``` 53 | * 54 | * @param {Object} `opts` optional, merged with options passed to `.parse` method 55 | * @return {Object} `app` object with `.use` and `.parse` methods 56 | * @name parseFunction 57 | * @api public 58 | */ 59 | 60 | export default function parseFunction (opts) { 61 | const plugins = [] 62 | const app = { 63 | /** 64 | * > Parse a given `code` and returns a `result` object 65 | * with useful properties - such as `name`, `body` and `args`. 66 | * By default it uses Babylon parser, but you can switch it by 67 | * passing `options.parse` - for example `options.parse: acorn.parse`. 68 | * In the below example will show how to use `acorn` parser, instead 69 | * of the default one. 70 | * 71 | * ```js 72 | * const acorn = require('acorn') 73 | * const parseFn = require('parse-function') 74 | * const app = parseFn() 75 | * 76 | * const fn = function foo (bar, baz) { return bar * baz } 77 | * const result = app.parse(fn, { 78 | * parse: acorn.parse, 79 | * ecmaVersion: 2017 80 | * }) 81 | * 82 | * console.log(result.name) // => 'foo' 83 | * console.log(result.args) // => ['bar', 'baz'] 84 | * console.log(result.body) // => ' return bar * baz ' 85 | * console.log(result.isNamed) // => true 86 | * console.log(result.isArrow) // => false 87 | * console.log(result.isAnonymous) // => false 88 | * console.log(result.isGenerator) // => false 89 | * ``` 90 | * 91 | * @param {Function|String} `code` any kind of function or string to be parsed 92 | * @param {Object} `options` directly passed to the parser - babylon, acorn, espree 93 | * @param {Function} `options.parse` by default `babylon.parseExpression`, 94 | * all `options` are passed as second argument 95 | * to that provided function 96 | * @return {Object} `result` see [result section](#result) for more info 97 | * @name .parse 98 | * @api public 99 | */ 100 | 101 | parse (code, options) { 102 | let result = utils.setDefaults(code) 103 | 104 | if (!result.isValid) { 105 | return result 106 | } 107 | 108 | opts = Object.assign({}, opts, options) 109 | 110 | const isFunction = result.value.startsWith('function') 111 | const isAsyncFn = result.value.startsWith('async function') 112 | const isAsync = result.value.startsWith('async') 113 | const isArrow = result.value.includes('=>') 114 | const isAsyncArrow = isAsync && isArrow 115 | 116 | // eslint-disable-next-line no-useless-escape 117 | const isMethod = /^\*?.+\([\s\S\w\W]*\)\s*\{/i.test(result.value) 118 | 119 | if (!(isFunction || isAsyncFn || isAsyncArrow) && isMethod) { 120 | result.value = `{ ${result.value} }` 121 | } 122 | 123 | let node = utils.getNode(result, opts) 124 | return plugins.reduce((res, fn) => fn(node, res) || res, result) 125 | }, 126 | 127 | /** 128 | * > Add a plugin `fn` function for extending the API or working on the 129 | * AST nodes. The `fn` is immediately invoked and passed 130 | * with `app` argument which is instance of `parseFunction()` call. 131 | * That `fn` may return another function that 132 | * accepts `(node, result)` signature, where `node` is an AST node 133 | * and `result` is an object which will be returned [result](#result) 134 | * from the `.parse` method. This retuned function is called on each 135 | * node only when `.parse` method is called. 136 | * 137 | * _See [Plugins Architecture](#plugins-architecture) section._ 138 | * 139 | * ```js 140 | * // plugin extending the `app` 141 | * app.use((app) => { 142 | * app.define(app, 'hello', (place) => `Hello ${place}!`) 143 | * }) 144 | * 145 | * const hi = app.hello('World') 146 | * console.log(hi) // => 'Hello World!' 147 | * 148 | * // or plugin that works on AST nodes 149 | * app.use((app) => (node, result) => { 150 | * if (node.type === 'ArrowFunctionExpression') { 151 | * result.thatIsArrow = true 152 | * } 153 | * return result 154 | * }) 155 | * 156 | * const result = app.parse((a, b) => (a + b + 123)) 157 | * console.log(result.name) // => null 158 | * console.log(result.isArrow) // => true 159 | * console.log(result.thatIsArrow) // => true 160 | * 161 | * const result = app.parse(function foo () { return 123 }) 162 | * console.log(result.name) // => 'foo' 163 | * console.log(result.isArrow) // => false 164 | * console.log(result.thatIsArrow) // => undefined 165 | * ``` 166 | * 167 | * @param {Function} `fn` plugin to be called 168 | * @return {Object} `app` instance for chaining 169 | * @name .use 170 | * @api public 171 | */ 172 | 173 | use (fn) { 174 | const ret = fn(app) 175 | if (typeof ret === 'function') { 176 | plugins.push(ret) 177 | } 178 | return app 179 | }, 180 | 181 | /** 182 | * > Define a non-enumerable property on an object. Just 183 | * a convenience mirror of the [define-property][] library, 184 | * so check out its docs. Useful to be used in plugins. 185 | * 186 | * ```js 187 | * const parseFunction = require('parse-function') 188 | * const app = parseFunction() 189 | * 190 | * // use it like `define-property` lib 191 | * const obj = {} 192 | * app.define(obj, 'hi', 'world') 193 | * console.log(obj) // => { hi: 'world' } 194 | * 195 | * // or define a custom plugin that adds `.foo` property 196 | * // to the end result, returned from `app.parse` 197 | * app.use((app) => { 198 | * return (node, result) => { 199 | * // this function is called 200 | * // only when `.parse` is called 201 | * 202 | * app.define(result, 'foo', 123) 203 | * 204 | * return result 205 | * } 206 | * }) 207 | * 208 | * // fixture function to be parsed 209 | * const asyncFn = async (qux) => { 210 | * const bar = await Promise.resolve(qux) 211 | * return bar 212 | * } 213 | * 214 | * const result = app.parse(asyncFn) 215 | * 216 | * console.log(result.name) // => null 217 | * console.log(result.foo) // => 123 218 | * console.log(result.args) // => ['qux'] 219 | * 220 | * console.log(result.isAsync) // => true 221 | * console.log(result.isArrow) // => true 222 | * console.log(result.isNamed) // => false 223 | * console.log(result.isAnonymous) // => true 224 | * ``` 225 | * 226 | * @param {Object} `obj` the object on which to define the property 227 | * @param {String} `prop` the name of the property to be defined or modified 228 | * @param {Any} `val` the descriptor for the property being defined or modified 229 | * @return {Object} `obj` the passed object, but modified 230 | * @name .define 231 | * @api public 232 | */ 233 | 234 | define: utils.define, 235 | } 236 | 237 | app.use(initial) 238 | 239 | return app 240 | } 241 | -------------------------------------------------------------------------------- /src/lib/plugins/body.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * parse-function 3 | * 4 | * Copyright (c) 2017 Charlike Mike Reagent (https://i.am.charlike.online) 5 | * Released under the MIT license. 6 | */ 7 | 8 | /* eslint-disable jsdoc/require-param-description, jsdoc/check-param-names */ 9 | 10 | /** 11 | * > Micro plugin to get the raw body, without the 12 | * surrounding curly braces. It also preserves 13 | * the whitespaces and newlines - they are original. 14 | * 15 | * @param {Object} node 16 | * @param {Object} result 17 | * @return {Object} result 18 | * @private 19 | */ 20 | export default (app) => (node, result) => { 21 | result.body = result.value.slice(node.body.start, node.body.end) 22 | 23 | const openCurly = result.body.charCodeAt(0) === 123 24 | const closeCurly = result.body.charCodeAt(result.body.length - 1) === 125 25 | 26 | if (openCurly && closeCurly) { 27 | result.body = result.body.slice(1, -1) 28 | } 29 | 30 | return result 31 | } 32 | -------------------------------------------------------------------------------- /src/lib/plugins/initial.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * parse-function 3 | * 4 | * Copyright (c) 2017 Charlike Mike Reagent (https://i.am.charlike.online) 5 | * Released under the MIT license. 6 | */ 7 | 8 | /* eslint-disable jsdoc/require-param-description, jsdoc/check-param-names */ 9 | 10 | import body from './body.js' 11 | import props from './props.js' 12 | import params from './params.js' 13 | 14 | /** 15 | * > Default plugin that handles regular functions, 16 | * arrow functions, generator functions 17 | * and ES6 object method notation. 18 | * 19 | * @param {Object} node 20 | * @param {Object} result 21 | * @return {Object} resul 22 | * @private 23 | */ 24 | export default (app) => (node, result) => { 25 | const isFn = node.type.endsWith('FunctionExpression') 26 | const isMethod = node.type === 'ObjectExpression' 27 | 28 | /* istanbul ignore next */ 29 | if (!isFn && !isMethod) { 30 | return 31 | } 32 | 33 | node = isMethod ? node.properties[0] : node 34 | node.id = isMethod ? node.key : node.id 35 | 36 | // babylon node.properties[0] is `ObjectMethod` that has `params` and `body`; 37 | // acorn node.properties[0] is `Property` that has `value`; 38 | if (node.type === 'Property') { 39 | const id = node.key 40 | node = node.value 41 | node.id = id 42 | } 43 | 44 | result = props(app)(node, result) 45 | result = params(app)(node, result) 46 | result = body(app)(node, result) 47 | 48 | return result 49 | } 50 | -------------------------------------------------------------------------------- /src/lib/plugins/params.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * parse-function 3 | * 4 | * Copyright (c) 2017 Charlike Mike Reagent (https://i.am.charlike.online) 5 | * Released under the MIT license. 6 | */ 7 | 8 | /* eslint-disable jsdoc/require-param-description, jsdoc/check-param-names */ 9 | 10 | /** 11 | * > Micro plugin to visit each of the params 12 | * in the given function and collect them into 13 | * an `result.args` array and `result.params` string. 14 | * 15 | * @param {Object} node 16 | * @param {Object} result 17 | * @return {Object} result 18 | * @private 19 | */ 20 | export default (app) => (node, result) => { 21 | if (!node.params.length) { 22 | return result 23 | } 24 | 25 | node.params.forEach((param) => { 26 | const defaultArgsName = 27 | param.type === 'AssignmentPattern' && param.left && param.left.name 28 | 29 | const restArgName = 30 | param.type === 'RestElement' && param.argument && param.argument.name 31 | 32 | const name = param.name || defaultArgsName || restArgName 33 | 34 | result.args.push(name) 35 | 36 | if (param.right && param.right.type === 'SequenceExpression') { 37 | let lastExpression = param.right.expressions.pop() 38 | 39 | result.defaults[name] = result.value.slice( 40 | lastExpression.start, 41 | lastExpression.end 42 | ) 43 | } else { 44 | result.defaults[name] = param.right 45 | ? result.value.slice(param.right.start, param.right.end) 46 | : undefined 47 | } 48 | }) 49 | result.params = result.args.join(', ') 50 | 51 | return result 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/plugins/props.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * parse-function 3 | * 4 | * Copyright (c) 2017 Charlike Mike Reagent (https://i.am.charlike.online) 5 | * Released under the MIT license. 6 | */ 7 | 8 | /* eslint-disable jsdoc/require-param-description, jsdoc/check-param-names */ 9 | 10 | /** 11 | * > Set couple of hidden properties and 12 | * the name of the given function to 13 | * the returned result object. Notice that 14 | * if function is called "anonymous" then 15 | * the `result.isAnonymous` would be `false`, because 16 | * in reality it is named function. It would be `true` 17 | * only when function is really anonymous and don't have 18 | * any name. 19 | * 20 | * @param {Object} node 21 | * @param {Object} result 22 | * @return {Object} result 23 | * @private 24 | */ 25 | export default (app) => (node, result) => { 26 | app.define(result, 'isArrow', node.type.startsWith('Arrow')) 27 | app.define(result, 'isAsync', node.async || false) 28 | app.define(result, 'isGenerator', node.generator || false) 29 | app.define(result, 'isExpression', node.expression || false) 30 | app.define(result, 'isAnonymous', node.id === null) 31 | app.define(result, 'isNamed', !result.isAnonymous) 32 | 33 | // if real anonymous -> set to null, 34 | // notice that you can name you function `anonymous`, haha 35 | // and it won't be "real" anonymous, so `isAnonymous` will be `false` 36 | result.name = result.isAnonymous ? null : node.id.name 37 | 38 | return result 39 | } 40 | -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * parse-function 3 | * 4 | * Copyright (c) 2017 Charlike Mike Reagent (https://i.am.charlike.online) 5 | * Released under the MIT license. 6 | */ 7 | 8 | /* eslint-disable jsdoc/require-param-description */ 9 | 10 | import arrayify from 'arrify' 11 | import babylon from 'babylon' 12 | import define from 'define-property' 13 | 14 | const utils = {} 15 | utils.define = define 16 | utils.arrayify = arrayify 17 | 18 | /** 19 | * > Create default result object, 20 | * and normalize incoming arguments. 21 | * 22 | * @param {Function|String} code 23 | * @return {Object} result 24 | * @private 25 | */ 26 | utils.setDefaults = function setDefaults (code) { 27 | const result = { 28 | name: null, 29 | body: '', 30 | args: [], 31 | params: '', 32 | } 33 | 34 | if (typeof code === 'function') { 35 | code = code.toString('utf8') 36 | } 37 | 38 | if (typeof code !== 'string') { 39 | code = '' // makes result.isValid === false 40 | } 41 | 42 | return utils.setHiddenDefaults(result, code) 43 | } 44 | 45 | /** 46 | * > Create hidden properties into 47 | * the result object. 48 | * 49 | * @param {Object} result 50 | * @param {Function|String} code 51 | * @return {Object} result 52 | * @private 53 | */ 54 | utils.setHiddenDefaults = function setHiddenDefaults (result, code) { 55 | utils.define(result, 'defaults', {}) 56 | utils.define(result, 'value', code) 57 | utils.define(result, 'isValid', code.length > 0) 58 | utils.define(result, 'isArrow', false) 59 | utils.define(result, 'isAsync', false) 60 | utils.define(result, 'isNamed', false) 61 | utils.define(result, 'isAnonymous', false) 62 | utils.define(result, 'isGenerator', false) 63 | utils.define(result, 'isExpression', false) 64 | 65 | return result 66 | } 67 | 68 | /** 69 | * > Get needed AST tree, depending on what 70 | * parse method is used. 71 | * 72 | * @param {Object} result 73 | * @param {Object} opts 74 | * @return {Object} node 75 | * @private 76 | */ 77 | utils.getNode = function getNode (result, opts) { 78 | if (typeof opts.parse === 'function') { 79 | result.value = `(${result.value})` 80 | 81 | const ast = opts.parse(result.value, opts) 82 | const body = (ast.program && ast.program.body) || ast.body 83 | 84 | return body[0].expression 85 | } 86 | 87 | return babylon.parseExpression(result.value, opts) 88 | } 89 | 90 | export default utils 91 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Charlike Mike Reagent 3 | * @copyright 2016-present @tunnckoCore/team and contributors 4 | * @license MIT 5 | */ 6 | 7 | /* eslint-disable max-len */ 8 | 9 | import test from 'mukla' 10 | 11 | import espree from 'espree' 12 | import babylon from 'babylon' 13 | import acornLoose from 'acorn/dist/acorn_loose' 14 | 15 | import acorn from 'acorn' 16 | import forIn from 'for-in' 17 | import clone from 'clone-deep' 18 | import parseFunction from '../src/index' 19 | 20 | const acornParse = acorn.parse 21 | const espreeParse = espree.parse 22 | const babylonParse = babylon.parse 23 | const acornLooseParse = acornLoose.parse_dammit 24 | 25 | const actuals = { 26 | regulars: [ 27 | 'function (a = {foo: "ba)r", baz: 123}, cb, ...restArgs) {return a * 3}', 28 | 'function (b, callback, ...restArgs) {callback(null, b + 3)}', 29 | 'function (c) {return c * 3}', 30 | 'function (...restArgs) {return 321}', 31 | 'function () {}', 32 | 'function (a = (true, false)) {}', 33 | 'function (a = (true, null)) {}', 34 | 'function (a = (true, "bar")) {}', 35 | 'function (a, b = (i++, true)) {}', 36 | 'function (a = 1) {}', 37 | ], 38 | named: [ 39 | 'function namedFn (a = {foo: "ba)r", baz: 123}, cb, ...restArgs) {return a * 3}', 40 | 'function namedFn (b, callback, ...restArgs) {callback(null, b + 3)}', 41 | 'function namedFn (c) {return c * 3}', 42 | 'function namedFn (...restArgs) {return 321}', 43 | 'function namedFn () {}', 44 | 'function namedFn(a = (true, false)) {}', 45 | 'function namedFn(a = (true, null)) {}', 46 | 'function namedFn(a = (true, "bar")) {}', 47 | 'function namedFn(a, b = (i++, true)) {}', 48 | 'function namedFn(a = 1) {}', 49 | ], 50 | generators: [ 51 | 'function * namedFn (a = {foo: "ba)r", baz: 123}, cb, ...restArgs) {return a * 3}', 52 | 'function * namedFn (b, callback, ...restArgs) {callback(null, b + 3)}', 53 | 'function * namedFn (c) {return c * 3}', 54 | 'function * namedFn (...restArgs) {return 321}', 55 | 'function * namedFn () {}', 56 | 'function * namedFn(a = (true, false)) {}', 57 | 'function * namedFn(a = (true, null)) {}', 58 | 'function * namedFn(a = (true, "bar")) {}', 59 | 'function * namedFn(a, b = (i++, true)) {}', 60 | 'function * namedFn(a = 1) {}', 61 | ], 62 | arrows: [ 63 | '(a = {foo: "ba)r", baz: 123}, cb, ...restArgs) => {return a * 3}', 64 | '(b, callback, ...restArgs) => {callback(null, b + 3)}', 65 | '(c) => {return c * 3}', 66 | '(...restArgs) => {return 321}', 67 | '() => {}', 68 | '(a = (true, false)) => {}', 69 | '(a = (true, null)) => {}', 70 | '(a = (true, "bar")) => {}', 71 | '(a, b = (i++, true)) => {}', 72 | '(a = 1) => {}', 73 | '(a) => a * 3 * a', 74 | 'd => d * 355 * d', 75 | 'e => {return e + 5235 / e}', 76 | '(a, b) => a + 3 + b', 77 | '(x, y, ...restArgs) => console.log({ value: x * y })', 78 | ], 79 | } 80 | 81 | /** 82 | * Merge all into one 83 | * and prepend `async` keyword 84 | * before each function 85 | */ 86 | 87 | actuals.asyncs = actuals.regulars 88 | .concat(actuals.named) 89 | .concat(actuals.arrows) 90 | .map((item) => `async ${item}`) 91 | 92 | const regulars = [ 93 | { 94 | name: null, 95 | params: 'a, cb, restArgs', 96 | args: ['a', 'cb', 'restArgs'], 97 | body: 'return a * 3', 98 | defaults: { 99 | a: '{foo: "ba)r", baz: 123}', 100 | cb: undefined, 101 | restArgs: undefined, 102 | }, 103 | }, 104 | { 105 | name: null, 106 | params: 'b, callback, restArgs', 107 | args: ['b', 'callback', 'restArgs'], 108 | body: 'callback(null, b + 3)', 109 | defaults: { b: undefined, callback: undefined, restArgs: undefined }, 110 | }, 111 | { 112 | name: null, 113 | params: 'c', 114 | args: ['c'], 115 | body: 'return c * 3', 116 | defaults: { c: undefined }, 117 | }, 118 | { 119 | name: null, 120 | params: 'restArgs', 121 | args: ['restArgs'], 122 | body: 'return 321', 123 | defaults: { restArgs: undefined }, 124 | }, 125 | { 126 | name: null, 127 | params: '', 128 | args: [], 129 | body: '', 130 | defaults: {}, 131 | }, 132 | { 133 | name: null, 134 | params: 'a', 135 | args: ['a'], 136 | body: '', 137 | defaults: { a: 'false' }, 138 | }, 139 | { 140 | name: null, 141 | params: 'a', 142 | args: ['a'], 143 | body: '', 144 | defaults: { a: 'null' }, 145 | }, 146 | { 147 | name: null, 148 | params: 'a', 149 | args: ['a'], 150 | body: '', 151 | defaults: { a: '"bar"' }, 152 | }, 153 | { 154 | name: null, 155 | params: 'a, b', 156 | args: ['a', 'b'], 157 | body: '', 158 | defaults: { a: undefined, b: 'true' }, 159 | }, 160 | { 161 | name: null, 162 | params: 'a', 163 | args: ['a'], 164 | body: '', 165 | defaults: { a: '1' }, 166 | }, 167 | ] 168 | 169 | /** 170 | * All of the regular functions 171 | * variants plus few more 172 | */ 173 | 174 | const arrows = clone(regulars).concat([ 175 | { 176 | name: null, 177 | params: 'a', 178 | args: ['a'], 179 | body: 'a * 3 * a', 180 | defaults: { a: undefined }, 181 | }, 182 | { 183 | name: null, 184 | params: 'd', 185 | args: ['d'], 186 | body: 'd * 355 * d', 187 | defaults: { d: undefined }, 188 | }, 189 | { 190 | name: null, 191 | params: 'e', 192 | args: ['e'], 193 | body: 'return e + 5235 / e', 194 | defaults: { e: undefined }, 195 | }, 196 | { 197 | name: null, 198 | params: 'a, b', 199 | args: ['a', 'b'], 200 | body: 'a + 3 + b', 201 | defaults: { a: undefined, b: undefined }, 202 | }, 203 | { 204 | name: null, 205 | params: 'x, y, restArgs', 206 | args: ['x', 'y', 'restArgs'], 207 | body: 'console.log({ value: x * y })', 208 | defaults: { x: undefined, y: undefined, restArgs: undefined }, 209 | }, 210 | ]) 211 | 212 | /** 213 | * All of the regulars, but 214 | * with different name 215 | */ 216 | 217 | const named = clone(regulars).map((item) => { 218 | item.name = 'namedFn' 219 | return item 220 | }) 221 | 222 | const expected = { 223 | regulars: regulars, 224 | named: named, 225 | generators: named, 226 | arrows: arrows, 227 | asyncs: regulars.concat(named).concat(arrows), 228 | } 229 | 230 | let testsCount = 1 231 | 232 | /** 233 | * Factory for DRY, we run all tests 234 | * over two available parsers - one 235 | * is the default `babylon`, second is 236 | * the `acorn.parse` method. 237 | */ 238 | 239 | function factory (parserName, parseFn) { 240 | forIn(actuals, (values, key) => { 241 | values.forEach((code, i) => { 242 | const actual = parseFn(code) 243 | const expect = expected[key][i] 244 | const value = actual.value.replace(/^\(\{? ?/, '').replace(/\)$/, '') 245 | 246 | test(`#${testsCount++} - ${parserName} - ${value}`, (done) => { 247 | test.strictEqual(actual.isValid, true) 248 | test.strictEqual(actual.name, expect.name) 249 | test.strictEqual(actual.body, expect.body) 250 | test.strictEqual(actual.params, expect.params) 251 | test.deepEqual(actual.args, expect.args) 252 | test.deepEqual(actual.defaults, expect.defaults) 253 | test.ok(actual.value) 254 | done() 255 | }) 256 | }) 257 | }) 258 | 259 | test(`#${testsCount++} - ${parserName} - should return object with default values when invalid`, (done) => { 260 | const actual = parseFn(123456) 261 | 262 | test.strictEqual(actual.isValid, false) 263 | test.strictEqual(actual.value, '') 264 | test.strictEqual(actual.name, null) 265 | test.strictEqual(actual.body, '') 266 | test.strictEqual(actual.params, '') 267 | test.deepEqual(actual.args, []) 268 | done() 269 | }) 270 | 271 | test(`#${testsCount++} - ${parserName} - should have '.isValid' and few '.is*'' hidden properties`, (done) => { 272 | const actual = parseFn([1, 2, 3]) 273 | 274 | test.strictEqual(actual.isValid, false) 275 | test.strictEqual(actual.isArrow, false) 276 | test.strictEqual(actual.isAsync, false) 277 | test.strictEqual(actual.isNamed, false) 278 | test.strictEqual(actual.isAnonymous, false) 279 | test.strictEqual(actual.isGenerator, false) 280 | test.strictEqual(actual.isExpression, false) 281 | done() 282 | }) 283 | 284 | // bug in v4 and v5 285 | // https://github.com/tunnckoCore/parse-function/issues/3 286 | // test(`#${testsCount++} - ${parserName} - should not fails to get .body when something after close curly`, (done) => { 287 | // const actual = parseFn('function (a) {return a * 2}; var b = 1') 288 | // test.strictEqual(actual.body, 'return a * 2') 289 | // done() 290 | // }) 291 | 292 | test(`#${testsCount++} - ${parserName} - should work when comment in arguments (see #11)`, (done) => { 293 | const actual = parseFn('function (/* done */) { return 123 }') 294 | test.strictEqual(actual.params, '') 295 | test.strictEqual(actual.body, ' return 123 ') 296 | 297 | const res = parseFn('function (foo/* done */, bar) { return 123 }') 298 | test.strictEqual(res.params, 'foo, bar') 299 | test.strictEqual(res.body, ' return 123 ') 300 | done() 301 | }) 302 | 303 | test(`#${testsCount++} - ${parserName} - should support to parse generator functions`, (done) => { 304 | const actual = parseFn('function * named (abc) { return abc + 123 }') 305 | test.strictEqual(actual.name, 'named') 306 | test.strictEqual(actual.params, 'abc') 307 | test.strictEqual(actual.body, ' return abc + 123 ') 308 | done() 309 | }) 310 | 311 | test(`#${testsCount++} - ${parserName} - should support to parse async functions (ES2016)`, (done) => { 312 | const actual = parseFn('async function foo (bar) { return bar }') 313 | test.strictEqual(actual.name, 'foo') 314 | test.strictEqual(actual.params, 'bar') 315 | test.strictEqual(actual.body, ' return bar ') 316 | done() 317 | }) 318 | 319 | test(`#${testsCount++} - ${parserName} - should parse a real function which is passed`, (done) => { 320 | const actual = parseFn(function fooBar (a, bc) { 321 | return a + bc 322 | }) 323 | test.strictEqual(actual.name, 'fooBar') 324 | test.strictEqual(actual.params, 'a, bc') 325 | test.strictEqual(actual.body, '\n return a + bc;\n ') 326 | done() 327 | }) 328 | 329 | test(`#${testsCount++} - ${parserName} - should work for object methods`, (done) => { 330 | const obj = { 331 | foo (a, b, c) { 332 | return 123 333 | }, 334 | bar (a) { 335 | return () => a 336 | }, 337 | * gen (a) {}, 338 | } 339 | 340 | const actual = parseFn(obj.foo) 341 | test.strictEqual(actual.name, 'foo') 342 | test.strictEqual(actual.params, 'a, b, c') 343 | test.strictEqual(actual.body, '\n return 123;\n ') 344 | 345 | const bar = parseFn(obj.bar) 346 | test.strictEqual(bar.name, 'bar') 347 | test.strictEqual(bar.body, '\n return () => a;\n ') 348 | 349 | const gen = parseFn(obj.gen) 350 | test.strictEqual(gen.name, 'gen') 351 | 352 | const namedFn = `namedFn (a = {foo: 'ba)r', baz: 123}, cb, ...restArgs) { return a * 3 }` 353 | const named = parseFn(namedFn) 354 | test.strictEqual(named.name, 'namedFn') 355 | test.strictEqual(named.args.length, 3) 356 | test.strictEqual(named.body, ' return a * 3 ') 357 | done() 358 | }) 359 | 360 | test(`#${testsCount++} - ${parserName} - plugins api`, (done) => { 361 | const fnStr = `() => 123 + a + 44` 362 | const plugin = (app) => (node, result) => { 363 | result.called = true 364 | // you may want to return the `result`, 365 | // but it is the same as not return it 366 | // return result 367 | } 368 | const result = parseFn(fnStr, {}, plugin) 369 | 370 | test.strictEqual(result.called, true) 371 | done() 372 | }) 373 | 374 | test(`#${testsCount++} - ${parserName} - fn named "anonymous" has .name: 'anonymous'`, (done) => { 375 | const result = parseFn('function anonymous () {}') 376 | test.strictEqual(result.name, 'anonymous') 377 | test.strictEqual(result.isAnonymous, false) 378 | done() 379 | }) 380 | 381 | test(`#${testsCount++} - ${parserName} - real anonymous fn has .name: null`, (done) => { 382 | const actual = parseFn('function () {}') 383 | test.strictEqual(actual.name, null) 384 | test.strictEqual(actual.isAnonymous, true) 385 | done() 386 | }) 387 | } 388 | 389 | /** 390 | * Actually run all the tests 391 | */ 392 | 393 | factory('babylon default', (code, opts, plugin) => { 394 | const app = parseFunction() 395 | if (plugin) app.use(plugin) 396 | return app.parse(code, opts) 397 | }) 398 | 399 | factory('babylon.parse', (code, opts, plugin) => { 400 | const app = parseFunction({ 401 | parse: babylonParse, 402 | ecmaVersion: 2017, 403 | }) 404 | if (plugin) app.use(plugin) 405 | return app.parse(code, opts) 406 | }) 407 | 408 | factory('acorn.parse', (code, opts, plugin) => { 409 | const app = parseFunction({ 410 | parse: acornParse, 411 | ecmaVersion: 2017, 412 | }) 413 | if (plugin) app.use(plugin) 414 | return app.parse(code, opts) 415 | }) 416 | 417 | factory('acorn.parse_dammit', (code, opts, plugin) => { 418 | const app = parseFunction() 419 | if (plugin) app.use(plugin) 420 | return app.parse( 421 | code, 422 | Object.assign( 423 | { 424 | parse: acornLooseParse, 425 | ecmaVersion: 2017, 426 | }, 427 | opts 428 | ) 429 | ) 430 | }) 431 | 432 | factory('espree.parse', (code, opts, plugin) => { 433 | const app = parseFunction({ 434 | parse: espreeParse, 435 | ecmaVersion: 8, 436 | }) 437 | if (plugin) app.use(plugin) 438 | return app.parse(code, opts) 439 | }) 440 | 441 | test('should just extend the core API, not the end result', (done) => { 442 | const app = parseFunction() 443 | app.use((inst) => { 444 | app.define(inst, 'hello', (place) => `Hello ${place}!!`) 445 | }) 446 | const ret = app.hello('pinky World') 447 | test.strictEqual(ret, 'Hello pinky World!!') 448 | done() 449 | }) 450 | 451 | test('should call fn returned from plugin only when `parse` is called', (done) => { 452 | const app = parseFunction({ 453 | ecmaVersion: 2017, 454 | }) 455 | let called = 0 456 | app.use((app) => { 457 | called = 1 458 | return (node, result) => { 459 | called = 2 460 | } 461 | }) 462 | 463 | test.strictEqual(called, 1) 464 | 465 | let res = app.parse('(a, b) => {}') 466 | test.strictEqual(called, 2) 467 | test.strictEqual(res.params, 'a, b') 468 | done() 469 | }) 470 | 471 | // https://github.com/tunnckoCore/parse-function/issues/61 472 | test('should work with an async arrow function with an `if` statement', (done) => { 473 | const app = parseFunction() 474 | const parsed = app.parse('async (v) => { if (v) {} }') 475 | test.deepEqual(parsed, { 476 | name: null, 477 | body: ' if (v) {} ', 478 | args: ['v'], 479 | params: 'v', 480 | }) 481 | done() 482 | }) 483 | --------------------------------------------------------------------------------