tunnckoCore/parse-function

View on GitHub
README.md

Summary

Maintainability
Test Coverage
<p align="center">
  <img align="center" src="https://cdn.jsdelivr.net/emojione/assets/svg/1f54e.svg" width="256" height="256" alt="Parse a function">
</p>

# parse-function [![npm version][npmv-img]][npmv-url] [![github release][github-release-img]][github-release-url] [![License][license-img]][license-url]

> Parse a function into an object using espree, acorn or babylon parsers. Extensible through Smart Plugins

<div id="thetop"></div>

You might also be interested in [hela](https://github.com/tunnckoCore/hela#readme).

## Quality Assurance :100:

[![Code Style Standard][standard-img]][standard-url] 
[![Linux Build][circleci-img]][circleci-url] 
[![Code Coverage][codecov-img]][codecov-url] 
[![Dependencies Status][dependencies-img]][dependencies-url] 
[![Renovate App Status][renovate-img]][renovate-url] 

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].  
You may also read the [Contributing Guide](./CONTRIBUTING.md). There, beside _"How to contribute?"_, we describe everything **_stated_** by  the badges.

[![Make A Pull Request][prs-welcome-img]][prs-welcome-url] 
[![Code Format Prettier][prettier-img]][prettier-url] 
[![Conventional Commits][ccommits-img]][ccommits-url] 
[![Semantically Released][new-release-img]][new-release-url] 
[![Renovate App Status][renovate-img]][renovate-url] 

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.

[![All Contributors Spec][all-contributors-img]](#contributors) 
[![Newsletter Subscribe][tinyletter-img]][tinyletter-url] 
[![Give thanks][give-donate-img]][give-donate-url] 
[![Share Love Tweet][share-love-img]][share-love-url] 
[![NPM Downloads Weekly][downloads-weekly-img]][npmv-url] 
[![NPM Downloads Monthly][downloads-monthly-img]][npmv-url] 
[![NPM Downloads Total][downloads-total-img]][npmv-url] 

## Features

- **Always up-to-date:** auto-publish new version when new version of dependency is out, [Renovate](https://renovateapp.com)
- **Standard:** using StandardJS, Prettier, SemVer, Semantic Release and conventional commits
- **Smart Plugins:** for extending the core API or the end [Result](#result), see [.use](#use) method and [Plugins Architecture](#plugins-architecture)
- **Extensible:** using plugins for working directly on AST nodes, see the [Plugins Architecture](#plugins-architecture)
- **ES2017 Ready:** by using `.parseExpression` method of the [babylon][] `v7.x` parser
- **Customization:** allows switching the parser, through `options.parse`
- **Support for:** arrow functions, default parameters, generators and async/await functions
- **Stable:** battle-tested in production and against all parsers - [espree][], [acorn][], [babylon][]
- **Tested:** with [450+ tests](./test.js) for _200%_ coverage

## Table of Contents
- [Install](#install)
- [Which version to use?](#which-version-to-use)
- [Notes](#notes)
  * [Throws in one specific case](#throws-in-one-specific-case)
  * [Function named _"anonymous"_](#function-named-_anonymous_)
  * [Real anonymous function](#real-anonymous-function)
  * [Plugins Architecture](#plugins-architecture)
- [API](#api)
  * [parseFunction](#parsefunction)
  * [.parse](#parse)
  * [.use](#use)
  * [.define](#define)
  * [Result](#result)
- [Related](#related)
- [Contributing](#contributing)
- [Author](#author)
- [License](#license)

_(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_

## Install

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.

```
$ yarn add parse-function
```

## Which version to use?

There's no breaking changes between the `v2.x` version. The only breaking is `v2.1` which also is not
working properly, so no use it.

**Use v2.0.x**

When you don't need support for `arrow functions` and `es6 default params`. This version 
uses a RegExp expression to work.

**Use v2.2.x**

Only when you need a _basic_ support for `es6 features` like arrow functions. This version 
uses a RegExp expression to work.

**Use v2.3.x**

When you want _full*_ support for `arrow functions` and `es6 default params`. Where this "full",
means "almost full", because it has bugs. This version also uses (`acorn.parse`) real parser 
to do the parsing.

**Use v3.x**

When you want to use different parser instead of the default `babylon.parse`, by passing custom
parse function to the `options.parse` option. **From this version we require `node >= 4`**.

**Use v4.x**

When you want full customization and most stable support for old and modern features. This version
uses `babylon.parseExpression` for parsing and provides a [Plugins API](#plugins-architecture). 
See the [Features](#features) section for more info.

**Use v5.x**

It is basically the same as `v4`, but requires Node 6 & npm 5. Another is boilerplate stuff.

**[back to top](#thetop)**

## Notes

### Throws in one specific case

> _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)_

It may throw in one specific case, otherwise it won't throw, so you should 
relay on the `result.isValid` for sure.

### Function named _"anonymous"_

> _see: [test/index.js#L319-L324](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L319-L324) and [Result](#result) section_

If you pass a function which is named _"anonymous"_ the `result.name` will be `'anonymous'`, 
but the `result.isAnonymous` will be `false` and `result.isNamed` will be `true`, because 
in fact it's a named function.

### Real anonymous function

> _see: [test/index.js#L326-L331](https://github.com/tunnckoCore/parse-function/blob/master/test/index.js#L326-L331) and [Result](#result) section_

Only if you pass really an anonymous function you will get `result.name` equal to `null`, 
`result.isAnonymous` equal to `true` and `result.isNamed` equal to `false`.

**[back to top](#thetop)**

### Plugins Architecture

> _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)_

A more human description of the plugin mechanism. Plugins are **synchronous** - no support
and no need for **async** plugins here, but notice that you can do that manually, because 
that exact architecture.

The first function that is passed to the [.use](#use) method is used for extending the core API, 
for example adding a new method to the `app` instance. That function is immediately invoked.

```js
const parseFunction = require('parse-function')
const app = parseFunction()

app.use((self) => {
  // self is same as `app`
  console.log(self.use)
  console.log(self.parse)
  console.log(self.define)

  self.define(self, 'foo', (bar) => bar + 1)
})

console.log(app.foo(2)) // => 3
```

On the other side, if you want to access the AST of the parser, you should return a function 
from that plugin, which function is passed with `(node, result)` signature.

This function is lazy plugin, it is called only when the [.parse](#parse) method is called.

```js
const parseFunction = require('parse-function')
const app = parseFunction()

app.use((self) => {
  console.log('immediately called')

  return (node, result) => {
    console.log('called only when .parse is invoked')
    console.log(node)
    console.log(result)
  } 
})
```

Where **1)** the `node` argument is an object - actual and real AST Node coming from the parser 
and **2)** the `result` is an object too - the end [Result](#result), on which 
you can add more properties if you want.

**[back to top](#thetop)**

## API
Review carefully the provided examples and the working [tests](./test/index.js).

### [parseFunction](src/index.js#L60)
> 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`.

**Params**

* `opts` **{Object}**: optional, merged with options passed to `.parse` method    
* `returns` **{Object}** `app`: object with `.use` and `.parse` methods  

**Example**

```js
const parseFunction = require('parse-function')

const app = parseFunction({
  ecmaVersion: 2017
})

const fixtureFn = (a, b, c) => {
  a = b + c
  return a + 2
}

const result = app.parse(fixtureFn)
console.log(result)

// see more
console.log(result.name) // => null
console.log(result.isNamed) // => false
console.log(result.isArrow) // => true
console.log(result.isAnonymous) // => true

// array of names of the arguments
console.log(result.args) // => ['a', 'b', 'c']

// comma-separated names of the arguments
console.log(result.params) // => 'a, b, c'
```

### [.parse](src/index.js#L101)
> 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.

**Params**

* `code` **{Function|String}**: any kind of function or string to be parsed    
* `options` **{Object}**: directly passed to the parser - babylon, acorn, espree    
* `options.parse` **{Function}**: by default `babylon.parseExpression`, all `options` are passed as second argument to that provided function    
* `returns` **{Object}** `result`: see [result section](#result) for more info  

**Example**

```js
const acorn = require('acorn')
const parseFn = require('parse-function')
const app = parseFn()

const fn = function foo (bar, baz) { return bar * baz }
const result = app.parse(fn, {
  parse: acorn.parse,
  ecmaVersion: 2017
})

console.log(result.name) // => 'foo'
console.log(result.args) // => ['bar', 'baz']
console.log(result.body) // => ' return bar * baz '
console.log(result.isNamed) // => true
console.log(result.isArrow) // => false
console.log(result.isAnonymous) // => false
console.log(result.isGenerator) // => false
```

### [.use](src/index.js#L173)
> 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.

_See [Plugins Architecture](#plugins-architecture) section._

**Params**

* `fn` **{Function}**: plugin to be called    
* `returns` **{Object}** `app`: instance for chaining  

**Example**

```js
// plugin extending the `app`
app.use((app) => {
  app.define(app, 'hello', (place) => `Hello ${place}!`)
})

const hi = app.hello('World')
console.log(hi) // => 'Hello World!'

// or plugin that works on AST nodes
app.use((app) => (node, result) => {
  if (node.type === 'ArrowFunctionExpression') {
    result.thatIsArrow = true
  }
  return result
})

const result = app.parse((a, b) => (a + b + 123))
console.log(result.name) // => null
console.log(result.isArrow) // => true
console.log(result.thatIsArrow) // => true

const result = app.parse(function foo () { return 123 })
console.log(result.name) // => 'foo'
console.log(result.isArrow) // => false
console.log(result.thatIsArrow) // => undefined
```

### [.define](src/index.js#L234)
> 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.

**Params**

* `obj` **{Object}**: the object on which to define the property    
* `prop` **{String}**: the name of the property to be defined or modified    
* `val` **{Any}**: the descriptor for the property being defined or modified    
* `returns` **{Object}** `obj`: the passed object, but modified  

**Example**

```js
const parseFunction = require('parse-function')
const app = parseFunction()

// use it like `define-property` lib
const obj = {}
app.define(obj, 'hi', 'world')
console.log(obj) // => { hi: 'world' }

// or define a custom plugin that adds `.foo` property
// to the end result, returned from `app.parse`
app.use((app) => {
  return (node, result) => {
    // this function is called
    // only when `.parse` is called

    app.define(result, 'foo', 123)

    return result
  }
})

// fixture function to be parsed
const asyncFn = async (qux) => {
  const bar = await Promise.resolve(qux)
  return bar
}

const result = app.parse(asyncFn)

console.log(result.name) // => null
console.log(result.foo) // => 123
console.log(result.args) // => ['qux']

console.log(result.isAsync) // => true
console.log(result.isArrow) // => true
console.log(result.isNamed) // => false
console.log(result.isAnonymous) // => true
```

**[back to top](#thetop)**

### Result
> In the result object you have `name`, `args`, `params`, `body` and few hidden properties
that can be useful to determine what the function is - arrow, regular, async/await or generator.

* `name` **{String|null}**: name of the passed function or `null` if anonymous
* `args` **{Array}**: arguments of the function
* `params` **{String}**: comma-separated list representing the `args`
* `defaults` **{Object}**: key/value pairs, useful when use ES2015 default arguments
* `body` **{String}**: actual body of the function, respects trailing newlines and whitespaces
* `isValid` **{Boolean}**: is the given value valid or not, that's because it never throws!
* `isAsync` **{Boolean}**: `true` if function is ES2015 async/await function
* `isArrow` **{Boolean}**: `true` if the function is arrow function
* `isNamed` **{Boolean}**: `true` if function has name, or `false` if is anonymous
* `isGenerator` **{Boolean}**: `true` if the function is ES2015 generator function
* `isAnonymous` **{Boolean}**: `true` if the function don't have name

**[back to top](#thetop)**

## Related
- [acorn](https://www.npmjs.com/package/acorn): ECMAScript parser | [homepage](https://github.com/acornjs/acorn "ECMAScript parser")
- [babylon](https://www.npmjs.com/package/babylon): A JavaScript parser | [homepage](https://babeljs.io/ "A JavaScript parser")
- [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.")
- [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")
- [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")
- [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.")

## Contributing
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue][open-issue-url].  
Please read the [Contributing Guide](./CONTRIBUTING.md) and [Code of Conduct](./CODE_OF_CONDUCT.md) documents for advices.  

## Author
- [github/tunnckoCore](https://github.com/tunnckoCore)
- [twitter/tunnckoCore](https://twitter.com/tunnckoCore)
- [codementor/tunnckoCore](https://codementor.io/tunnckoCore)

## License
Copyright © 2016, 2018, [Charlike Mike Reagent](https://i.am.charlike.online). Released under the [MIT License](LICENSE).

***

_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on March 05, 2018._  
_Project automation and management with [hela][] task framework._

[acorn]: https://github.com/acornjs/acorn
[babylon]: https://babeljs.io/
[charlike-cli]: https://github.com/tunnckoCore/charlike-cli
[charlike]: https://github.com/tunnckoCore/charlike
[define-property]: https://github.com/jonschlinkert/define-property
[espree]: https://github.com/eslint/espree
[execa]: https://github.com/sindresorhus/execa
[function-arguments]: https://github.com/tunnckocore/function-arguments
[hela]: https://github.com/tunnckoCore/hela
[new-release]: https://github.com/tunnckoCore/new-release

<!-- Heading badges -->
[npmv-url]: https://www.npmjs.com/package/parse-function
[npmv-img]: https://img.shields.io/npm/v/parse-function.svg?label=npm%20version

[github-release-url]: https://github.com/tunnckoCore/parse-function/releases/latest
[github-release-img]: https://img.shields.io/github/release/tunnckoCore/parse-function.svg?label=github%20release

[license-url]: https://github.com/tunnckoCore/parse-function/blob/master/LICENSE
[license-img]: https://img.shields.io/badge/license-MIT-blue.svg
<!-- [license-img]: https://img.shields.io/badge/license-tunnckoCore_1%2E0-blue.svg -->

<!-- Front line badges -->
[bithound-score-url]: https://www.bithound.io/github/tunnckoCore/parse-function
[bithound-score-img]: https://www.bithound.io/github/tunnckoCore/parse-function/badges/score.svg

[bithound-code-url]: https://www.bithound.io/github/tunnckoCore/parse-function
[bithound-code-img]: https://www.bithound.io/github/tunnckoCore/parse-function/badges/code.svg

[standard-url]: https://github.com/airbnb/javascript
[standard-img]: https://img.shields.io/badge/code_style-airbnb-brightgreen.svg

[circleci-url]: https://circleci.com/gh/tunnckoCoreLabs/parse-function/tree/master
[circleci-img]: https://img.shields.io/circleci/project/github/tunnckoCoreLabs/parse-function/master.svg

[codecov-url]: https://codecov.io/gh/tunnckoCoreLabs/parse-function
[codecov-img]: https://img.shields.io/codecov/c/github/tunnckoCoreLabs/parse-function/master.svg

[bithound-deps-url]: https://www.bithound.io/github/tunnckoCore/parse-function/dependencies/npm
[bithound-deps-img]: https://www.bithound.io/github/tunnckoCore/parse-function/badges/dependencies.svg

[dependencies-url]: https://david-dm.org/tunnckoCore/parse-function
[dependencies-img]: https://img.shields.io/david/tunnckoCore/parse-function.svg

<!-- Second front of badges -->
[prs-welcome-img]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg
[prs-welcome-url]: http://makeapullrequest.com

[prettier-url]: https://github.com/prettier/prettier
[prettier-img]: https://img.shields.io/badge/styled_with-prettier-f952a5.svg

[nodesecurity-url]: https://nodesecurity.io/orgs/tunnckocore/projects/42a5e14a-70da-49ee-86e7-d1f39ed08603/master
[nodesecurity-img]: https://nodesecurity.io/orgs/tunnckocore/projects/42a5e14a-70da-49ee-86e7-d1f39ed08603/badge
<!-- the original color of nsp: 
[nodesec-img]: https://img.shields.io/badge/nsp-no_known_vulns-35a9e0.svg -->

[ccommits-url]: https://conventionalcommits.org/
[ccommits-img]: https://img.shields.io/badge/conventional_commits-1.0.0-yellow.svg

[new-release-url]: https://github.com/tunnckoCore/new-release
[new-release-img]: https://img.shields.io/badge/semantically-released-05C5FF.svg

[nodeversion-url]: https://nodejs.org/en/download
[nodeversion-img]: https://img.shields.io/node/v/parse-function.svg

[renovate-url]: https://renovateapp.com
[renovate-img]: https://img.shields.io/badge/renovate-enabled-brightgreen.svg

<!-- Third badges line (After CodeSponsor.io ad) -->
[all-contributors-img]: https://img.shields.io/github/contributors/tunnckoCore/parse-function.svg?label=all%20contributors&colorB=ffa500

[tinyletter-url]: https://tinyletter.com/tunnckoCore
[tinyletter-img]: https://img.shields.io/badge/join-newsletter-9caaf8.svg
<!-- 
[paypal-donate-url]: https://paypal.me/tunnckoCore/10
[paypal-donate-img]: https://img.shields.io/badge/$-support-f47721.svg
 -->
[give-donate-url]: https://paypal.me/tunnckoCore/10
[give-donate-img]: https://img.shields.io/badge/give-donation-f47721.svg

[downloads-weekly-img]: https://img.shields.io/npm/dw/parse-function.svg
[downloads-monthly-img]: https://img.shields.io/npm/dm/parse-function.svg
[downloads-total-img]: https://img.shields.io/npm/dt/parse-function.svg

<!-- Miscellaneous -->
[share-love-url]: https://twitter.com/intent/tweet?text=https://github.com/tunnckoCore/parse-function&via=tunnckoCore
[share-love-img]: https://img.shields.io/badge/tweet-about-1da1f2.svg

[open-issue-url]: https://github.com/tunnckoCore/parse-function/issues/new
[highlighted-link]: https://ghub.now.sh/hela
[author-link]: https://i.am.charlike.online

[standard-url]: https://github.com/standard/standard
[standard-img]: https://img.shields.io/badge/code_style-standard-brightgreen.svg