inline-loops.macro
Advanced tools
Comparing version 1.0.0-beta.1 to 1.0.0
@@ -5,4 +5,10 @@ const { every, everyObject, everyRight } = require('../../inline-loops.macro'); | ||
const OBJECT = { | ||
one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, | ||
one: 1, | ||
two: 2, | ||
three: 3, | ||
four: 4, | ||
five: 5, | ||
six: 6, | ||
}; | ||
const NESTED_ARRAY = [ARRAY, ARRAY]; | ||
@@ -69,2 +75,8 @@ const isEven = value => value % 2 === 0; | ||
}, | ||
nested: { | ||
standard: { | ||
false: every(NESTED_ARRAY, array => every(array, isEven)), | ||
true: every(NESTED_ARRAY, array => every(array, isPositive)), | ||
}, | ||
}, | ||
uncached: { | ||
@@ -71,0 +83,0 @@ decrementing: { |
@@ -1,14 +0,16 @@ | ||
const { transformFileSync } = require("@babel/core"); | ||
const fs = require("fs"); | ||
const path = require("path"); | ||
/* eslint-disable */ | ||
const TRANSFORM_OPTIONS = require("../package.json").babel; | ||
const { transformFileSync } = require('@babel/core'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const TEST_FILES = path.join(__dirname, "__runtime__"); | ||
const TRANSFORM_OPTIONS = require('../package.json').babel; | ||
const TEST_FILES = path.join(__dirname, '__runtime__'); | ||
const TRANSFORMED_FILES = fs.readdirSync(TEST_FILES).reduce((tests, file) => { | ||
const filename = path.join(TEST_FILES, file); | ||
const fn = file.replace(".js", ""); | ||
const fn = file.replace('.js', ''); | ||
if (fn.startsWith("error-")) { | ||
test(`if ${fn.replace("error-", "").replace("-", " ")} will throw an error`, () => { | ||
if (fn.startsWith('error-')) { | ||
test(`if ${fn.replace('error-', '').replace('-', ' ')} will throw an error`, () => { | ||
expect(() => transformFileSync(filename, TRANSFORM_OPTIONS)).toThrow(); | ||
@@ -27,6 +29,6 @@ }); | ||
describe("runtime tests", () => { | ||
describe('runtime tests', () => { | ||
for (const fn in TRANSFORMED_FILES) { | ||
if (fn === "errors") { | ||
console.log("foo"); | ||
if (fn === 'errors') { | ||
console.log('foo'); | ||
@@ -33,0 +35,0 @@ continue; |
@@ -1,45 +0,45 @@ | ||
const pluginTester = require("babel-plugin-tester"); | ||
const plugin = require("babel-plugin-macros"); | ||
const path = require("path"); | ||
const pluginTester = require('babel-plugin-tester'); | ||
const plugin = require('babel-plugin-macros'); | ||
const path = require('path'); | ||
pluginTester({ | ||
title: "Cached references", | ||
title: 'Cached references', | ||
plugin, | ||
fixtures: path.join(__dirname, "__fixtures__", "cached"), | ||
filename: __filename | ||
fixtures: path.join(__dirname, '__fixtures__', 'cached'), | ||
filename: __filename, | ||
}); | ||
pluginTester({ | ||
title: "Inlined function references (arrow expreasion)", | ||
title: 'Inlined function references (arrow expreasion)', | ||
plugin, | ||
fixtures: path.join(__dirname, "__fixtures__", "inlined-arrow-expression"), | ||
filename: __filename | ||
fixtures: path.join(__dirname, '__fixtures__', 'inlined-arrow-expression'), | ||
filename: __filename, | ||
}); | ||
pluginTester({ | ||
title: "Inlined function references (arrow return)", | ||
title: 'Inlined function references (arrow return)', | ||
plugin, | ||
fixtures: path.join(__dirname, "__fixtures__", "inlined-arrow-return"), | ||
filename: __filename | ||
fixtures: path.join(__dirname, '__fixtures__', 'inlined-arrow-return'), | ||
filename: __filename, | ||
}); | ||
pluginTester({ | ||
title: "Inlined function references (function return)", | ||
title: 'Inlined function references (function return)', | ||
plugin, | ||
fixtures: path.join(__dirname, "__fixtures__", "inlined-function-return"), | ||
filename: __filename | ||
fixtures: path.join(__dirname, '__fixtures__', 'inlined-function-return'), | ||
filename: __filename, | ||
}); | ||
pluginTester({ | ||
title: "Uncached references", | ||
title: 'Uncached references', | ||
plugin, | ||
fixtures: path.join(__dirname, "__fixtures__", "uncached"), | ||
filename: __filename | ||
fixtures: path.join(__dirname, '__fixtures__', 'uncached'), | ||
filename: __filename, | ||
}); | ||
pluginTester({ | ||
title: "Nested references", | ||
title: 'Nested references', | ||
plugin, | ||
fixtures: path.join(__dirname, "__fixtures__", "nested"), | ||
filename: __filename | ||
fixtures: path.join(__dirname, '__fixtures__', 'nested'), | ||
filename: __filename, | ||
}); |
@@ -716,8 +716,29 @@ "use strict"; | ||
return { | ||
decrementingCalls: decrementingCalls, | ||
decrementingCalls: decrementingCalls.map(function (path) { | ||
return { | ||
method: decrementingMethod, | ||
path: path, | ||
sourceMethod: method, | ||
type: 'decrementing' | ||
}; | ||
}), | ||
decrementingMethod: decrementingMethod, | ||
incrementingCalls: incrementingCalls, | ||
incrementingCalls: incrementingCalls.map(function (path) { | ||
return { | ||
method: method, | ||
path: path, | ||
sourceMethod: method, | ||
type: 'incrementing' | ||
}; | ||
}), | ||
isArrayOnly: isArrayOnly, | ||
isObjectOnly: isObjectOnly, | ||
objectCalls: objectCalls, | ||
objectCalls: objectCalls.map(function (path) { | ||
return { | ||
method: objectMethod, | ||
path: path, | ||
sourceMethod: method, | ||
type: 'object' | ||
}; | ||
}), | ||
objectMethod: objectMethod | ||
@@ -750,2 +771,47 @@ }; | ||
}); | ||
allMethods.forEach(function (_ref13) { | ||
var path = _ref13.path; | ||
path.node.__inlineLoopsMacro = true; | ||
}); | ||
allMethods.sort(function (_ref14, _ref15) { | ||
var a = _ref14.path; | ||
var b = _ref15.path; | ||
var aContainer = a.container; | ||
var bContainer = b.container; | ||
if (aContainer.arguments) { | ||
var _aContainer$arguments = _slicedToArray(aContainer.arguments, 1), | ||
iterableA = _aContainer$arguments[0]; | ||
if (t.isCallExpression(iterableA) && iterableA.callee.__inlineLoopsMacro && iterableA.callee === b.node) { | ||
return 1; | ||
} | ||
} | ||
if (bContainer.arguments) { | ||
var _bContainer$arguments = _slicedToArray(bContainer.arguments, 1), | ||
iterableB = _bContainer$arguments[0]; | ||
if (t.isCallExpression(iterableB) && iterableB.callee.__inlineLoopsMacro && iterableB.callee === a.node) { | ||
return -1; | ||
} | ||
} | ||
var aStart = a.node.loc.start; | ||
var bStart = b.node.loc.start; | ||
if (bStart.line > aStart.line) { | ||
return -1; | ||
} | ||
if (aStart.line > bStart.line) { | ||
return 1; | ||
} | ||
if (bStart.column > aStart.column) { | ||
return -1; | ||
} | ||
return 1; | ||
}); | ||
var handlers = { | ||
@@ -763,3 +829,3 @@ every: handleEvery, | ||
function createHandler(name, transform, isDecrementing, isObject) { | ||
function createTransformer(name, transform, isDecrementing, isObject) { | ||
return function _transform(path) { | ||
@@ -772,35 +838,24 @@ if (path.findParent(function (_path) { | ||
var callee = path.parentPath.parent.callee; | ||
var args = path.parent.arguments; | ||
if (callee) { | ||
var ancestorPath = path.parentPath; | ||
if (args.some(function (arg) { | ||
return t.isSpreadElement(arg); | ||
})) { | ||
throw new MacroError('You cannot use spread arguments with the macro, please declare the arguments explicitly.'); | ||
} | ||
while (ancestorPath) { | ||
if (ancestorPath.node && ancestorPath.node.body) { | ||
break; | ||
} | ||
var _args = _slicedToArray(args, 3), | ||
object = _args[0], | ||
handler = _args[1], | ||
initialValue = _args[2]; | ||
if (t.isCallExpression(ancestorPath)) { | ||
(function () { | ||
var expression = ancestorPath.parent.expression; | ||
var caller = expression ? expression.callee : ancestorPath.parent.callee; | ||
var isHandlerMacro = allMethods.find(function (_ref16) { | ||
var methodPath = _ref16.path; | ||
return methodPath.node !== path.node && handler === methodPath.node; | ||
}); | ||
if (allMethods.find(function (_ref13) { | ||
var node = _ref13.node; | ||
return node === caller && node !== path.node; | ||
})) { | ||
throw new MacroError("You cannot nest looper methods. You should store the results of ".concat(name, " to a variable, and then call ").concat(path.parentPath.parent.callee.name, " with it.")); | ||
} | ||
})(); | ||
} | ||
ancestorPath = ancestorPath.parentPath; | ||
} | ||
if (isHandlerMacro) { | ||
throw new MacroError('You cannot use the macro directly as a handler, please wrap it in a function call.'); | ||
} | ||
var _path$parent$argument = _slicedToArray(path.parent.arguments, 3), | ||
object = _path$parent$argument[0], | ||
handler = _path$parent$argument[1], | ||
initialValue = _path$parent$argument[2]; | ||
transform({ | ||
@@ -818,20 +873,11 @@ t: t, | ||
METHODS.forEach(function (method) { | ||
var _getCallTypes2 = getCallTypes(references, method), | ||
decrementingCalls = _getCallTypes2.decrementingCalls, | ||
decrementingMethod = _getCallTypes2.decrementingMethod, | ||
incrementingCalls = _getCallTypes2.incrementingCalls, | ||
isArrayOnly = _getCallTypes2.isArrayOnly, | ||
isObjectOnly = _getCallTypes2.isObjectOnly, | ||
objectCalls = _getCallTypes2.objectCalls, | ||
objectMethod = _getCallTypes2.objectMethod; | ||
if (!isObjectOnly) { | ||
incrementingCalls.forEach(createHandler(method, handlers[method])); | ||
decrementingCalls.forEach(createHandler(decrementingMethod, handlers[method], true)); | ||
} | ||
if (!isArrayOnly) { | ||
objectCalls.forEach(createHandler(objectMethod, handlers[method], false, true)); | ||
} | ||
allMethods.forEach(function (_ref17) { | ||
var method = _ref17.method, | ||
path = _ref17.path, | ||
sourceMethod = _ref17.sourceMethod, | ||
type = _ref17.type; | ||
var isDecrementing = type === 'decrementing'; | ||
var isObject = type === 'object'; | ||
var handler = createTransformer(method, handlers[sourceMethod], isDecrementing, isObject); | ||
handler(path); | ||
}); | ||
@@ -838,0 +884,0 @@ } |
@@ -916,8 +916,23 @@ const { createMacro, MacroError } = require('babel-plugin-macros'); | ||
return { | ||
decrementingCalls, | ||
decrementingCalls: decrementingCalls.map(path => ({ | ||
method: decrementingMethod, | ||
path, | ||
sourceMethod: method, | ||
type: 'decrementing', | ||
})), | ||
decrementingMethod, | ||
incrementingCalls, | ||
incrementingCalls: incrementingCalls.map(path => ({ | ||
method, | ||
path, | ||
sourceMethod: method, | ||
type: 'incrementing', | ||
})), | ||
isArrayOnly, | ||
isObjectOnly, | ||
objectCalls, | ||
objectCalls: objectCalls.map(path => ({ | ||
method: objectMethod, | ||
path, | ||
sourceMethod: method, | ||
type: 'object', | ||
})), | ||
objectMethod, | ||
@@ -952,2 +967,52 @@ }; | ||
allMethods.forEach(({ path }) => { | ||
path.node.__inlineLoopsMacro = true; | ||
}); | ||
allMethods.sort(({ path: a }, { path: b }) => { | ||
const aContainer = a.container; | ||
const bContainer = b.container; | ||
if (aContainer.arguments) { | ||
const [iterableA] = aContainer.arguments; | ||
if ( | ||
t.isCallExpression(iterableA) | ||
&& iterableA.callee.__inlineLoopsMacro | ||
&& iterableA.callee === b.node | ||
) { | ||
return 1; | ||
} | ||
} | ||
if (bContainer.arguments) { | ||
const [iterableB] = bContainer.arguments; | ||
if ( | ||
t.isCallExpression(iterableB) | ||
&& iterableB.callee.__inlineLoopsMacro | ||
&& iterableB.callee === a.node | ||
) { | ||
return -1; | ||
} | ||
} | ||
const aStart = a.node.loc.start; | ||
const bStart = b.node.loc.start; | ||
if (bStart.line > aStart.line) { | ||
return -1; | ||
} | ||
if (aStart.line > bStart.line) { | ||
return 1; | ||
} | ||
if (bStart.column > aStart.column) { | ||
return -1; | ||
} | ||
return 1; | ||
}); | ||
const handlers = { | ||
@@ -965,3 +1030,3 @@ every: handleEvery, | ||
function createHandler(name, transform, isDecrementing, isObject) { | ||
function createTransformer(name, transform, isDecrementing, isObject) { | ||
return function _transform(path) { | ||
@@ -972,39 +1037,17 @@ if (path.findParent(_path => _path.isConditionalExpression())) { | ||
); | ||
} const args = path.parent.arguments; | ||
if (args.some(arg => t.isSpreadElement(arg))) { | ||
throw new MacroError('You cannot use spread arguments with the macro, please declare the arguments explicitly.'); | ||
} | ||
const { callee } = path.parentPath.parent; | ||
const [object, handler, initialValue] = args; | ||
const isHandlerMacro = allMethods.find( | ||
({ path: methodPath }) => methodPath.node !== path.node && handler === methodPath.node, | ||
); | ||
if (callee) { | ||
let ancestorPath = path.parentPath; | ||
while (ancestorPath) { | ||
if (ancestorPath.node && ancestorPath.node.body) { | ||
break; | ||
} | ||
if (t.isCallExpression(ancestorPath)) { | ||
const { expression } = ancestorPath.parent; | ||
const caller = expression | ||
? expression.callee | ||
: ancestorPath.parent.callee; | ||
if ( | ||
allMethods.find( | ||
({ node }) => node === caller && node !== path.node, | ||
) | ||
) { | ||
throw new MacroError( | ||
`You cannot nest looper methods. You should store the results of ${name} to a variable, and then call ${ | ||
path.parentPath.parent.callee.name | ||
} with it.`, | ||
); | ||
} | ||
} | ||
ancestorPath = ancestorPath.parentPath; | ||
} | ||
if (isHandlerMacro) { | ||
throw new MacroError('You cannot use the macro directly as a handler, please wrap it in a function call.'); | ||
} | ||
const [object, handler, initialValue] = path.parent.arguments; | ||
transform({ | ||
@@ -1022,25 +1065,16 @@ t, | ||
METHODS.forEach((method) => { | ||
const { | ||
decrementingCalls, | ||
decrementingMethod, | ||
incrementingCalls, | ||
isArrayOnly, | ||
isObjectOnly, | ||
objectCalls, | ||
objectMethod, | ||
} = getCallTypes(references, method); | ||
allMethods.forEach(({ | ||
method, path, sourceMethod, type, | ||
}) => { | ||
const isDecrementing = type === 'decrementing'; | ||
const isObject = type === 'object'; | ||
if (!isObjectOnly) { | ||
incrementingCalls.forEach(createHandler(method, handlers[method])); | ||
decrementingCalls.forEach( | ||
createHandler(decrementingMethod, handlers[method], true), | ||
); | ||
} | ||
const handler = createTransformer( | ||
method, | ||
handlers[sourceMethod], | ||
isDecrementing, | ||
isObject, | ||
); | ||
if (!isArrayOnly) { | ||
objectCalls.forEach( | ||
createHandler(objectMethod, handlers[method], false, true), | ||
); | ||
} | ||
handler(path); | ||
}); | ||
@@ -1047,0 +1081,0 @@ } |
@@ -61,12 +61,15 @@ { | ||
"scripts": { | ||
"build": "babel inline-loops.macro.js -d build && cp ./index.d.ts ./build/", | ||
"build": "babel inline-loops.macro.js -d build", | ||
"copy:types": "cp ./index.d.ts ./build/", | ||
"dist": "npm run build && npm run copy:types", | ||
"lint": "eslint ./inline-loops.macro.js", | ||
"lint:fix": "npm run lint -- --fix", | ||
"prepublishOnly": "npm run lint && npm run test && npm run build", | ||
"prepublishOnly": "npm run lint && npm run test && npm run dist", | ||
"release": "release-it", | ||
"release:beta": "release-it --config=.release-it.beta.json", | ||
"test": "jest" | ||
"test": "jest", | ||
"test:watch": "npm run test -- --watch" | ||
}, | ||
"typings": "./index.d.ts", | ||
"version": "1.0.0-beta.1" | ||
"version": "1.0.0" | ||
} |
215
README.md
@@ -1,1 +0,214 @@ | ||
# inline-loops.macro | ||
# inline-loops.macro | ||
Iteration helpers that inline to native loops for performance | ||
## Table of Contents | ||
- [inline-loops.macro](#inline-loopsmacro) | ||
- [Table of Contents](#table-of-contents) | ||
- [Summary](#summary) | ||
- [Usage](#usage) | ||
- [Methods](#methods) | ||
- [How it works](#how-it-works) | ||
- [Gotchas](#gotchas) | ||
- [`*Object` methods do not perform `hasOwnProperty` check](#object-methods-do-not-perform-hasownproperty-check) | ||
- [`findIndex` vs `findKey`](#findindex-vs-findkey) | ||
- [Development](#development) | ||
## Summary | ||
`inline-loops.macro` is a babel macro that will inline calls to the iteration methods provided, replacing them with `for` loops (or `for-in` in the case of objects). While this adds more code, it is also considerably more performant than the native versions of these methods. When working in non-JIT environments this is also faster than equivalent runtime helpers, as it avoids function calls and inlines operations when possible. | ||
This is inspired by the work done on [babel-plugin-loop-optimizer](https://www.npmjs.com/package/babel-plugin-loop-optimizer), but aims to be both more targeted and more full-featured. Rather than globally replace all native calls, the use of macros allow a controlled, opt-in usage. This macro also supports decrementing array and object iteration, as well as nested usage. | ||
You can use it for everything, only for hotpaths, as a replacement for `lodash` with legacy support, whatever you see fit for your project. The support should be the same as the support for `babel-plugin-macros`. | ||
## Usage | ||
```javascript | ||
import { map, reduce, someObject } from 'inline-loops.macro'; | ||
function contrivedExample(array) { | ||
const doubled = map(array, (value) => value * 2); | ||
const doubleObject = reduce(doubled, (object, value) => ({ | ||
...object, | ||
[value]: value | ||
}); | ||
if (someObject(doubleObject, (value) => value > 100)) { | ||
console.log('I am large!'); | ||
} | ||
} | ||
``` | ||
## Methods | ||
- `every` ([MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every)) | ||
- `everyRight` => same as `every`, but iterating in reverse | ||
- `everyObject` => same as `every` but iterating over objects intead of arrays | ||
- `filter` ([MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)) | ||
- `filterRight` => same as `filter`, but iterating in reverse | ||
- `filterObject` => same as `filter` but iterating over objects intead of arrays | ||
- `find` ([MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)) | ||
- `findRight` => same as `find`, but iterating in reverse | ||
- `findObject` => same as `find` but iterating over objects intead of arrays | ||
- `findIndex` ([MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex)) | ||
- `findIndexRight` => same as `findIndex`, but iterating in reverse | ||
- `findKey` => same as `findIndex` but iterating over objects intead of arrays | ||
- `forEach` ([MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)) | ||
- `forEachRight` => same as `forEach`, but iterating in reverse | ||
- `forEachObject` => same as `forEach` but iterating over objects intead of arrays | ||
- `map` ([MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)) | ||
- `mapRight` => same as `map`, but iterating in reverse | ||
- `mapObject` => same as `map` but iterating over objects intead of arrays | ||
- `reduce` ([MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)) | ||
- `reduceRight` => same as `reduce`, but iterating in reverse | ||
- `reduceObject` => same as `reduce` but iterating over objects intead of arrays | ||
- `some` ([MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some)) | ||
- `someRight` => same as `some`, but iterating in reverse | ||
- `someObject` => same as `some` but iterating over objects intead of arrays | ||
## How it works | ||
Internally Babel will transform these calls to their respective loop-driven alternatives. Example | ||
```javascript | ||
// this | ||
const foo = map(array, fn); | ||
// becomes this | ||
let _result = []; | ||
for (let _key = 0, _length = array.length, _value; _key < _length; ++_key) { | ||
_value = array[_key]; | ||
_result.push(fn(_value, _key, array)); | ||
} | ||
const foo = _result; | ||
``` | ||
If you are passing uncached values as the array or the handler, it will store those values as local variables and execute the same loop based on those variables. | ||
One extra performance boost is that `inline-loops` will try to inline operations when possible. For example: | ||
```javascript | ||
// this | ||
const doubled = map(array, value => value * 2); | ||
// becomes this | ||
let _result = []; | ||
for (let _key = 0, _length = array.length, _value; _key < _length; ++_key) { | ||
_value = array[_key]; | ||
_result.push(_value * 2); | ||
} | ||
const doubled = _result; | ||
``` | ||
Notice that there is no reference to the original function, because it used the return directly. This even works with nested calls! | ||
```javascript | ||
// this | ||
const isAllTuples = every(array, tuple => | ||
every(tuple, (value) => Array.isArray(value) && value.length === 2) | ||
); | ||
// becomes this | ||
let _result = true; | ||
for (let _key = 0, _length = array.length, _value; _key < _length; ++_key) { | ||
_value = array[_key]; | ||
let _result2 = true; | ||
for (let _key2 = 0, _length2 = _value.length, _value2; _key2 < _length2; ++_key2) { | ||
_value2 = _value[_key2]; | ||
if (!(Array.isArray(_value2) && _value2.length === 2)) { | ||
_result2 = false; | ||
break; | ||
} | ||
} | ||
if (!_result2) { | ||
_result = false; | ||
break; | ||
} | ||
} | ||
const isAllTuples = _result; | ||
``` | ||
## Gotchas | ||
Some aspects of implementing this macro that you should be aware of: | ||
### `*Object` methods do not perform `hasOwnProperty` check | ||
The object methods will do operations in `for-in` loop, but will not guard via a `hasOwnProperty` check. For example: | ||
```javascript | ||
// this | ||
const doubled = mapObject(object, value => value * 2); | ||
// becomes this | ||
let _result = {}; | ||
let _value; | ||
for (let _key in object) { | ||
_value = object[_key]; | ||
_result[key] = _value * 2; | ||
} | ||
const doubled = _result; | ||
``` | ||
This works in a vast majority of cases, as the need for `hasOwnProperty` checks are often an edge case; it only matters when using objects created via a custom constructor, iterating over static properties on functions, or other non-standard operations. `hasOwnProperty` is a slowdown, but can be especially expensive in legacy browsers or non-JIT environments. | ||
If you need to incorporate this, you can do it one of two ways: | ||
**Add filtering (iterates twice, but arguably cleaner semantics)** | ||
```javascript | ||
const raw = mapObject(object, (value, key) => object.hasOwnProperty(key) ? value * 2 : null); | ||
const doubled = filterObject(raw, value => value !== null); | ||
``` | ||
**Use reduce instead (iterates only once, but a little harder to grok)** | ||
```javascript | ||
const doubled = reduceObject(object, (_doubled, value, key) => { | ||
if (object.hasOwnProperty(key)) { | ||
_doubled[key] = value * 2; | ||
} | ||
return _doubled; | ||
}); | ||
``` | ||
### `findIndex` vs `findKey` | ||
Most of the operations follow the same naming conventions: | ||
- `{method}` (incrementing array) | ||
- `{method}Right` (decrementing array) | ||
- `{method}Object` (object) | ||
The exception to this is `findIndex` / `findKey` (which are specific to arrays) and `findKey` (which is specific to objects). The rationale should be obvious (arrays only have indices, objects only have keys), but because it is the only exception to the rule I wanted to call it out. | ||
## Development | ||
Standard stuff, clone the repo and `npm install` dependencies. The npm scripts available: | ||
- `build` => runs babel to transform the macro for legacy NodeJS support | ||
- `copy:types` => copies `index.d.ts` to `build` | ||
- `dist` => runs `build` and `copy:types` | ||
- `lint` => runs ESLint against all files in the `src` folder | ||
- `lint:fix` => runs `lint``, fixing any errors if possible | ||
- `prepublishOnly` => run `lint`, `test`, `test:coverage`, and `dist` | ||
- `release` => release new version (expects globally-installed `release-it`) | ||
- `release:beta` => release new beta version (expects globally-installed `release-it`) | ||
- `test` => run jest tests | ||
- `test:watch` => run `test`, but with persistent watcher |
Sorry, the diff of this file is not supported yet
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
151122
271
4970
0
215