|
1 |
| -/** |
2 |
| - * Utilities |
3 |
| - */ |
| 1 | +/* eslint-disable node/file-extension-in-import, import/extensions */ |
4 | 2 |
|
5 |
| -import utils from './utils'; |
| 3 | +import arrayify from 'arrify'; |
| 4 | +import { parse as babelParse } from '@babel/parser'; |
6 | 5 |
|
7 |
| -/** |
8 |
| - * Core plugins |
9 |
| - */ |
| 6 | +import { setDefaults, getNode } from './utils.js'; |
| 7 | +import basePlugin from './plugins/initial.js'; |
10 | 8 |
|
11 |
| -import initial from './plugins/initial'; |
| 9 | +// eslint-disable-next-line import/prefer-default-export |
| 10 | +export function parseFunction(code, options) { |
| 11 | + const opts = { parse: babelParse, ...options }; |
| 12 | + const result = setDefaults(code); |
12 | 13 |
|
13 |
| -/** |
14 |
| - * > Initializes with optional `opts` object which is passed directly |
15 |
| - * to the desired parser and returns an object |
16 |
| - * with `.use` and `.parse` methods. The default parse which |
17 |
| - * is used is [babylon][]'s `.parseExpression` method from `v7`. |
18 |
| - * |
19 |
| - * ```js |
20 |
| - * const parseFunction = require('parse-function') |
21 |
| - * |
22 |
| - * const app = parseFunction({ |
23 |
| - * ecmaVersion: 2017 |
24 |
| - * }) |
25 |
| - * |
26 |
| - * const fixtureFn = (a, b, c) => { |
27 |
| - * a = b + c |
28 |
| - * return a + 2 |
29 |
| - * } |
30 |
| - * |
31 |
| - * const result = app.parse(fixtureFn) |
32 |
| - * console.log(result) |
33 |
| - * |
34 |
| - * // see more |
35 |
| - * console.log(result.name) // => null |
36 |
| - * console.log(result.isNamed) // => false |
37 |
| - * console.log(result.isArrow) // => true |
38 |
| - * console.log(result.isAnonymous) // => true |
39 |
| - * |
40 |
| - * // array of names of the arguments |
41 |
| - * console.log(result.args) // => ['a', 'b', 'c'] |
42 |
| - * |
43 |
| - * // comma-separated names of the arguments |
44 |
| - * console.log(result.params) // => 'a, b, c' |
45 |
| - * ``` |
46 |
| - * |
47 |
| - * @param {Object} `opts` optional, merged with options passed to `.parse` method |
48 |
| - * @return {Object} `app` object with `.use` and `.parse` methods |
49 |
| - * @name parseFunction |
50 |
| - * @api public |
51 |
| - */ |
52 |
| -export default function parseFunction(opts = {}) { |
53 |
| - const plugins = []; |
54 |
| - const app = { |
55 |
| - /** |
56 |
| - * > Parse a given `code` and returns a `result` object |
57 |
| - * with useful properties - such as `name`, `body` and `args`. |
58 |
| - * By default it uses Babylon parser, but you can switch it by |
59 |
| - * passing `options.parse` - for example `options.parse: acorn.parse`. |
60 |
| - * In the below example will show how to use `acorn` parser, instead |
61 |
| - * of the default one. |
62 |
| - * |
63 |
| - * ```js |
64 |
| - * const acorn = require('acorn') |
65 |
| - * const parseFn = require('parse-function') |
66 |
| - * const app = parseFn() |
67 |
| - * |
68 |
| - * const fn = function foo (bar, baz) { return bar * baz } |
69 |
| - * const result = app.parse(fn, { |
70 |
| - * parse: acorn.parse, |
71 |
| - * ecmaVersion: 2017 |
72 |
| - * }) |
73 |
| - * |
74 |
| - * console.log(result.name) // => 'foo' |
75 |
| - * console.log(result.args) // => ['bar', 'baz'] |
76 |
| - * console.log(result.body) // => ' return bar * baz ' |
77 |
| - * console.log(result.isNamed) // => true |
78 |
| - * console.log(result.isArrow) // => false |
79 |
| - * console.log(result.isAnonymous) // => false |
80 |
| - * console.log(result.isGenerator) // => false |
81 |
| - * ``` |
82 |
| - * |
83 |
| - * @param {Function|String} `code` any kind of function or string to be parsed |
84 |
| - * @param {Object} `options` directly passed to the parser - babylon, acorn, espree |
85 |
| - * @param {Function} `options.parse` by default `babylon.parseExpression`, |
86 |
| - * all `options` are passed as second argument |
87 |
| - * to that provided function |
88 |
| - * @return {Object} `result` see [result section](#result) for more info |
89 |
| - * @name .parse |
90 |
| - * @api public |
91 |
| - */ |
92 |
| - parse(code, options) { |
93 |
| - const result = utils.setDefaults(code); |
| 14 | + if (!result.isValid) { |
| 15 | + return result; |
| 16 | + } |
94 | 17 |
|
95 |
| - if (!result.isValid) { |
96 |
| - return result; |
97 |
| - } |
| 18 | + const isFunction = result.value.startsWith('function'); |
| 19 | + const isAsyncFn = result.value.startsWith('async function'); |
| 20 | + const isAsync = result.value.startsWith('async'); |
| 21 | + const isArrow = result.value.includes('=>'); |
| 22 | + const isAsyncArrow = isAsync && isArrow; |
98 | 23 |
|
99 |
| - const mergedOptions = { ...opts, ...options }; |
| 24 | + const isMethod = /^\*?.+\([\s\S\w\W]*\)\s*\{/i.test(result.value); |
100 | 25 |
|
101 |
| - const isFunction = result.value.startsWith('function'); |
102 |
| - const isAsyncFn = result.value.startsWith('async function'); |
103 |
| - const isAsync = result.value.startsWith('async'); |
104 |
| - const isArrow = result.value.includes('=>'); |
105 |
| - const isAsyncArrow = isAsync && isArrow; |
| 26 | + if (!(isFunction || isAsyncFn || isAsyncArrow) && isMethod) { |
| 27 | + result.value = `{ ${result.value} }`; |
| 28 | + } |
106 | 29 |
|
107 |
| - const isMethod = /^\*?.+\([\s\S\w\W]*\)\s*\{/i.test(result.value); |
| 30 | + const node = getNode(result, opts); |
| 31 | + const plugins = arrayify(opts.plugins); |
108 | 32 |
|
109 |
| - if (!(isFunction || isAsyncFn || isAsyncArrow) && isMethod) { |
110 |
| - result.value = `{ ${result.value} }`; |
111 |
| - } |
| 33 | + return [basePlugin, ...plugins].filter(Boolean).reduce((res, fn) => { |
| 34 | + const pluginResult = fn(node, { ...res }) || res; |
112 | 35 |
|
113 |
| - const node = utils.getNode(result, mergedOptions); |
114 |
| - return plugins.reduce((res, fn) => fn(node, res) || res, result); |
115 |
| - }, |
116 |
| - |
117 |
| - /** |
118 |
| - * > Add a plugin `fn` function for extending the API or working on the |
119 |
| - * AST nodes. The `fn` is immediately invoked and passed |
120 |
| - * with `app` argument which is instance of `parseFunction()` call. |
121 |
| - * That `fn` may return another function that |
122 |
| - * accepts `(node, result)` signature, where `node` is an AST node |
123 |
| - * and `result` is an object which will be returned [result](#result) |
124 |
| - * from the `.parse` method. This retuned function is called on each |
125 |
| - * node only when `.parse` method is called. |
126 |
| - * |
127 |
| - * _See [Plugins Architecture](#plugins-architecture) section._ |
128 |
| - * |
129 |
| - * ```js |
130 |
| - * // plugin extending the `app` |
131 |
| - * app.use((app) => { |
132 |
| - * app.define(app, 'hello', (place) => `Hello ${place}!`) |
133 |
| - * }) |
134 |
| - * |
135 |
| - * const hi = app.hello('World') |
136 |
| - * console.log(hi) // => 'Hello World!' |
137 |
| - * |
138 |
| - * // or plugin that works on AST nodes |
139 |
| - * app.use((app) => (node, result) => { |
140 |
| - * if (node.type === 'ArrowFunctionExpression') { |
141 |
| - * result.thatIsArrow = true |
142 |
| - * } |
143 |
| - * return result |
144 |
| - * }) |
145 |
| - * |
146 |
| - * const result = app.parse((a, b) => (a + b + 123)) |
147 |
| - * console.log(result.name) // => null |
148 |
| - * console.log(result.isArrow) // => true |
149 |
| - * console.log(result.thatIsArrow) // => true |
150 |
| - * |
151 |
| - * const result = app.parse(function foo () { return 123 }) |
152 |
| - * console.log(result.name) // => 'foo' |
153 |
| - * console.log(result.isArrow) // => false |
154 |
| - * console.log(result.thatIsArrow) // => undefined |
155 |
| - * ``` |
156 |
| - * |
157 |
| - * @param {Function} `fn` plugin to be called |
158 |
| - * @return {Object} `app` instance for chaining |
159 |
| - * @name .use |
160 |
| - * @api public |
161 |
| - */ |
162 |
| - use(fn) { |
163 |
| - const ret = fn(app); |
164 |
| - if (typeof ret === 'function') { |
165 |
| - plugins.push(ret); |
166 |
| - } |
167 |
| - return app; |
168 |
| - }, |
169 |
| - |
170 |
| - /** |
171 |
| - * > Define a non-enumerable property on an object. Just |
172 |
| - * a convenience mirror of the [define-property][] library, |
173 |
| - * so check out its docs. Useful to be used in plugins. |
174 |
| - * |
175 |
| - * ```js |
176 |
| - * const parseFunction = require('parse-function') |
177 |
| - * const app = parseFunction() |
178 |
| - * |
179 |
| - * // use it like `define-property` lib |
180 |
| - * const obj = {} |
181 |
| - * app.define(obj, 'hi', 'world') |
182 |
| - * console.log(obj) // => { hi: 'world' } |
183 |
| - * |
184 |
| - * // or define a custom plugin that adds `.foo` property |
185 |
| - * // to the end result, returned from `app.parse` |
186 |
| - * app.use((app) => { |
187 |
| - * return (node, result) => { |
188 |
| - * // this function is called |
189 |
| - * // only when `.parse` is called |
190 |
| - * |
191 |
| - * app.define(result, 'foo', 123) |
192 |
| - * |
193 |
| - * return result |
194 |
| - * } |
195 |
| - * }) |
196 |
| - * |
197 |
| - * // fixture function to be parsed |
198 |
| - * const asyncFn = async (qux) => { |
199 |
| - * const bar = await Promise.resolve(qux) |
200 |
| - * return bar |
201 |
| - * } |
202 |
| - * |
203 |
| - * const result = app.parse(asyncFn) |
204 |
| - * |
205 |
| - * console.log(result.name) // => null |
206 |
| - * console.log(result.foo) // => 123 |
207 |
| - * console.log(result.args) // => ['qux'] |
208 |
| - * |
209 |
| - * console.log(result.isAsync) // => true |
210 |
| - * console.log(result.isArrow) // => true |
211 |
| - * console.log(result.isNamed) // => false |
212 |
| - * console.log(result.isAnonymous) // => true |
213 |
| - * ``` |
214 |
| - * |
215 |
| - * @param {Object} `obj` the object on which to define the property |
216 |
| - * @param {String} `prop` the name of the property to be defined or modified |
217 |
| - * @param {Any} `val` the descriptor for the property being defined or modified |
218 |
| - * @return {Object} `obj` the passed object, but modified |
219 |
| - * @name .define |
220 |
| - * @api public |
221 |
| - */ |
222 |
| - define: utils.define, |
223 |
| - }; |
224 |
| - |
225 |
| - app.use(initial); |
226 |
| - |
227 |
| - return app; |
| 36 | + return pluginResult; |
| 37 | + }, result); |
228 | 38 | }
|
0 commit comments