From c370308f5736edd79d9752baacf48818336ee0b6 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Sun, 21 Oct 2018 18:19:06 +0200 Subject: [PATCH] feat(lambda): allow custom variable names in lambda functions --- package.json | 6 +- .../__snapshots__/interpreter.test.js.snap | 349 +++++++++++------- src/__tests__/interpreter.test.js | 5 + src/generators/__tests__/lambda.test.js | 31 +- src/generators/lambda.js | 8 +- src/parsers/__tests__/lambda.test.js | 27 +- src/parsers/lambda.js | 27 +- src/types.js | 7 +- 8 files changed, 312 insertions(+), 148 deletions(-) diff --git a/package.json b/package.json index 4c908e84..ce9af30e 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,15 @@ "license": "MIT", "scripts": { "analyze": "yarn build --json | tac | tac | tail -n +3 > stats.json", - "analyze-bundle": "yarn analyze && yarn webpack-bundle-analyzer stats.json lib/", + "analyze-bundle": + "yarn analyze && yarn webpack-bundle-analyzer stats.json lib/", "test": "jest ./src", "build": "yarn webpack && yarn babel src/ -d lib/", "test:eslint": "yarn eslint src", "test:flow": "yarn flow check", "prepublish": "yarn run build", - "checks": "yarn --ignore-engines && madge --circular src && yarn run test:flow && yarn run test:eslint --fix && jscpd --path src -b --limit 0", + "checks": + "yarn --ignore-engines && madge --circular src && yarn run test:flow && yarn run test:eslint --fix && jscpd --path src -b --limit 0 && rm -r lib", "travis-deploy-once": "travis-deploy-once", "semantic-release": "semantic-release" }, diff --git a/src/__tests__/__snapshots__/interpreter.test.js.snap b/src/__tests__/__snapshots__/interpreter.test.js.snap index 2cf88b1e..da8371ed 100644 --- a/src/__tests__/__snapshots__/interpreter.test.js.snap +++ b/src/__tests__/__snapshots__/interpreter.test.js.snap @@ -111,6 +111,8 @@ exports[`interpreter correct target code map $ => {"original": $.foo} | "foo" 1` exports[`interpreter correct target code map $ => {"original": $.foo} | map $=> {"new": $} 1`] = `"(function(_) { return (function(input) { return (function (input) {return _.map((function(input) {return (_.objectify([[\\"new\\",input]]))}))(input)})(_.map((function(input) {return (_.objectify([[\\"original\\",input.foo]]))}))(input))})})"`; +exports[`interpreter correct target code map $foo => [$foo] 1`] = `"(function(_) { return (function(input) { return _.map((function(foo) {_ = _.assign('foo', foo, _); return [_.get('foo')]}))(input)})})"`; + exports[`interpreter correct target code map \\" " 1`] = `"(function(_) { return (function(input) { return _.map((function(input) {return \\" \\"}))(input)})})"`; exports[`interpreter correct target code null 1`] = `"(function(_) { return (function(input) { return null})})"`; @@ -853,21 +855,27 @@ Object { "offset": 28, }, "value": Object { - "end": Object { - "column": 40, - "line": 1, - "offset": 39, - }, - "name": "lambda", - "start": Object { - "column": 34, - "line": 1, - "offset": 33, - }, - "value": Object { - "name": "primitive", - "value": "4", + "definition": Object { + "end": Object { + "column": 40, + "line": 1, + "offset": 39, + }, + "name": "lambda", + "start": Object { + "column": 34, + "line": 1, + "offset": 33, + }, + "value": Object { + "definition": Object { + "name": "primitive", + "value": "4", + }, + "variable": "input", + }, }, + "variable": "input", }, }, }, @@ -955,8 +963,11 @@ Object { "offset": 38, }, "value": Object { - "name": "primitive", - "value": "4", + "definition": Object { + "name": "primitive", + "value": "4", + }, + "variable": "input", }, }, }, @@ -4134,31 +4145,34 @@ Object { "offset": 7, }, "value": Object { - "name": "binaryOperation", - "value": Array [ - Object { - "name": "inputProp", - "value": ".age", - }, - Object { - "end": Object { - "column": 20, - "line": 1, - "offset": 19, + "definition": Object { + "name": "binaryOperation", + "value": Array [ + Object { + "name": "inputProp", + "value": ".age", }, - "name": "primitive", - "start": Object { - "column": 18, - "line": 1, - "offset": 17, + Object { + "end": Object { + "column": 20, + "line": 1, + "offset": 19, + }, + "name": "primitive", + "start": Object { + "column": 18, + "line": 1, + "offset": 17, + }, + "value": ">=", }, - "value": ">=", - }, - Object { - "name": "primitive", - "value": "18", - }, - ], + Object { + "name": "primitive", + "value": "18", + }, + ], + }, + "variable": "input", }, }, }, @@ -4183,8 +4197,11 @@ Object { "offset": 29, }, "value": Object { - "name": "inputProp", - "value": ".name", + "definition": Object { + "name": "inputProp", + "value": ".name", + }, + "variable": "input", }, }, }, @@ -4239,38 +4256,41 @@ Object { "offset": 4, }, "value": Object { - "end": Object { - "column": 29, - "line": 1, - "offset": 28, - }, - "name": "object", - "start": Object { - "column": 10, - "line": 1, - "offset": 9, - }, - "value": Array [ - Object { - "name": "tuple", - "value": Array [ - Object { - "name": "primitive", - "value": "\\"original\\"", - }, - Object { - "name": "valueProp", - "value": Object { - "left": Object { - "name": "variable", - "value": "$", + "definition": Object { + "end": Object { + "column": 29, + "line": 1, + "offset": 28, + }, + "name": "object", + "start": Object { + "column": 10, + "line": 1, + "offset": 9, + }, + "value": Array [ + Object { + "name": "tuple", + "value": Array [ + Object { + "name": "primitive", + "value": "\\"original\\"", + }, + Object { + "name": "valueProp", + "value": Object { + "left": Object { + "name": "variable", + "value": "$", + }, + "right": ".foo", }, - "right": ".foo", }, - }, - ], - }, - ], + ], + }, + ], + }, + "variable": "input", }, }, }, @@ -4310,38 +4330,41 @@ Object { "offset": 4, }, "value": Object { - "end": Object { - "column": 29, - "line": 1, - "offset": 28, - }, - "name": "object", - "start": Object { - "column": 10, - "line": 1, - "offset": 9, - }, - "value": Array [ - Object { - "name": "tuple", - "value": Array [ - Object { - "name": "primitive", - "value": "\\"original\\"", - }, - Object { - "name": "valueProp", - "value": Object { - "left": Object { - "name": "variable", - "value": "$", + "definition": Object { + "end": Object { + "column": 29, + "line": 1, + "offset": 28, + }, + "name": "object", + "start": Object { + "column": 10, + "line": 1, + "offset": 9, + }, + "value": Array [ + Object { + "name": "tuple", + "value": Array [ + Object { + "name": "primitive", + "value": "\\"original\\"", + }, + Object { + "name": "valueProp", + "value": Object { + "left": Object { + "name": "variable", + "value": "$", + }, + "right": ".foo", }, - "right": ".foo", }, - }, - ], - }, - ], + ], + }, + ], + }, + "variable": "input", }, }, }, @@ -4366,32 +4389,35 @@ Object { "offset": 35, }, "value": Object { - "end": Object { - "column": 50, - "line": 1, - "offset": 49, - }, - "name": "object", - "start": Object { - "column": 40, - "line": 1, - "offset": 39, - }, - "value": Array [ - Object { - "name": "tuple", - "value": Array [ - Object { - "name": "primitive", - "value": "\\"new\\"", - }, - Object { - "name": "variable", - "value": "$", - }, - ], + "definition": Object { + "end": Object { + "column": 50, + "line": 1, + "offset": 49, }, - ], + "name": "object", + "start": Object { + "column": 40, + "line": 1, + "offset": 39, + }, + "value": Array [ + Object { + "name": "tuple", + "value": Array [ + Object { + "name": "primitive", + "value": "\\"new\\"", + }, + Object { + "name": "variable", + "value": "$", + }, + ], + }, + ], + }, + "variable": "input", }, }, }, @@ -4401,6 +4427,56 @@ Object { } `; +exports[`interpreter correct target tree map $foo => [$foo] 1`] = ` +Object { + "status": true, + "value": Object { + "name": "functionCall", + "value": Object { + "left": Object { + "name": "identifier", + "value": "map", + }, + "right": Object { + "end": Object { + "column": 19, + "line": 1, + "offset": 18, + }, + "name": "lambda", + "start": Object { + "column": 5, + "line": 1, + "offset": 4, + }, + "value": Object { + "definition": Object { + "end": Object { + "column": 19, + "line": 1, + "offset": 18, + }, + "name": "list", + "start": Object { + "column": 13, + "line": 1, + "offset": 12, + }, + "value": Array [ + Object { + "name": "variable", + "value": "$foo", + }, + ], + }, + "variable": "foo", + }, + }, + }, + }, +} +`; + exports[`interpreter correct target tree map \\" " 1`] = ` Object { "status": true, @@ -4424,8 +4500,11 @@ Object { "offset": 4, }, "value": Object { - "name": "primitive", - "value": "\\" \\"", + "definition": Object { + "name": "primitive", + "value": "\\" \\"", + }, + "variable": "input", }, }, }, @@ -4488,8 +4567,11 @@ Object { "offset": 7, }, "value": Object { - "name": "inputProp", - "value": ".age", + "definition": Object { + "name": "inputProp", + "value": ".age", + }, + "variable": "input", }, }, }, @@ -4514,8 +4596,11 @@ Object { "offset": 19, }, "value": Object { - "name": "inputProp", - "value": ".name", + "definition": Object { + "name": "inputProp", + "value": ".name", + }, + "variable": "input", }, }, }, diff --git a/src/__tests__/interpreter.test.js b/src/__tests__/interpreter.test.js index 8361bc08..611ebd47 100644 --- a/src/__tests__/interpreter.test.js +++ b/src/__tests__/interpreter.test.js @@ -291,6 +291,11 @@ const tests = [ { sourceCode: `([1, 2] | foo) where $foo = $reverse`, output: [2, 1] + }, + { + sourceCode: `map $foo => [$foo]`, + input: [1, 2], + output: [[1], [2]] } ] diff --git a/src/generators/__tests__/lambda.test.js b/src/generators/__tests__/lambda.test.js index 811620f3..37c9b9af 100644 --- a/src/generators/__tests__/lambda.test.js +++ b/src/generators/__tests__/lambda.test.js @@ -6,8 +6,11 @@ describe('lambda generator', () => { lambda({ name: 'lambda', value: { - name: 'primitive', - value: '4' + variable: 'input', + definition: { + name: 'primitive', + value: '4' + } } }) ).toEqual(`(function(input) {return 4})`) @@ -18,10 +21,30 @@ describe('lambda generator', () => { lambda({ name: 'lambda', value: { - name: 'primitive', - value: '8' + variable: 'input', + definition: { + name: 'primitive', + value: '8' + } } }) ).toEqual(`(function(input) {return 8})`) }) + + it('returns correct code', () => { + expect( + lambda({ + name: 'lambda', + value: { + variable: 'foobar3', + definition: { + name: 'primitive', + value: '8' + } + } + }) + ).toEqual( + `(function(foobar3) {_ = _.assign('foobar3', foobar3, _); return 8})` + ) + }) }) diff --git a/src/generators/lambda.js b/src/generators/lambda.js index e2483119..4feae4e6 100644 --- a/src/generators/lambda.js +++ b/src/generators/lambda.js @@ -3,7 +3,11 @@ import type { LambdaNodeType, GeneratedCodeType } from '../types' export default ({ value }: LambdaNodeType): GeneratedCodeType => - `(function(input) {return ${((): GeneratedCodeType => { + `(function(${value.variable}) {${ + value.variable === 'input' + ? '' + : `_ = _.assign('${value.variable}', ${value.variable}, _); ` + }return ${((): GeneratedCodeType => { const Generator = require('./generator').default - return Generator(value) + return Generator(value.definition) })()}})` diff --git a/src/parsers/__tests__/lambda.test.js b/src/parsers/__tests__/lambda.test.js index 28d2129e..70636177 100644 --- a/src/parsers/__tests__/lambda.test.js +++ b/src/parsers/__tests__/lambda.test.js @@ -6,6 +6,13 @@ describe('lambda parser', () => { expect(parser.parse(`$ => 1`).status).toBe(true) }) + it('parses correct string - syntax 1 - custom variable name', () => { + expect(parser.parse(`$foo => $foo`).status).toBe(true) + expect(parser.parse(`$bar => $baz => "Hello" + $bar + $baz`).status).toBe( + true + ) + }) + it('parses correct string - syntax 2', () => { expect(parser.parse(`\\3 + 4`).status).toBe(true) expect(parser.parse(`\\1`).status).toBe(true) @@ -15,8 +22,24 @@ describe('lambda parser', () => { expect(parser.parse('$ => 4').value).toMatchObject({ name: 'lambda', value: { - name: 'primitive', - value: '4' + variable: 'input', + definition: { + name: 'primitive', + value: '4' + } + } + }) + }) + + it('returns correct string - custom variable name', () => { + expect(parser.parse('$foo => 4').value).toMatchObject({ + name: 'lambda', + value: { + variable: 'foo', + definition: { + name: 'primitive', + value: '4' + } } }) }) diff --git a/src/parsers/lambda.js b/src/parsers/lambda.js index 2a14ef89..05e21558 100644 --- a/src/parsers/lambda.js +++ b/src/parsers/lambda.js @@ -3,16 +3,33 @@ import P from 'parsimmon' import crap from './crap' import TupleParser from './tuple/tuple' +import IdentifierParser from './identifier' + +import type { LambdaNodeValueType, NodeType, VariableNodeType } from '../types' export default P.alt( - P.string('$') - .then(crap) - .then(P.string('=>')) - .then(crap) - .then(TupleParser), + P.seq( + P.string('$') + .then(IdentifierParser.atMost(1)) + .skip(crap) + .skip(P.string('=>')), + crap.then(TupleParser) + ).map( + ([variable, definition]: [ + Array, + NodeType + ]): LambdaNodeValueType => ({ + variable: variable.length ? variable[0].value : 'input', + definition + }) + ), P.string('\\') .then(crap) .then(TupleParser) + .map((definition: NodeType): LambdaNodeValueType => ({ + variable: 'input', + definition + })) ) .trim(crap) .node('lambda') diff --git a/src/types.js b/src/types.js index 46fc2b1c..46ef5d10 100644 --- a/src/types.js +++ b/src/types.js @@ -91,9 +91,14 @@ export type FunctionCallNodeType = {| } |}; +export type LambdaNodeValueType = {| + variable: string, + definition: NodeType // eslint-disable-line no-use-before-define +|}; + export type LambdaNodeType = {| name: 'lambda', - value: NodeType // eslint-disable-line no-use-before-define + value: LambdaNodeValueType |}; export type ProjectionNodeType = {|