├── .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 |
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 | 
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 | 
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 | 
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 |
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 |
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 |
--------------------------------------------------------------------------------