├── .editorconfig
├── .gitattributes
├── .gitignore
├── .jscsrc
├── README.md
├── fp
└── _mapping.js
├── known_problems.md
├── lib
└── doc
│ ├── apply-fp-mapping
│ ├── common.js
│ ├── description.js
│ ├── example.js
│ ├── index.js
│ └── parameters.js
│ └── build.js
├── lodash.js
├── package.json
└── test
└── test-fp-doc.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | charset = utf-8
8 | end_of_line = lf
9 | indent_size = 2
10 | indent_style = space
11 | insert_final_newline = true
12 | trim_trailing_whitespace = true
13 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.custom.*
3 | *.log
4 | *.map
5 | lodash.compat.min.js
6 | coverage
7 | node_modules
8 | /doc/
9 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "maxErrors": "2000",
3 | "maximumLineLength": {
4 | "value": 180,
5 | "allExcept": ["comments", "functionSignature", "regex"]
6 | },
7 | "requireCurlyBraces": [
8 | "if",
9 | "else",
10 | "for",
11 | "while",
12 | "do",
13 | "try",
14 | "catch"
15 | ],
16 | "requireOperatorBeforeLineBreak": [
17 | "=",
18 | "+",
19 | "-",
20 | "/",
21 | "*",
22 | "==",
23 | "===",
24 | "!=",
25 | "!==",
26 | ">",
27 | ">=",
28 | "<",
29 | "<="
30 | ],
31 | "requireSpaceAfterKeywords": [
32 | "if",
33 | "else",
34 | "for",
35 | "while",
36 | "do",
37 | "switch",
38 | "return",
39 | "try",
40 | "catch"
41 | ],
42 | "requireSpaceBeforeBinaryOperators": [
43 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
44 | "&=", "|=", "^=", "+=",
45 |
46 | "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
47 | "|", "^", "&&", "||", "===", "==", ">=",
48 | "<=", "<", ">", "!=", "!=="
49 | ],
50 | "requireSpacesInFunctionExpression": {
51 | "beforeOpeningCurlyBrace": true
52 | },
53 | "requireCamelCaseOrUpperCaseIdentifiers": true,
54 | "requireDotNotation": { "allExcept": ["keywords"] },
55 | "requireEarlyReturn": true,
56 | "requireLineFeedAtFileEnd": true,
57 | "requireSemicolons": true,
58 | "requireSpaceAfterBinaryOperators": true,
59 | "requireSpacesInConditionalExpression": true,
60 | "requireSpaceBeforeObjectValues": true,
61 | "requireSpaceBeforeBlockStatements": true,
62 | "requireSpacesInForStatement": true,
63 |
64 | "validateIndentation": 2,
65 | "validateParameterSeparator": ", ",
66 | "validateQuoteMarks": { "mark": "'", "escape": true },
67 |
68 | "disallowSpacesInAnonymousFunctionExpression": {
69 | "beforeOpeningRoundBrace": true
70 | },
71 | "disallowSpacesInFunctionDeclaration": {
72 | "beforeOpeningRoundBrace": true
73 | },
74 | "disallowSpacesInFunctionExpression": {
75 | "beforeOpeningRoundBrace": true
76 | },
77 | "disallowKeywords": ["with"],
78 | "disallowMixedSpacesAndTabs": true,
79 | "disallowMultipleLineBreaks": true,
80 | "disallowNewlineBeforeBlockStatements": true,
81 | "disallowSpaceAfterObjectKeys": true,
82 | "disallowSpaceAfterPrefixUnaryOperators": true,
83 | "disallowSpacesInCallExpression": true,
84 | "disallowSpacesInsideArrayBrackets": true,
85 | "disallowSpacesInsideParentheses": true,
86 | "disallowTrailingWhitespace": true,
87 |
88 | "jsDoc": {
89 | "checkRedundantAccess": true,
90 | "checkTypes": true,
91 | "requireNewlineAfterDescription": true,
92 | "requireParamDescription": true,
93 | "requireParamTypes": true,
94 | "requireReturnTypes": true
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # `lodash/fp` doc generator
2 |
3 | This project aims to generate proper docs for `Lodash`'s functional programming flavor, aka `lodash/fp`.
4 | The end goal would be generate a doc similar to the [official documentation](https://lodash.com/docs). The result can be found **[HERE](https://gist.github.com/jfmengels/6b973b69c491375117dc)**.
5 |
6 | ## Why
7 |
8 | Lodash FP aims to combine the great functionality given by the library and the ideas of functional programming. Only, this behavior is [documented]([FP Guide](https://github.com/lodash/lodash/wiki/FP-Guide)) in a manner unbefitting of what users are used to with the [official documentation](https://lodash.com/docs).
9 |
10 | ## What changed in FP?
11 |
12 | - The order of arguments has been changed, in particular the data argument has been moved to the last position
13 | - No methods mutate the arguments
14 | - Some methods are curried
15 | - Some methods have their arguments capped, meaning some arguments have disappeared
16 | - Some methods with `...values` have that argument now in an array
17 |
18 | ## What now
19 |
20 | Lodash's sources are generated using [`docdown`](https://github.com/jdalton/docdown). What it does is read the source files, extract the JSDoc comments, then generate a Markdown file. By overriding Docdown's behavior, we can change the documentation too, in the methods' signature and example:
21 | - change the order of arguments
22 | - remove now ignored arguments
23 | - group `...values` arguments into an array argument
24 | - remove calls to `console.log()` that aim to demonstrate a value was mutated (only in example)
25 | - inject optional arguments that have become mandatory using their default value (only in example)
26 |
27 | ## Run it
28 |
29 | ```
30 | npm i
31 | npm run doc:fp
32 | // now open doc/fp.md!
33 | ```
34 |
--------------------------------------------------------------------------------
/fp/_mapping.js:
--------------------------------------------------------------------------------
1 | /** Used to map aliases to their real names. */
2 | exports.aliasToReal = {
3 |
4 | // Lodash aliases.
5 | 'each': 'forEach',
6 | 'eachRight': 'forEachRight',
7 | 'entries': 'toPairs',
8 | 'entriesIn': 'toPairsIn',
9 | 'extend': 'assignIn',
10 | 'extendAll': 'assignInAll',
11 | 'extendAllWith': 'assignInAllWith',
12 | 'extendWith': 'assignInWith',
13 | 'first': 'head',
14 |
15 | // Methods that are curried variants of others.
16 | 'conforms': 'conformsTo',
17 | 'matches': 'isMatch',
18 | 'property': 'get',
19 |
20 | // Ramda aliases.
21 | '__': 'placeholder',
22 | 'F': 'stubFalse',
23 | 'T': 'stubTrue',
24 | 'all': 'every',
25 | 'allPass': 'overEvery',
26 | 'always': 'constant',
27 | 'any': 'some',
28 | 'anyPass': 'overSome',
29 | 'apply': 'spread',
30 | 'assoc': 'set',
31 | 'assocPath': 'set',
32 | 'complement': 'negate',
33 | 'compose': 'flowRight',
34 | 'contains': 'includes',
35 | 'dissoc': 'unset',
36 | 'dissocPath': 'unset',
37 | 'dropLast': 'dropRight',
38 | 'dropLastWhile': 'dropRightWhile',
39 | 'equals': 'isEqual',
40 | 'identical': 'eq',
41 | 'indexBy': 'keyBy',
42 | 'init': 'initial',
43 | 'invertObj': 'invert',
44 | 'juxt': 'over',
45 | 'omitAll': 'omit',
46 | 'nAry': 'ary',
47 | 'path': 'get',
48 | 'pathEq': 'matchesProperty',
49 | 'pathOr': 'getOr',
50 | 'paths': 'at',
51 | 'pickAll': 'pick',
52 | 'pipe': 'flow',
53 | 'pluck': 'map',
54 | 'prop': 'get',
55 | 'propEq': 'matchesProperty',
56 | 'propOr': 'getOr',
57 | 'props': 'at',
58 | 'symmetricDifference': 'xor',
59 | 'symmetricDifferenceBy': 'xorBy',
60 | 'symmetricDifferenceWith': 'xorWith',
61 | 'takeLast': 'takeRight',
62 | 'takeLastWhile': 'takeRightWhile',
63 | 'unapply': 'rest',
64 | 'unnest': 'flatten',
65 | 'useWith': 'overArgs',
66 | 'where': 'conformsTo',
67 | 'whereEq': 'isMatch',
68 | 'zipObj': 'zipObject'
69 | };
70 |
71 | /** Used to map ary to method names. */
72 | exports.aryMethod = {
73 | '1': [
74 | 'assignAll', 'assignInAll', 'attempt', 'castArray', 'ceil', 'create',
75 | 'curry', 'curryRight', 'defaultsAll', 'defaultsDeepAll', 'floor', 'flow',
76 | 'flowRight', 'fromPairs', 'invert', 'iteratee', 'memoize', 'method', 'mergeAll',
77 | 'methodOf', 'mixin', 'nthArg', 'over', 'overEvery', 'overSome','rest', 'reverse',
78 | 'round', 'runInContext', 'spread', 'template', 'trim', 'trimEnd', 'trimStart',
79 | 'uniqueId', 'words', 'zipAll'
80 | ],
81 | '2': [
82 | 'add', 'after', 'ary', 'assign', 'assignAllWith', 'assignIn', 'assignInAllWith',
83 | 'at', 'before', 'bind', 'bindAll', 'bindKey', 'chunk', 'cloneDeepWith',
84 | 'cloneWith', 'concat', 'conformsTo', 'countBy', 'curryN', 'curryRightN',
85 | 'debounce', 'defaults', 'defaultsDeep', 'defaultTo', 'delay', 'difference',
86 | 'divide', 'drop', 'dropRight', 'dropRightWhile', 'dropWhile', 'endsWith', 'eq',
87 | 'every', 'filter', 'find', 'findIndex', 'findKey', 'findLast', 'findLastIndex',
88 | 'findLastKey', 'flatMap', 'flatMapDeep', 'flattenDepth', 'forEach',
89 | 'forEachRight', 'forIn', 'forInRight', 'forOwn', 'forOwnRight', 'get',
90 | 'groupBy', 'gt', 'gte', 'has', 'hasIn', 'includes', 'indexOf', 'intersection',
91 | 'invertBy', 'invoke', 'invokeMap', 'isEqual', 'isMatch', 'join', 'keyBy',
92 | 'lastIndexOf', 'lt', 'lte', 'map', 'mapKeys', 'mapValues', 'matchesProperty',
93 | 'maxBy', 'meanBy', 'merge', 'mergeAllWith', 'minBy', 'multiply', 'nth', 'omit',
94 | 'omitBy', 'overArgs', 'pad', 'padEnd', 'padStart', 'parseInt', 'partial',
95 | 'partialRight', 'partition', 'pick', 'pickBy', 'propertyOf', 'pull', 'pullAll',
96 | 'pullAt', 'random', 'range', 'rangeRight', 'rearg', 'reject', 'remove',
97 | 'repeat', 'restFrom', 'result', 'sampleSize', 'some', 'sortBy', 'sortedIndex',
98 | 'sortedIndexOf', 'sortedLastIndex', 'sortedLastIndexOf', 'sortedUniqBy',
99 | 'split', 'spreadFrom', 'startsWith', 'subtract', 'sumBy', 'take', 'takeRight',
100 | 'takeRightWhile', 'takeWhile', 'tap', 'throttle', 'thru', 'times', 'trimChars',
101 | 'trimCharsEnd', 'trimCharsStart', 'truncate', 'union', 'uniqBy', 'uniqWith',
102 | 'unset', 'unzipWith', 'without', 'wrap', 'xor', 'zip', 'zipObject',
103 | 'zipObjectDeep'
104 | ],
105 | '3': [
106 | 'assignInWith', 'assignWith', 'clamp', 'differenceBy', 'differenceWith',
107 | 'findFrom', 'findIndexFrom', 'findLastFrom', 'findLastIndexFrom', 'getOr',
108 | 'includesFrom', 'indexOfFrom', 'inRange', 'intersectionBy', 'intersectionWith',
109 | 'invokeArgs', 'invokeArgsMap', 'isEqualWith', 'isMatchWith', 'flatMapDepth',
110 | 'lastIndexOfFrom', 'mergeWith', 'orderBy', 'padChars', 'padCharsEnd',
111 | 'padCharsStart', 'pullAllBy', 'pullAllWith', 'rangeStep', 'rangeStepRight',
112 | 'reduce', 'reduceRight', 'replace', 'set', 'slice', 'sortedIndexBy',
113 | 'sortedLastIndexBy', 'transform', 'unionBy', 'unionWith', 'update', 'xorBy',
114 | 'xorWith', 'zipWith'
115 | ],
116 | '4': [
117 | 'fill', 'setWith', 'updateWith'
118 | ]
119 | };
120 |
121 | /** Used to map ary to rearg configs. */
122 | exports.aryRearg = {
123 | '2': [1, 0],
124 | '3': [2, 0, 1],
125 | '4': [3, 2, 0, 1]
126 | };
127 |
128 | /** Used to map method names to their iteratee ary. */
129 | exports.iterateeAry = {
130 | 'dropRightWhile': 1,
131 | 'dropWhile': 1,
132 | 'every': 1,
133 | 'filter': 1,
134 | 'find': 1,
135 | 'findFrom': 1,
136 | 'findIndex': 1,
137 | 'findIndexFrom': 1,
138 | 'findKey': 1,
139 | 'findLast': 1,
140 | 'findLastFrom': 1,
141 | 'findLastIndex': 1,
142 | 'findLastIndexFrom': 1,
143 | 'findLastKey': 1,
144 | 'flatMap': 1,
145 | 'flatMapDeep': 1,
146 | 'flatMapDepth': 1,
147 | 'forEach': 1,
148 | 'forEachRight': 1,
149 | 'forIn': 1,
150 | 'forInRight': 1,
151 | 'forOwn': 1,
152 | 'forOwnRight': 1,
153 | 'map': 1,
154 | 'mapKeys': 1,
155 | 'mapValues': 1,
156 | 'partition': 1,
157 | 'reduce': 2,
158 | 'reduceRight': 2,
159 | 'reject': 1,
160 | 'remove': 1,
161 | 'some': 1,
162 | 'takeRightWhile': 1,
163 | 'takeWhile': 1,
164 | 'times': 1,
165 | 'transform': 2
166 | };
167 |
168 | /** Used to map method names to iteratee rearg configs. */
169 | exports.iterateeRearg = {
170 | 'mapKeys': [1],
171 | 'reduceRight': [1, 0]
172 | };
173 |
174 | /** Used to map method names to rearg configs. */
175 | exports.methodRearg = {
176 | 'assignInAllWith': [1, 0],
177 | 'assignInWith': [1, 2, 0],
178 | 'assignAllWith': [1, 0],
179 | 'assignWith': [1, 2, 0],
180 | 'differenceBy': [1, 2, 0],
181 | 'differenceWith': [1, 2, 0],
182 | 'getOr': [2, 1, 0],
183 | 'intersectionBy': [1, 2, 0],
184 | 'intersectionWith': [1, 2, 0],
185 | 'isEqualWith': [1, 2, 0],
186 | 'isMatchWith': [2, 1, 0],
187 | 'mergeAllWith': [1, 0],
188 | 'mergeWith': [1, 2, 0],
189 | 'padChars': [2, 1, 0],
190 | 'padCharsEnd': [2, 1, 0],
191 | 'padCharsStart': [2, 1, 0],
192 | 'pullAllBy': [2, 1, 0],
193 | 'pullAllWith': [2, 1, 0],
194 | 'rangeStep': [1, 2, 0],
195 | 'rangeStepRight': [1, 2, 0],
196 | 'setWith': [3, 1, 2, 0],
197 | 'sortedIndexBy': [2, 1, 0],
198 | 'sortedLastIndexBy': [2, 1, 0],
199 | 'unionBy': [1, 2, 0],
200 | 'unionWith': [1, 2, 0],
201 | 'updateWith': [3, 1, 2, 0],
202 | 'xorBy': [1, 2, 0],
203 | 'xorWith': [1, 2, 0],
204 | 'zipWith': [1, 2, 0]
205 | };
206 |
207 | /** Used to map method names to spread configs. */
208 | exports.methodSpread = {
209 | 'assignAll': { 'start': 0 },
210 | 'assignAllWith': { 'start': 0 },
211 | 'assignInAll': { 'start': 0 },
212 | 'assignInAllWith': { 'start': 0 },
213 | 'defaultsAll': { 'start': 0 },
214 | 'defaultsDeepAll': { 'start': 0 },
215 | 'invokeArgs': { 'start': 2 },
216 | 'invokeArgsMap': { 'start': 2 },
217 | 'mergeAll': { 'start': 0 },
218 | 'mergeAllWith': { 'start': 0 },
219 | 'partial': { 'start': 1 },
220 | 'partialRight': { 'start': 1 },
221 | 'without': { 'start': 1 },
222 | 'zipAll': { 'start': 0 }
223 | };
224 |
225 | /** Used to identify methods which mutate arrays or objects. */
226 | exports.mutate = {
227 | 'array': {
228 | 'fill': true,
229 | 'pull': true,
230 | 'pullAll': true,
231 | 'pullAllBy': true,
232 | 'pullAllWith': true,
233 | 'pullAt': true,
234 | 'remove': true,
235 | 'reverse': true
236 | },
237 | 'object': {
238 | 'assign': true,
239 | 'assignAll': true,
240 | 'assignAllWith': true,
241 | 'assignIn': true,
242 | 'assignInAll': true,
243 | 'assignInAllWith': true,
244 | 'assignInWith': true,
245 | 'assignWith': true,
246 | 'defaults': true,
247 | 'defaultsAll': true,
248 | 'defaultsDeep': true,
249 | 'defaultsDeepAll': true,
250 | 'merge': true,
251 | 'mergeAll': true,
252 | 'mergeAllWith': true,
253 | 'mergeWith': true,
254 | },
255 | 'set': {
256 | 'set': true,
257 | 'setWith': true,
258 | 'unset': true,
259 | 'update': true,
260 | 'updateWith': true
261 | }
262 | };
263 |
264 | /** Used to map real names to their aliases. */
265 | exports.realToAlias = (function() {
266 | var hasOwnProperty = Object.prototype.hasOwnProperty,
267 | object = exports.aliasToReal,
268 | result = {};
269 |
270 | for (var key in object) {
271 | var value = object[key];
272 | if (hasOwnProperty.call(result, value)) {
273 | result[value].push(key);
274 | } else {
275 | result[value] = [key];
276 | }
277 | }
278 | return result;
279 | }());
280 |
281 | /** Used to map method names to other names. */
282 | exports.remap = {
283 | 'assignAll': 'assign',
284 | 'assignAllWith': 'assignWith',
285 | 'assignInAll': 'assignIn',
286 | 'assignInAllWith': 'assignInWith',
287 | 'curryN': 'curry',
288 | 'curryRightN': 'curryRight',
289 | 'defaultsAll': 'defaults',
290 | 'defaultsDeepAll': 'defaultsDeep',
291 | 'findFrom': 'find',
292 | 'findIndexFrom': 'findIndex',
293 | 'findLastFrom': 'findLast',
294 | 'findLastIndexFrom': 'findLastIndex',
295 | 'getOr': 'get',
296 | 'includesFrom': 'includes',
297 | 'indexOfFrom': 'indexOf',
298 | 'invokeArgs': 'invoke',
299 | 'invokeArgsMap': 'invokeMap',
300 | 'lastIndexOfFrom': 'lastIndexOf',
301 | 'mergeAll': 'merge',
302 | 'mergeAllWith': 'mergeWith',
303 | 'padChars': 'pad',
304 | 'padCharsEnd': 'padEnd',
305 | 'padCharsStart': 'padStart',
306 | 'propertyOf': 'get',
307 | 'rangeStep': 'range',
308 | 'rangeStepRight': 'rangeRight',
309 | 'restFrom': 'rest',
310 | 'spreadFrom': 'spread',
311 | 'trimChars': 'trim',
312 | 'trimCharsEnd': 'trimEnd',
313 | 'trimCharsStart': 'trimStart',
314 | 'zipAll': 'zip'
315 | };
316 |
317 | /** Used to track methods that skip fixing their arity. */
318 | exports.skipFixed = {
319 | 'castArray': true,
320 | 'flow': true,
321 | 'flowRight': true,
322 | 'iteratee': true,
323 | 'mixin': true,
324 | 'rearg': true,
325 | 'runInContext': true
326 | };
327 |
328 | /** Used to track methods that skip rearranging arguments. */
329 | exports.skipRearg = {
330 | 'add': true,
331 | 'assign': true,
332 | 'assignIn': true,
333 | 'bind': true,
334 | 'bindKey': true,
335 | 'concat': true,
336 | 'difference': true,
337 | 'divide': true,
338 | 'eq': true,
339 | 'gt': true,
340 | 'gte': true,
341 | 'isEqual': true,
342 | 'lt': true,
343 | 'lte': true,
344 | 'matchesProperty': true,
345 | 'merge': true,
346 | 'multiply': true,
347 | 'overArgs': true,
348 | 'partial': true,
349 | 'partialRight': true,
350 | 'propertyOf': true,
351 | 'random': true,
352 | 'range': true,
353 | 'rangeRight': true,
354 | 'subtract': true,
355 | 'zip': true,
356 | 'zipObject': true,
357 | 'zipObjectDeep': true
358 | };
359 |
--------------------------------------------------------------------------------
/known_problems.md:
--------------------------------------------------------------------------------
1 | ## Incorrect examples
2 |
3 | ### concat
4 | Should
5 | ```js
6 | _.concat([1], [2, [3], [[4]]]);
7 | // => [ 1, 2, [ 3 ], [ [ 4 ] ] ]
8 | ```
9 | Does
10 | ```js
11 | // => [ 1, 2, [ 3 ], [ [ 4 ] ] ]
12 | ```
13 |
14 | ### indexOf
15 | https://gist.github.com/jfmengels/6b973b69c491375117dc#_indexofvalue-array
16 | ```js
17 | // Search from the `fromIndex`.
18 | _.indexOf([2, 2], [1, 2, 1, 2]);
19 | // => 3
20 | ```
21 | `fromIndex` param is ignored in fp, so it should not be grouped in an array. Even with that, the example would not return the expected result though.
22 |
23 | ### lastIndexOf
24 |
25 | Same as indexOf
26 |
27 | ### intersection
28 | ```js
29 | _.intersection([[4, 2], [1, 2]], [2, 1]);
30 | // => Expected [2]
31 | // => Getting []
32 | ```
33 | Intersection does not support more than 2 arrays
34 |
35 | ### union
36 |
37 | Same as intersection
38 |
39 | ### zip
40 |
41 | Same as intersection
42 |
43 | ### unzip
44 |
45 | Same as zip, because it is using zip in the example
46 |
47 | ### unzipWith
48 |
49 | Same as zip, because it is using zip in the example
50 |
51 | ### pullAt
52 | https://gist.github.com/jfmengels/6b973b69c491375117dc#_pullatindexes-array
53 | Too many console.logs removed and nothing remains...
54 |
55 | ### remove
56 |
57 | Same as pullAt
58 |
59 | ### reverse
60 | Working, but there are useless trailing comments
61 |
62 | ### without
63 | Issue filed https://github.com/lodash/lodash/issues/2122
64 |
65 | ### zipWith
66 |
67 | Has one argument too many. Could send a PR for it.
68 |
69 | ### invokeMap
70 | Too many args in example
71 |
72 | ### reduce
73 | Uses key in cb function, which is capped in FP.
74 |
75 | ### defer
76 | Uses defer with 2 args, which now only takes one
77 |
78 | ### now
79 | Uses defer with 2 args, which now only takes one
80 |
81 | ### ary
82 | Arguments are not switched as expected
83 |
84 | ### bind, bindKey
85 | `partials` param is ignored in fp, so it should not be grouped in an array. Even with that, the example would not return the expected result though.
86 |
87 |
88 |
89 | #### TODO
90 | Checked up to curry
91 |
--------------------------------------------------------------------------------
/lib/doc/apply-fp-mapping/common.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | Entry = require('docdown/lib/entry');
3 |
4 | var baseGetParams = Entry.prototype.getParams;
5 |
6 | // Function copied from docdown/lib/entry that is not exported.
7 | function getMultilineValue(string, tagName) {
8 | var prelude = tagName == 'description' ? '^ */\\*\\*(?: *\\n *\\* *)?' : ('^ *\\*[\\t ]*@' + _.escapeRegExp(tagName) + '\\b'),
9 | postlude = '(?=\\*\\s+\\@[a-z]|\\*/)',
10 | result = _.result(RegExp(prelude + '([\\s\\S]*?)' + postlude, 'gm').exec(string), 1, '');
11 |
12 | return _.trim(result.replace(RegExp('(?:^|\\n)[\\t ]*\\*[\\t ]' + (tagName == 'example' ? '?' : '*'), 'g'), '\n'));
13 |
14 | }
15 |
16 | // Function copied from docdown/lib/entry that is not exported.
17 | function hasTag(string, tagName) {
18 | tagName = tagName == '*' ? '\\w+' : _.escapeRegExp(tagName);
19 | return RegExp('^ *\\*[\\t ]*@' + tagName + '\\b', 'm').test(string);
20 | }
21 |
22 | /**
23 | * Return the new ary of a given function.
24 | *
25 | * @param {object} mapping Mapping object that defines the arity of all functions.
26 | * @param {String} name Name of the function associated to the call/function definition.
27 | * @param {boolean} wrapped Flag indicating whether method is wrapped. Will decrement ary if true.
28 | * @return {number} Ary of the function as an integer
29 | */
30 | function getMethodAry(mapping, name, wrapped) {
31 | var ary = _.find([1, 2, 3, 4], function(cap) {
32 | return _.includes(mapping.aryMethod[cap], name) && cap;
33 | });
34 | if (_.isNumber(ary) && wrapped) {
35 | return ary - 1;
36 | }
37 | return ary;
38 | }
39 |
40 | /**
41 | * Reorder `params` for a given function definition/call.
42 | *
43 | * @param {object} mapping Mapping object that defines if and how the `params` will be reordered.
44 | * @param {String} name Name of the function associated to the call/function definition.
45 | * @param {*[]} params Parameters/arguments to reorder.
46 | * @param {boolean} wrapped Flag indicating whether method is wrapped. Will decrement ary if true.
47 | * @returns {*[]} Reordered parameters/arguments.
48 | */
49 | function reorderParams(mapping, name, params, wrapped) {
50 | // Check if reordering is needed.
51 | if (!mapping || mapping.skipRearg[name]) {
52 | return params;
53 | }
54 | var reargOrder = mapping.methodRearg[name] || mapping.aryRearg[getMethodAry(mapping, name, wrapped)];
55 | if (!reargOrder) {
56 | return params;
57 | }
58 | // Reorder params.
59 | var newParams = [];
60 | reargOrder.forEach(function(newPosition, index) {
61 | newParams[newPosition] = params[index];
62 | });
63 | return newParams;
64 | }
65 |
66 | module.exports = {
67 | baseGetParams: baseGetParams,
68 | getMultilineValue: getMultilineValue,
69 | hasTag: hasTag,
70 | getMethodAry: getMethodAry,
71 | reorderParams: reorderParams
72 | };
73 |
--------------------------------------------------------------------------------
/lib/doc/apply-fp-mapping/description.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | j = require('jscodeshift'),
3 | Entry = require('docdown/lib/entry'),
4 | common = require('./common');
5 |
6 | var baseGetDesc = Entry.prototype.getDesc;
7 |
8 | var lineBreaksRegex = /
\n?$/g;
9 |
10 | /**
11 | * Return the `description` of the entry, only without the possible note about mutation.
12 | *
13 | * @returns {Function} Updated description.
14 | */
15 | function getReorderedExample() {
16 | var lines = baseGetDesc.call(this).split('\n');
17 |
18 | var indexOfLine = _.findIndex(lines, function(line) {
19 | return _.includes(line, 'mutate');
20 | });
21 |
22 | if (indexOfLine === -1) {
23 | return lines.join('\n');
24 | }
25 |
26 | var linesToDelete = 1;
27 | while (indexOfLine + linesToDelete < lines.length &&
28 | !lines[indexOfLine + linesToDelete].startsWith('
0), the node will be simplified.
54 | * If `defaultValue` references another argument, it will be replaced by the value of that argument.
55 | *
56 | * @param {object} j JSCodeShift object.
57 | * @param {ASTObject} object Object of the member expression.
58 | * @param {string} property Property of the member expression.
59 | * @return {ASTObject} AST node.
60 | */
61 | function memberExpressiontoASTNode(j, object, property) {
62 | var node = j.memberExpression(object, j.identifier(property));
63 | try {
64 | // Attempt to evaluate the value of the node to have simpler calls
65 | // [1, 2, 3, 4].length --> 4
66 | var evaluatedNode = eval(recast.print(node).code);
67 | return stringToASTNode(j, JSON.stringify(evaluatedNode));
68 | } catch (e) {
69 | return node;
70 | }
71 | }
72 |
73 | /**
74 | * Return a AST node representation of `defaultValue`.
75 | * If `defaultValue` references another argument, it will be replaced by the value of that argument.
76 | *
77 | * @param {object} j JSCodeShift object.
78 | * @param {string} defaultValue Value to convert.
79 | * @param {ASTObject[]} args Arguments given to the function.
80 | * @param {string[]} paramNames Name of the expected parameters.
81 | * @return {ASTObject} AST node representation of `defaultValue`.
82 | */
83 | function defaultValueToASTNode(j, defaultValue, args, paramNames) {
84 | // var endValue = replaceValueByArgValue(j, defaultValue, args, paramNames);
85 | var splitDefaultValue = defaultValue.split('.');
86 | var indexOfReferencedParam = paramNames.indexOf(splitDefaultValue[0]);
87 | if (indexOfReferencedParam !== -1) {
88 | if (splitDefaultValue.length > 1) {
89 | // defaultValue is probably of the type 'someArg.length'
90 | // Other more complicated cases could be handled but none exist as of this writing.
91 | return memberExpressiontoASTNode(j, args[indexOfReferencedParam], splitDefaultValue[1]);
92 | }
93 | return args[indexOfReferencedParam];
94 | }
95 | if (defaultValue === '{}') {
96 | return j.objectExpression([]);
97 | }
98 | return stringToASTNode(j, defaultValue);
99 | }
100 |
101 | /**
102 | * Same as _.map, but applied in reverse order.
103 | *
104 | * @param {Array} collection The collection to iterate over.
105 | * @param {Function} iteratee The function invoked per iteration.
106 | * @return {Array} Returns the new mapped array.
107 | */
108 | function mapRight(array, iteratee) {
109 | var res = [];
110 | var index = array.length;
111 | while (index--) {
112 | res = [iteratee(array[index], index)].concat(res);
113 | }
114 | return res;
115 | }
116 |
117 | /**
118 | * Return the list of arguments, augmented by the default value of the arguments that were ommitted.
119 | * The augmentation only happens when the method call is made without some of the optional arguments,
120 | * and when the arguments these optional arguments have become compulsory.
121 | * For a `function fn(a, b, c=0, d=b.length) { ... }` with an arity of 4,
122 | * when called with `args` [a, ['b']], returns [a, ['b'], 0, ['b'].length].
123 | * If possible, the value will be evaluated such that ̀`['b'].length` becomes `1`.
124 | *
125 | * @param {object} j JSCodeShift object.
126 | * @param {object} mapping Mapping object that defines if and how the arguments will be reordered.
127 | * @param {String} name Name of the function associated to the call/function definition.
128 | * @param {ASTObject[]} args Arguments to concatenate.
129 | * @param {string[][]} paramsDescription Description of the expected params.
130 | * @return {ASTObject[]} Args along with missing arguments.
131 | */
132 | function addMissingArguments(j, mapping, name, args, paramsDescription) {
133 | var ary = common.getMethodAry(mapping, name) || 1;
134 | if (ary <= args.length) {
135 | return args;
136 | }
137 | var paramNames = paramsDescription.map(paramName);
138 | var tmpArgs = _.clone(args);
139 | var newArgs = _.compact(mapRight(_.take(paramsDescription, ary), function(paramDescription, index) {
140 | if (index === tmpArgs.length - 1) {
141 | return tmpArgs.pop();
142 | }
143 | var defaultValue = getDefaultValue(paramDescription);
144 | if (defaultValue !== null) {
145 | return defaultValueToASTNode(j, defaultValue, args, paramNames);
146 | }
147 | return tmpArgs.pop();
148 | }));
149 |
150 | var nbMissingArgs = Math.max(0, ary - newArgs.length);
151 | var value = stringToASTNode(j, 'null');
152 | return newArgs.concat(_.times(nbMissingArgs, _.constant(value)));
153 | }
154 |
155 | /**
156 | * Concatenate arguments into an array of arguments.
157 | * For a `function fn(a, b, ...args) { ... }` with an arity of 3,
158 | * when called with `args` [a, b, c, d, e, f], returns [a, b, [c, d, e, f]].
159 | *
160 | * @param {object} j JSCodeShift object.
161 | * @param {object} mapping Mapping object that defines if and how the arguments will be reordered.
162 | * @param {String} name Name of the function associated to the call/function definition.
163 | * @param {ASTObject[]} args Arguments to concatenate.
164 | * @return {ASTObject[]} Concatenated arguments
165 | */
166 | function concatExtraArgs(j, mapping, name, args) {
167 | var ary = common.getMethodAry(mapping, name) || 1;
168 | if (args.length <= ary) {
169 | return args;
170 | }
171 |
172 | var concatenatedArgs = j.arrayExpression(_.takeRight(args, args.length - ary + 1));
173 | return _.take(args, ary - 1).concat(concatenatedArgs);
174 | }
175 |
176 | /**
177 | * Reorder the args in the example if needed, and eventually merges them when
178 | * the method is called with more args than the method's ary.
179 | *
180 | * @param {object} j JSCodeShift object.
181 | * @param {ASTObject} root AST representation of the example
182 | * @param {object} mapping Mapping object that defines if and how the arguments will be reordered.
183 | * @return {ASTObject} AST object where the arguments are reordered/merged
184 | */
185 | function reorderMethodArgs(j, root, mapping, paramsDescription) {
186 | root.find(j.CallExpression, { callee: { object: {name: '_' }}})
187 | .replaceWith(function(callExpr, i) {
188 | var value = callExpr.value;
189 | var name = value.callee.property.name;
190 | var argsIncludingMissingOnes = addMissingArguments(j, mapping, name, value.arguments, paramsDescription);
191 | var args = concatExtraArgs(j, mapping, name, argsIncludingMissingOnes);
192 | return j.callExpression(
193 | value.callee,
194 | common.reorderParams(mapping, name, args)
195 | );
196 | });
197 | }
198 |
199 | /**
200 | * Remove calls to `console.log` from `codeSample`.
201 | *
202 | * @param {string} codeSample string to remove the calls from.
203 | * @return {string} Updated code sample.
204 | */
205 | function removeConsoleLogs(codeSample) {
206 | return codeSample
207 | .split('\n')
208 | .filter(function(line) {
209 | return !line.startsWith('console.log');
210 | })
211 | .join('\n');
212 | }
213 |
214 | /**
215 | * Updates a code sample so that the arguments in the call are reordered according to `mapping`.
216 | *
217 | * @param {object} mapping Mapping object that defines if and how the arguments will be reordered.
218 | * @param {string} codeSample Code sample to update.
219 | * @returns {string} Updated code sample.
220 | */
221 | function reorderParamsInExample(mapping, codeSample, paramsDescription) {
222 | var root = j(removeConsoleLogs(codeSample));
223 | try {
224 | reorderMethodArgs(j, root, mapping, paramsDescription);
225 | } catch (error) {
226 | console.error(codeSample);
227 | console.error(error.stack);
228 | process.exit(1);
229 | }
230 | return root.toSource();
231 | }
232 |
233 | /**
234 | * Returns the original, not reordered, list of parameters.
235 | *
236 | * @param {Entry} entryItem Entry whose parameters to get.
237 | * @return {string[][]} List of args.
238 | */
239 | function getOriginalParams(entryItem) {
240 | var prev = entryItem._params;
241 | entryItem._params = undefined;
242 | common.baseGetParams.call(entryItem);
243 | var result = entryItem._params;
244 | entryItem._params = prev;
245 | return result;
246 | }
247 |
248 | /**
249 | * Returns a function that extracts the entry's `example` data,
250 | * where function call arguments are reordered according to `mapping`.
251 | *
252 | * @param {object} mapping Mapping object that defines if and how the `params` will be reordered.
253 | * @returns {Function} Function that returns the entry's `example` data.
254 | */
255 | function getReorderedExample(mapping) {
256 | return function() {
257 | var result = common.getMultilineValue(this.entry, 'example');
258 | if (!result) {
259 | return result;
260 | }
261 |
262 | var paramsDescription = getOriginalParams(this);
263 | var resultReordered = reorderParamsInExample(mapping, result, paramsDescription);
264 | return '```' + this.lang + '\n' + resultReordered + '\n```';
265 | };
266 | }
267 |
268 | module.exports = getReorderedExample;
269 |
--------------------------------------------------------------------------------
/lib/doc/apply-fp-mapping/index.js:
--------------------------------------------------------------------------------
1 | var Entry = require('docdown/lib/entry'),
2 | getUpdatedDesc = require('./description'),
3 | getReorderedParams = require('./parameters'),
4 | getReorderedExample = require('./example');
5 |
6 | /**
7 | * Updates `docdown` `Entry`'s prototype so that parameters/arguments are reordered according to `mapping`.
8 | */
9 | module.exports = function applyFPMapping(mapping) {
10 | Entry.prototype.getDesc = getUpdatedDesc;
11 | Entry.prototype.getParams = getReorderedParams(mapping);
12 | Entry.prototype.getExample = getReorderedExample(mapping);
13 | };
14 |
--------------------------------------------------------------------------------
/lib/doc/apply-fp-mapping/parameters.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | j = require('jscodeshift'),
3 | Entry = require('docdown/lib/entry'),
4 | common = require('./common');
5 |
6 | var baseGetParams = Entry.prototype.getParams;
7 |
8 | var dotsRegex = /^\.\.\./;
9 | var parensRegex = /^\((.*)\)$/;
10 | var squareBracketsRegex = /^\[(.*)\]$/;
11 | var arrayRegex = /\[\]$/;
12 |
13 | /**
14 | * Return whether method is wrapped.
15 | *
16 | * @param {Entry} entry Entry to look at.
17 | * @return {Boolean} true if the method is wrapped, false if it is static.
18 | */
19 | function isWrapped(entry) {
20 | return !common.hasTag(entry, 'static');
21 | }
22 |
23 | /**
24 | * Extract the entry's `name` data.
25 | * Sub-part of Entry.prototype.getCall() that fetches the name. Using `Entry.prototype.getCall()`
26 | * makes a call to getParams(), which itself call getBaseName --> infinite recursion.
27 | *
28 | * @param {object} entry Entry whose name to extract.
29 | * @returns {string} The entry's `name` data.
30 | */
31 | function getBaseName(entry) {
32 | var result = /\*\/\s*(?:function\s+([^(]*)|(.*?)(?=[:=,]))/.exec(entry);
33 | if (result) {
34 | result = (result[1] || result[2]).split('.').pop();
35 | result = _.trim(_.trim(result), "'").split('var ').pop();
36 | result = _.trim(result);
37 | }
38 | // Get the function name.
39 | return _.result(/\*[\t ]*@name\s+(.+)/.exec(entry), 1, result || '');
40 | }
41 |
42 | /**
43 | * Return `types` as '(X|Y|...)' if `types` contains multiple values, `types[0]` otherwise.
44 | *
45 | * @param {string[]} types Possible types of the parameter.
46 | * @return {string} `types` as a string.
47 | */
48 | function wrapInParensIfMultiple(types) {
49 | if (types.length > 1) {
50 | return '(' + types.join('|') + ')';
51 | }
52 | return types[0];
53 | }
54 |
55 | /**
56 | * Transform parameter type from 'X' to 'X|X[]'.
57 | *
58 | * @param {string[]} param Array whose first item is a description of the parameter type.
59 | * @return {string[]} `param` with the updated type.
60 | */
61 | function singleItemOrArrayOf(type) {
62 | return type + '|' + type + '[]';
63 | }
64 |
65 | /**
66 | * Replace parameter type from something like `...number` to `number|number[]`.
67 | *
68 | * @param {string[]} param Array whose first item is a description of the parameter type.
69 | * @return {string[]} `param` with the updated type.
70 | */
71 | function removeDotsFromTypeAndAllowMultiple(param) {
72 | var type = param[0];
73 | if (!dotsRegex.test(type)) {
74 | return param;
75 | }
76 |
77 | var newType = _.chain(type)
78 | .replace(dotsRegex, '')
79 | .replace(parensRegex, '$1')
80 | .split('|')
81 | .map(function(s) {
82 | return s.replace(arrayRegex, '');
83 | })
84 | .uniq()
85 | .thru(wrapInParensIfMultiple)
86 | .thru(singleItemOrArrayOf)
87 | .value();
88 |
89 | return [newType].concat(_.tail(param));
90 | }
91 |
92 | /**
93 | * Replace parameter type from something like `...number` to `number|number[]`.
94 | *
95 | * @param {string[]} param Array whose first item is a description of the parameter type.
96 | * @return {string[]} `param` with the updated type.
97 | */
98 | function removeDotsFromType(param) {
99 | var type = param[0];
100 | if (!dotsRegex.test(type)) {
101 | return param;
102 | }
103 |
104 | var newType = type
105 | .replace(dotsRegex, '')
106 | .replace(parensRegex, '$1');
107 |
108 | return [newType].concat(_.tail(param));
109 | }
110 |
111 | /**
112 | * Find and duplicate the parameter with a type of the form '...x'.
113 | *
114 | * @param {string} name Name of the method.
115 | * @param {string[]} params Description of the parameters of the method.
116 | * @return {string[]} Updated parameters.
117 | */
118 | function duplicateRestArrays(name, params) {
119 | var indexOfRestParam = _.findIndex(params, function(param) {
120 | return dotsRegex.test(param[0]);
121 | });
122 | if (indexOfRestParam === -1) {
123 | console.log('WARNING: method `' + name + '`',
124 | 'is capped to more arguments than its declared number of parameters,',
125 | 'but does not have a parameter like `...x`');
126 | }
127 | // duplicates param[indexOfRestParam] at its position
128 | return params.slice(0, indexOfRestParam + 1)
129 | .concat(params.slice(indexOfRestParam));
130 | }
131 |
132 | /**
133 | * Remove the optional default value and brackets around the name of the method.
134 | *
135 | * @param {string[]} param Array whose second item is the name of the param of the form
136 | * 'name', '[name]' or [name=defaultValue].
137 | * @return {string[]} `param` with the updated name.
138 | */
139 | function removeDefaultValue(param) {
140 | var name = param[1]
141 | .replace(squareBracketsRegex, '$1')
142 | .split('=')
143 | [0];
144 |
145 | return [param[0], name, param[2]];
146 | }
147 |
148 | /**
149 | * Return the updated list of parameters of a method described by `entry`,
150 | * according to changes described by `mapping`. Will, if needed:
151 | * - reorder the arguments
152 | * - remove default values and brackets around previously optional arguments
153 | * - remove ignored arguments
154 | * - duplicate rest arguments if the number of params is less than its cap
155 | * - de-restify arguments
156 | *
157 | * @param {object} mapping Mapping object that defines if and how the `params` will be reordered.
158 | * @param {Entry} entry Method to update.
159 | * @param {string[][]} params List of the original parameters of the method.
160 | * @return {string[][]} Updated list of params.
161 | */
162 | function updateParamsDescription(mapping, entry, params) {
163 | var tmpParams;
164 | var name = getBaseName(entry);
165 | var ary = common.getMethodAry(mapping, name);
166 |
167 | var wrapped = isWrapped(entry);
168 | if (wrapped) {
169 | // Needs one less argument when wrapped
170 | ary = ary - 1;
171 | params.shift();
172 | }
173 |
174 | if (ary > params.length) {
175 | tmpParams = duplicateRestArrays(name, params)
176 | .map(removeDotsFromType);
177 | } else {
178 | tmpParams = params
179 | .map(removeDotsFromTypeAndAllowMultiple);
180 | }
181 | tmpParams = _.take(tmpParams, ary).map(removeDefaultValue);
182 | return common.reorderParams(mapping, name, tmpParams, wrapped);
183 | }
184 |
185 | /**
186 | * Return a function that extracts the entry's `param` data, reordered according to `mapping`.
187 | *
188 | * @param {object} mapping Mapping object that defines if and how the `params` will be reordered.
189 | * @returns {Function} Function that returns the entry's `param` data.
190 | */
191 | function getReorderedParams(mapping) {
192 | return function(index) {
193 | if (!this._params) {
194 | // Call baseGetParams in order to compute `this._params`.
195 | baseGetParams.call(this);
196 | // Reorder params according to the `mapping`.
197 | this._params = updateParamsDescription(mapping, this.entry, this._params);
198 | }
199 | return baseGetParams.call(this, index);
200 | };
201 | }
202 |
203 | /**
204 | * Updates `docdown` `Entry`'s prototype so that parameters/arguments are reordered according to `mapping`.
205 | */
206 | module.exports = getReorderedParams;
207 |
--------------------------------------------------------------------------------
/lib/doc/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash'),
4 | docdown = require('docdown'),
5 | fs = require('fs-extra'),
6 | path = require('path');
7 |
8 | var mapping = require('../../fp/_mapping'),
9 | applyFPMapping = require('./apply-fp-mapping');
10 |
11 | var basePath = path.join(__dirname, '..', '..'),
12 | docPath = path.join(basePath, 'doc'),
13 | readmePath = path.join(docPath, 'README.md'),
14 | fpReadmePath = path.join(docPath, 'fp.md');
15 |
16 | var pkg = require('../../package.json'),
17 | version = pkg.version;
18 |
19 | var config = {
20 | 'base': {
21 | 'entryLinks': [
22 | '<% if (name == "templateSettings" || !/^(?:methods|properties|seq)$/i.test(category)) {' +
23 | 'print("[Ⓝ](https://www.npmjs.com/package/lodash." + name.toLowerCase() + " \\"See the npm package\\")")' +
24 | '} %>'
25 | ],
26 | 'path': path.join(basePath, 'lodash.js'),
27 | 'title': 'lodash v' + version + '',
28 | 'toc': 'categories',
29 | 'url': 'https://github.com/lodash/lodash/blob/' + version + '/lodash.js'
30 | },
31 | 'github': {
32 | 'hash': 'github'
33 | },
34 | 'site': {
35 | 'tocLink': '#docs'
36 | }
37 | };
38 |
39 | function postprocess(string) {
40 | // Fix docdown bug by wrapping symbol property identifiers in brackets.
41 | return string.replace(/\.(Symbol\.(?:[a-z]+[A-Z]?)+)/g, '[$1]');
42 | }
43 |
44 | /*----------------------------------------------------------------------------*/
45 |
46 | function onComplete(error) {
47 | if (error) {
48 | throw error;
49 | }
50 | }
51 |
52 | function build(fpFlag, type) {
53 | if (fpFlag) {
54 | applyFPMapping(mapping);
55 | }
56 | var options = _.defaults({}, config.base, config[type]),
57 | markdown = docdown(options),
58 | filePath = fpFlag ? fpReadmePath : readmePath;
59 |
60 | fs.writeFile(filePath, postprocess(markdown), onComplete);
61 | }
62 |
63 | build(_.includes(process.argv, '--fp'), _.last(process.argv));
64 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lodash-fp-docs",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "doc": "mkdir doc ; node lib/doc/build github",
8 | "doc:fp": "mkdir doc ; node lib/doc/build --fp github",
9 | "style": "jscs lib/**/*.js",
10 | "test": "node test/test-fp-doc"
11 | },
12 | "keywords": [],
13 | "author": "Jeroen Engels ",
14 | "license": "MIT",
15 | "devDependencies": {
16 | "fs-extra": "^0.26.5",
17 | "glob": "^7.0.0",
18 | "jscs": "^2.10.1",
19 | "qunit-extras": "^1.4.5",
20 | "qunitjs": "^1.22.0"
21 | },
22 | "dependencies": {
23 | "docdown": "~0.5.0",
24 | "jscodeshift": "^0.3.13",
25 | "lodash": "^4.5.1",
26 | "recast": "^0.11.2"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/test/test-fp-doc.js:
--------------------------------------------------------------------------------
1 | ;(function() {
2 | /** Used as a safe reference for `undefined` in pre-ES5 environments. */
3 | var undefined;
4 |
5 | /** Used as a reference to the global object. */
6 | var root = (typeof global == 'object' && global) || this;
7 |
8 | var phantom = root.phantom,
9 | amd = root.define && define.amd,
10 | document = !phantom && root.document,
11 | noop = function() {},
12 | argv = root.process && process.argv;
13 |
14 | /** Use a single "load" function. */
15 | var load = (!amd && typeof require == 'function')
16 | ? require
17 | : noop;
18 |
19 | /** The unit testing framework. */
20 | var QUnit = root.QUnit || (root.QUnit = (
21 | QUnit = load('../node_modules/qunitjs/qunit/qunit.js') || root.QUnit,
22 | QUnit = QUnit.QUnit || QUnit
23 | ));
24 |
25 | /** Load stable Lodash and QUnit Extras. */
26 | var _ = root._ || load('../lodash.js');
27 | if (_) {
28 | _ = _.runInContext(root);
29 | }
30 | var QUnitExtras = load('../node_modules/qunit-extras/qunit-extras.js');
31 | if (QUnitExtras) {
32 | QUnitExtras.runInContext(root);
33 | }
34 |
35 | var mapping = root.mapping || load('../fp/_mapping.js'),
36 | applyFPMapping = load('../lib/doc/apply-fp-mapping'),
37 | Entry = load('docdown/lib/entry');
38 |
39 | /*--------------------------------------------------------------------------*/
40 |
41 | function toCommentLine(line) {
42 | return ' * ' + line;
43 | }
44 |
45 | function toEntry(name, paramLines, exampleLines, attachedToPrototype) {
46 | var start = [
47 | '/**',
48 | ' * ',
49 | ' * Foo',
50 | ' * '
51 | ];
52 | var end = [
53 | ' */',
54 | 'function ' + name + '(a, b, c) {',
55 | '',
56 | '}'
57 | ];
58 | var staticLine = attachedToPrototype ? [] : [' * @static'];
59 | var params = paramLines.map(function(line) {
60 | return ' * @param ' + line;
61 | });
62 | var example = (exampleLines || []).map(toCommentLine);
63 |
64 | return [].concat(start, staticLine, params, [' * @example'], example, end).join('\n');
65 | }
66 |
67 | var differenceBySource = toEntry('differenceBy', [
68 | '{Array} array The array to inspect.',
69 | '{...Array} [values] The values to exclude.',
70 | '{Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.'
71 | ], [
72 | '_.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);',
73 | '// → [3.1, 1.3]',
74 | '',
75 | '// The `_.property` iteratee shorthand.',
76 | "_.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');",
77 | "// → [{ 'x': 2 }]"
78 | ]);
79 |
80 | var setParams = [
81 | '{Object} object The object to modify.',
82 | '{Array|string} path The path of the property to set.',
83 | '{*} value The value to set.'
84 | ];
85 |
86 | /*--------------------------------------------------------------------------*/
87 |
88 | if (argv) {
89 | console.log('Running doc generation tests.');
90 | }
91 |
92 | mapping.aryMethod[2].push('customFun');
93 | applyFPMapping(mapping);
94 |
95 | /*--------------------------------------------------------------------------*/
96 |
97 | QUnit.module('getExample');
98 |
99 | (function() {
100 | QUnit.test('should reorder parameters', function(assert) {
101 | assert.expect(1);
102 |
103 | var entry = new Entry(differenceBySource, differenceBySource);
104 |
105 | var actual = entry.getExample();
106 |
107 | assert.equal(actual, [
108 | '```js',
109 | '_.differenceBy(Math.floor, [4.4, 2.5], [3.1, 2.2, 1.3]);',
110 | '// → [3.1, 1.3]',
111 | '',
112 | '// The `_.property` iteratee shorthand.',
113 | "_.differenceBy('x', [{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }]);",
114 | "// → [{ 'x': 2 }]",
115 | '```'
116 | ].join('\n'));
117 | });
118 |
119 | QUnit.test('should reorder parameters that have a special order', function(assert) {
120 | assert.expect(1);
121 |
122 | var example = toEntry('set', setParams, [
123 | "var object = { 'a': [{ 'b': { 'c': 3 } }] };",
124 | "_.set(object, 'a[0].b.c', 4);",
125 | "_.set(object, 'x[0].y.z', 5);",
126 | ]);
127 | var entry = new Entry(example, example);
128 |
129 | var actual = entry.getExample();
130 |
131 | assert.equal(actual, [
132 | '```js',
133 | "var object = { 'a': [{ 'b': { 'c': 3 } }] };",
134 | "_.set('a[0].b.c', 4, object);",
135 | "_.set('x[0].y.z', 5, object);",
136 | '```'
137 | ].join('\n'));
138 | });
139 |
140 | QUnit.test('should preserve comments', function(assert) {
141 | assert.expect(1);
142 |
143 | var example = toEntry('set', setParams, [
144 | "var object = { 'a': [{ 'b': { 'c': 3 } }] };",
145 | "_.set(object, 'a[0].b.c', 4);",
146 | '// => 4',
147 | "_.set(object, 'x[0].y.z', 5);",
148 | ]);
149 | var entry = new Entry(example, example);
150 |
151 | var actual = entry.getExample();
152 |
153 | assert.equal(actual, [
154 | '```js',
155 | "var object = { 'a': [{ 'b': { 'c': 3 } }] };",
156 | "_.set('a[0].b.c', 4, object);",
157 | '// => 4',
158 | "_.set('x[0].y.z', 5, object);",
159 | '```'
160 | ].join('\n'));
161 | });
162 |
163 | QUnit.test('should remove console.logs from example', function(assert) {
164 | assert.expect(1);
165 |
166 | var example = toEntry('set', setParams, [
167 | "var object = { 'a': [{ 'b': { 'c': 3 } }] };",
168 | '',
169 | "_.set(object, 'a[0].b.c', 4);",
170 | 'console.log(object.a[0].b.c);',
171 | '// => 4',
172 | '',
173 | "_.set(object, 'x[0].y.z', 5);",
174 | 'console.log(object.x[0].y.z);',
175 | '// => 5'
176 | ]);
177 | var entry = new Entry(example, example);
178 |
179 | var actual = entry.getExample();
180 |
181 | assert.equal(actual, [
182 | '```js',
183 | "var object = { 'a': [{ 'b': { 'c': 3 } }] };",
184 | '',
185 | "_.set('a[0].b.c', 4, object);",
186 | '// => 4',
187 | '',
188 | "_.set('x[0].y.z', 5, object);",
189 | '// => 5',
190 | '```'
191 | ].join('\n'));
192 | });
193 |
194 | QUnit.test('should merge extra arguments into an array', function(assert) {
195 | assert.expect(1);
196 |
197 | var example = toEntry('pullAt', [
198 | '{Array} array The array to modify.',
199 | '{...(number|number[])} [indexes] The indexes of elements to remove,\n' +
200 | ' * specified individually or in arrays.'
201 | ], [
202 | 'var array = [5, 10, 15, 20];',
203 | 'var evens = _.pullAt(array, 1, 3);',
204 | '',
205 | 'console.log(array);',
206 | '// => [5, 15]',
207 | '',
208 | 'console.log(evens);',
209 | '// => [10, 20]'
210 | ]);
211 | var entry = new Entry(example, example);
212 |
213 | var actual = entry.getExample();
214 |
215 | assert.equal(actual, [
216 | '```js',
217 | 'var array = [5, 10, 15, 20];',
218 | 'var evens = _.pullAt([1, 3], array);',
219 | '',
220 | '// => [5, 15]',
221 | '',
222 | '// => [10, 20]',
223 | '```'
224 | ].join('\n'));
225 | });
226 |
227 | QUnit.test('should inject default values into optional arguments that became compulsory', function(assert) {
228 | assert.expect(1);
229 |
230 | var example = toEntry('sampleSize', [
231 | '{Array|Object} collection The collection to sample.',
232 | '{number} [n=0] The number of elements to sample.'
233 | ], [
234 | '_.sampleSize([1, 2, 3]);',
235 | '// => [3, 1]',
236 | '',
237 | '_.sampleSize([1, 2, 3], 4);',
238 | '// => [2, 3, 1]'
239 | ]);
240 | var entry = new Entry(example, example);
241 |
242 | var actual = entry.getExample();
243 |
244 | assert.equal(actual, [
245 | '```js',
246 | '_.sampleSize(0, [1, 2, 3]);',
247 | '// => [3, 1]',
248 | '',
249 | '_.sampleSize(4, [1, 2, 3]);',
250 | '// => [2, 3, 1]',
251 | '```'
252 | ].join('\n'));
253 | });
254 |
255 | QUnit.test('should inject referenced values into optional arguments that became compulsory, ' +
256 |
257 | 'if a parameter\'s default value references parameter (direct reference)',
258 | function(assert) {
259 | assert.expect(1);
260 |
261 | var example = toEntry('customFun', [
262 | '{Array} array Array',
263 | '{number} [foo=array] Foo'
264 | ], [
265 | '_.customFun([1, 2, 3]);',
266 | ]);
267 | var entry = new Entry(example, example);
268 |
269 | var actual = entry.getExample();
270 |
271 | assert.equal(actual, [
272 | '```js',
273 | '_.customFun([1, 2, 3], [1, 2, 3]);',
274 | '```'
275 | ].join('\n'));
276 | });
277 |
278 | QUnit.test('should inject referenced values into optional arguments that became compulsory, ' +
279 | 'if a parameter\'s default value references parameter (member expression)',
280 | function(assert) {
281 | assert.expect(1);
282 |
283 | var example = toEntry('fill', [
284 | '{Array} array The array to fill.',
285 | '{*} value The value to fill `array` with.',
286 | '{number} [start=0] The start position.',
287 | '{number} [end=array.length] The end position.'
288 | ], [
289 | 'var array = [1, 2, 3];',
290 | '',
291 | "_.fill(array, 'a');",
292 | 'console.log(array);',
293 | "// => ['a', 'a', 'a']",
294 | '',
295 | '_.fill(Array(3), 2, 1);',
296 | '// => [undefined, 2, 2]',
297 | '',
298 | "_.fill([4, 6, 8, 10], '*');",
299 | "// => [*, '*', '*', *]"
300 | ]);
301 | var entry = new Entry(example, example);
302 |
303 | var actual = entry.getExample();
304 |
305 | assert.equal(actual, [
306 | '```js',
307 | 'var array = [1, 2, 3];',
308 | '',
309 | "_.fill(0, array.length, 'a', array);",
310 | "// => ['a', 'a', 'a']",
311 | '',
312 | '_.fill(1, 3, 2, Array(3));',
313 | '// => [undefined, 2, 2]',
314 | '',
315 | "_.fill(0, 4, '*', [4, 6, 8, 10]);",
316 | "// => [*, '*', '*', *]",
317 | '```'
318 | ].join('\n'));
319 | });
320 |
321 | QUnit.test('should inject default values in the middle of the arguments', function(assert) {
322 | assert.expect(1);
323 |
324 | var example = toEntry('inRange', [
325 | '{number} number The number to check.',
326 | '{number} [start=0] The start of the range.',
327 | '{number} end The end of the range.'
328 | ], [
329 | '_.inRange(4, 8);',
330 | '// => true'
331 | ]);
332 | var entry = new Entry(example, example);
333 |
334 | var actual = entry.getExample();
335 |
336 | assert.equal(actual, [
337 | '```js',
338 | '_.inRange(8, 0, 4);',
339 | '// => true',
340 | '```'
341 | ].join('\n'));
342 | });
343 |
344 | QUnit.test('should not use ignored params as default values', function(assert) {
345 | assert.expect(1);
346 |
347 | var example = toEntry('drop', [
348 | '{Array} array The array to query.',
349 | '{number} [n=1] The number of elements to drop.',
350 | '{Object} [guard] Enables use as an iteratee for functions like `_.map`.'
351 | ], [
352 | '_.drop([1, 2, 3]);',
353 | '// => [2, 3]'
354 | ]);
355 | var entry = new Entry(example, example);
356 |
357 | var actual = entry.getExample();
358 |
359 | assert.equal(actual, [
360 | '```js',
361 | '_.drop(1, [1, 2, 3]);',
362 | '// => [2, 3]',
363 | '```'
364 | ].join('\n'));
365 | });
366 | }());
367 |
368 | /*--------------------------------------------------------------------------*/
369 |
370 | QUnit.module('getParams');
371 |
372 | (function() {
373 | QUnit.test('should reorder arguments and remove default values', function(assert) {
374 | assert.expect(1);
375 |
376 | var example = toEntry('differenceBy', [
377 | '{Array} array The array to inspect.',
378 | '{...Array} [values] The values to exclude.',
379 | '{Function|Object|string} [iteratee=_.identity] The iteratee invoked per element.'
380 | ]);
381 | var entry = new Entry(example, example);
382 |
383 | var actual = entry.getParams();
384 |
385 | assert.deepEqual(actual, [
386 | ['Function|Object|string', 'iteratee', 'The iteratee invoked per element. '],
387 | ['Array|Array[]', 'values', 'The values to exclude. '],
388 | ['Array', 'array', 'The array to inspect. ']
389 | ]);
390 | });
391 |
392 | QUnit.test('should reorder arguments that have a special order', function(assert) {
393 | assert.expect(1);
394 |
395 | var example = toEntry('set', [
396 | '{Object} object The object to modify.',
397 | '{Array|string} path The path of the property to set.',
398 | '{*} value The value to set.'
399 | ]);
400 | var entry = new Entry(example, example);
401 |
402 | var actual = entry.getParams();
403 |
404 | assert.deepEqual(actual, [
405 | ['Array|string', 'path', 'The path of the property to set. '],
406 | ['*', 'value', 'The value to set. '],
407 | ['Object', 'object', 'The object to modify. '],
408 | ]);
409 | });
410 |
411 | QUnit.test('should transform rest arguments into an array', function(assert) {
412 | assert.expect(1);
413 |
414 | var example = toEntry('pullAt', [
415 | '{Array} array The array to modify.',
416 | '{...(number|number[])} [indexes] The indexes of elements to remove,\n' +
417 | ' * specified individually or in arrays.'
418 | ]);
419 | var entry = new Entry(example, example);
420 |
421 | var actual = entry.getParams();
422 |
423 | assert.deepEqual(actual, [
424 | // TODO Remove this line in favor of the commented one.
425 | // Is linked to a docdown issue (https://github.com/jdalton/docdown/pull/37)
426 | // that does not handle parens in the arguments well
427 | ['((number|number)|((number|number)[]', 'indexes', 'The indexes of elements to remove, specified individually or in arrays. '],
428 | // ['number|number[]', '[indexes]', 'The indexes of elements to remove, specified individually or in arrays. '],
429 | ['Array', 'array', 'The array to modify. '],
430 | ]);
431 | });
432 |
433 | QUnit.test('should duplicate and de-restify "rest" parameters if there are less parameters than cap', function(assert) {
434 | assert.expect(1);
435 |
436 | var example = toEntry('intersectionWith', [
437 | '{...Array} [arrays] The arrays to inspect.',
438 | '{Function} [comparator] The comparator invoked per element.'
439 | ]);
440 | var entry = new Entry(example, example);
441 |
442 | var actual = entry.getParams();
443 |
444 | assert.deepEqual(actual, [
445 | ['Function', 'comparator', 'The comparator invoked per element. '],
446 | ['Array', 'arrays', 'The arrays to inspect. '],
447 | ['Array', 'arrays', 'The arrays to inspect. ']
448 | ]);
449 | });
450 |
451 | QUnit.test('should consider method to have an ary of `ary - 1` when capped and wrapped', function(assert) {
452 | assert.expect(1);
453 |
454 | var wrapped = true;
455 | var example = toEntry('flatMap', [
456 | '{Array} array The array to iterate over.',
457 | '{Function|Object|string} [iteratee=_.identity] The function invoked per iteration.'
458 | ], [], wrapped);
459 | var entry = new Entry(example, example);
460 |
461 | var actual = entry.getParams();
462 |
463 | assert.deepEqual(actual, [
464 | ['Function|Object|string', 'iteratee', 'The function invoked per iteration. ']
465 | ]);
466 | });
467 |
468 | QUnit.test('should remove arguments ignored because of capping (includes)', function(assert) {
469 | assert.expect(1);
470 |
471 | var example = toEntry('includes', [
472 | '{Array|Object|string} collection The collection to search.',
473 | '{*} value The value to search for.',
474 | '{number} [fromIndex=0] The index to search from.'
475 | ]);
476 | var entry = new Entry(example, example);
477 |
478 | var actual = entry.getParams();
479 |
480 | assert.deepEqual(actual, [
481 | ['*', 'value', 'The value to search for. '],
482 | ['Array|Object|string', 'collection', 'The collection to search. ']
483 | ]);
484 | });
485 |
486 | QUnit.test('should remove arguments ignored because of capping (trim)', function(assert) {
487 | assert.expect(1);
488 |
489 | var example = toEntry('trim', [
490 | "{string} [string=''] The string to trim.",
491 | '{string} [chars=whitespace] The characters to trim.'
492 | ]);
493 | var entry = new Entry(example, example);
494 |
495 | var actual = entry.getParams();
496 |
497 | assert.deepEqual(actual, [
498 | ['string', 'string', 'The string to trim. ']
499 | ]);
500 | });
501 | }());
502 |
503 | /*--------------------------------------------------------------------------*/
504 |
505 | QUnit.module('getDesc');
506 |
507 | (function() {
508 | function toSourceWithDescription(name, description) {
509 | var start = [
510 | '/**',
511 | ' * '
512 | ];
513 |
514 | var end = [
515 | ' * @static',
516 | ' * ',
517 | ' */',
518 | 'function ' + name + '(a, b, c) {',
519 | '',
520 | '}'
521 | ];
522 |
523 | var descriptionLines = description.map(toCommentLine);
524 | return [].concat(start, descriptionLines, end).join('\n');
525 | }
526 |
527 | QUnit.test('should remove notes about mutators arguments and remove default values', function(assert) {
528 | assert.expect(1);
529 |
530 | var example = toSourceWithDescription('pullAt', [
531 | 'Removes elements from `array` corresponding to `indexes` and returns an',
532 | 'array of removed elements.',
533 | '',
534 | '**Note:** Unlike `_.at`, this method mutates `array`.',
535 | ''
536 | ]);
537 |
538 | var entry = new Entry(example, example);
539 |
540 | var actual = entry.getDesc();
541 |
542 | assert.equal(actual, [
543 | 'Removes elements from `array` corresponding to `indexes` and returns an',
544 | 'array of removed elements.'
545 | ].join('\n'));
546 | });
547 |
548 | QUnit.test('should remove following related lines', function(assert) {
549 | assert.expect(1);
550 |
551 | var example = toSourceWithDescription('assign', [
552 | 'Assigns own enumerable properties of source objects to the destination',
553 | 'object. Source objects are applied from left to right. Subsequent sources',
554 | 'overwrite property assignments of previous sources.',
555 | '',
556 | '**Note:** This method mutates `object` and is loosely based on',
557 | '[`Object.assign`](https://mdn.io/Object/assign).',
558 | ''
559 | ]);
560 |
561 | var entry = new Entry(example, example);
562 |
563 | var actual = entry.getDesc();
564 |
565 | assert.equal(actual, [
566 | 'Assigns own enumerable properties of source objects to the destination',
567 | 'object. Source objects are applied from left to right. Subsequent sources',
568 | 'overwrite property assignments of previous sources.',
569 | ].join('\n'));
570 | });
571 | }());
572 |
573 | QUnit.config.asyncRetries = 10;
574 | QUnit.config.hidepassed = true;
575 |
576 | if (!document) {
577 | QUnit.config.noglobals = true;
578 | QUnit.load();
579 | }
580 | }.call(this));
581 |
--------------------------------------------------------------------------------