New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

inline-loops.macro

Package Overview
Dependencies
Maintainers
1
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

inline-loops.macro - npm Package Compare versions

Comparing version 1.0.0-beta.1 to 1.0.0

__tests__/__fixtures__/nested/contrived/code.js

14

__tests__/__runtime__/every.js

@@ -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: {

24

__tests__/runtime.test.js

@@ -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"
}

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc