From 64bd12bd5e06306f4cfc7abce5a428dc60d41669 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Wed, 7 Jun 2017 17:11:02 -0700 Subject: [PATCH 1/7] Pattern Matching WIP --- src/index.js | 197 +++++++++++++++++- .../match/basic-placeholder/actual.js | 7 + .../match/basic-placeholder/expected.js | 13 ++ test/fixtures/match/basic-statement/actual.js | 7 + .../match/basic-statement/expected.js | 7 + .../match/basic-value-checks/actual.js | 7 + .../match/basic-value-checks/expected.js | 13 ++ test/fixtures/match/basic/actual.js | 7 + test/fixtures/match/basic/expected.js | 9 + test/fixtures/match/no-else/actual.js | 5 + test/fixtures/match/no-else/expected.js | 5 + .../fixtures/match/single-else-expr/actual.js | 4 + .../match/single-else-expr/expected.js | 6 + .../fixtures/match/single-else-stmt/actual.js | 4 + .../match/single-else-stmt/expected.js | 4 + 15 files changed, 291 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/match/basic-placeholder/actual.js create mode 100644 test/fixtures/match/basic-placeholder/expected.js create mode 100644 test/fixtures/match/basic-statement/actual.js create mode 100644 test/fixtures/match/basic-statement/expected.js create mode 100644 test/fixtures/match/basic-value-checks/actual.js create mode 100644 test/fixtures/match/basic-value-checks/expected.js create mode 100644 test/fixtures/match/basic/actual.js create mode 100644 test/fixtures/match/basic/expected.js create mode 100644 test/fixtures/match/no-else/actual.js create mode 100644 test/fixtures/match/no-else/expected.js create mode 100644 test/fixtures/match/single-else-expr/actual.js create mode 100644 test/fixtures/match/single-else-expr/expected.js create mode 100644 test/fixtures/match/single-else-stmt/actual.js create mode 100644 test/fixtures/match/single-else-stmt/expected.js diff --git a/src/index.js b/src/index.js index a109a62..5365e68 100644 --- a/src/index.js +++ b/src/index.js @@ -255,9 +255,9 @@ export default function (babel) { return body; } - function ensureBlockBody(path) { - if (!t.isBlockStatement(path.node.body)) { - path.get("body").replaceWith(t.blockStatement([path.node.body])); + function ensureBlockBody(path, bodyKey = "body") { + if (!path.get(bodyKey).isBlockStatement()) { + path.get(bodyKey).replaceWith(t.blockStatement([path.node[bodyKey]])); } } @@ -663,7 +663,7 @@ export default function (babel) { } } - function generateForInIterator (path, type: "array" | "object") { + function generateForInIterator(path, type: "array" | "object") { const idx = path.node.idx || path.scope.generateUidIdentifier("i"); const len = path.scope.generateUidIdentifier("len"); @@ -758,6 +758,117 @@ export default function (babel) { return t.forStatement(init, test, update, path.node.body); } + function addAlternateToIfChain(rootIf, alternate) { + let tail = rootIf + while (tail.alternate) tail = tail.alternate; + tail.alternate = alternate; + return rootIf; + } + + function isUndefined(path) { + return path.isIdentifier() && path.node.name === "undefined"; + } + + function isSignedNumber(path) { + return path.isUnaryExpression() && + path.get("argument").isNumericLiteral() && + (path.node.operator === "+" || path.node.operator === "-"); + } + + function looksLikeClassName(path) { + // disallow Foo.Bar for now. + if (!path.isIdentifier()) return false; + const { name } = path.node; + if (name[0].toUpperCase() === name[0]) { + // A -> true + if (name.length === 1) return true; + // ABC -> false, it's a constant + if (name.toUpperCase() === name) return false; + // Abc -> true + return true; + } + return false; + } + + function isPrimitiveClass({ node: { name }}) { + switch (name) { + case "Number": + case "String": + case "Boolean": + return true; + default: + return false; + } + } + + function transformMatchCaseTest(path, argRef) { + if (path.isLogicalExpression()) { + transformMatchCaseTest(path.get("left"), argRef); + transformMatchCaseTest(path.get("right"), argRef); + } else if (path.isUnaryExpression() && path.node.operator === "!") { + transformMatchCaseTest(path.get("argument"), argRef); + } else if (path.isRegExpLiteral()) { + const testCall = t.callExpression( + t.memberExpression(path.node, t.identifier("test")), + [argRef] + ); + path.replaceWith(testCall); + } else if (looksLikeClassName(path)) { + const classInstanceCheck = isPrimitiveClass(path) + ? t.binaryExpression("===", + t.unaryExpression("typeof", argRef), + t.stringLiteral(path.node.name.toLowerCase()) + ) + : t.binaryExpression("instanceof", argRef, path.node) + path.replaceWith(classInstanceCheck); + } else if (path.isLiteral() || isUndefined(path) || isSignedNumber(path)) { + const isEq = t.binaryExpression("===", argRef, path.node); + path.replaceWith(isEq); + } + } + + function transformMatchCases(argRef, cases) { + return cases.reduce((rootIf, path) => { + + // fill in placeholders + path.get("test").traverse({ + PlaceholderExpression(placeholderPath) { + placeholderPath.replaceWith(argRef); + } + }); + + // add in ===, etc + transformMatchCaseTest(path.get("test"), argRef); + + // add binding (and always use block bodies) + ensureBlockBody(path, "consequent"); + if (path.node.binding) { + const bindingDecl = t.variableDeclaration("const", [ + t.variableDeclarator(path.node.binding, argRef) + ]); + path.get("consequent").unshiftContainer("body", bindingDecl); + } + + // handle `else` + if (path.node.test.type === "MatchElse") { + if (rootIf) { + return addAlternateToIfChain(rootIf, path.node.consequent); + } else { + // single match case with "else", weird. Just generate an anonymous block for now. + return path.node.consequent; + } + } + + // generate `if` and append to if-else chain + const ifStmt = t.ifStatement(path.node.test, path.node.consequent); + if (!rootIf) { + return ifStmt; + } else { + return addAlternateToIfChain(rootIf, ifStmt); + } + }, null); + } + // TYPE DEFINITIONS definePluginType("ForInArrayStatement", { visitor: ["idx", "elem", "array", "body"], @@ -960,6 +1071,55 @@ export default function (babel) { aliases: ["MemberExpression", "Expression", "LVal"], }); + definePluginType("MatchExpression", { + builder: ["discriminant", "cases"], + visitor: ["discriminant", "cases"], + aliases: ["Expression", "Conditional"], + fields: { + discriminant: { + validate: assertNodeType("Expression") + }, + cases: { + validate: chain(assertValueType("array"), assertEach(assertNodeType("MatchCase"))) + } + } + }); + + definePluginType("MatchStatement", { + builder: ["discriminant", "cases"], + visitor: ["discriminant", "cases"], + aliases: ["Statement", "Conditional"], + fields: { + discriminant: { + validate: assertNodeType("Expression") + }, + cases: { + validate: chain(assertValueType("array"), assertEach(assertNodeType("MatchCase"))) + } + } + }); + + definePluginType("MatchCase", { + builder: ["test", "consequent", "functional"], + visitor: ["test", "consequent"], + fields: { + test: { + validate: assertNodeType("Expression", "MatchElse") + }, + consequent: { + validate: assertNodeType("BlockStatement", "ExpressionStatement") + } + } + }); + + definePluginType("MatchElse", { + // only allowed in MatchCase, so don't alias to Expression + }); + + definePluginType("PlaceholderExpression", { + aliases: ["Expression"] + }); + // traverse as top-level item so as to run before other babel plugins // (and avoid traversing any of their output) function Program(path, state) { @@ -1258,6 +1418,35 @@ export default function (babel) { } }, + MatchExpression(path) { + const { discriminant } = path.node; + + const argRef = path.scope.generateUidIdentifier("it"); + const matchBody = transformMatchCases(argRef, path.get("cases")); + + const iife = t.callExpression( + t.arrowFunctionExpression([argRef], t.blockStatement([matchBody])), + [discriminant] + ); + path.replaceWith(iife); + }, + + MatchStatement(path) { + const { discriminant } = path.node; + + let argRef; + if (t.isIdentifier(discriminant)) { + argRef = discriminant; + } else { + argRef = path.scope.generateUidIdentifier("it"); + path.insertBefore(t.variableDeclaration("const", [ + t.variableDeclarator(argRef, discriminant) + ])); + } + + const matchBody = transformMatchCases(argRef, path.get("cases")); + path.replaceWith(matchBody); + }, }); diff --git a/test/fixtures/match/basic-placeholder/actual.js b/test/fixtures/match/basic-placeholder/actual.js new file mode 100644 index 0000000..61c0908 --- /dev/null +++ b/test/fixtures/match/basic-placeholder/actual.js @@ -0,0 +1,7 @@ +match x: + | < 1: "lt one" + | + 1 == 1: "eq zero" + | == 2: "eq two" + | ~f(): "f(x) truthy" + | .prop: "has prop" + | .0: "has first child" diff --git a/test/fixtures/match/basic-placeholder/expected.js b/test/fixtures/match/basic-placeholder/expected.js new file mode 100644 index 0000000..d0d1511 --- /dev/null +++ b/test/fixtures/match/basic-placeholder/expected.js @@ -0,0 +1,13 @@ +if (x < 1) { + "lt one"; + } else if (x + 1 === 1) { + "eq zero"; + } else if (x === 2) { + "eq two"; + } else if (f(x)) { + "f(x) truthy"; + } else if (x.prop) { + "has prop"; + } else if (x[0]) { + "has first child"; + } diff --git a/test/fixtures/match/basic-statement/actual.js b/test/fixtures/match/basic-statement/actual.js new file mode 100644 index 0000000..338ac70 --- /dev/null +++ b/test/fixtures/match/basic-statement/actual.js @@ -0,0 +1,7 @@ +match x: + | 1: + "one" + | 2: + "two" + | else: + "idk" diff --git a/test/fixtures/match/basic-statement/expected.js b/test/fixtures/match/basic-statement/expected.js new file mode 100644 index 0000000..b4a04a2 --- /dev/null +++ b/test/fixtures/match/basic-statement/expected.js @@ -0,0 +1,7 @@ +if (x === 1) { + "one"; +} else if (x === 2) { + "two"; +} else { + "idk"; +} diff --git a/test/fixtures/match/basic-value-checks/actual.js b/test/fixtures/match/basic-value-checks/actual.js new file mode 100644 index 0000000..0a15e2b --- /dev/null +++ b/test/fixtures/match/basic-value-checks/actual.js @@ -0,0 +1,7 @@ +match x: + | 1: "lt one" + | "hi": "eq zero" + | `there ${1 + 1}`: "eq two" + | ~f(): "f(x) truthy" + | .prop: "has prop" + | .0: "has first child" diff --git a/test/fixtures/match/basic-value-checks/expected.js b/test/fixtures/match/basic-value-checks/expected.js new file mode 100644 index 0000000..9cef0c8 --- /dev/null +++ b/test/fixtures/match/basic-value-checks/expected.js @@ -0,0 +1,13 @@ +if (x === 1) { + "lt one"; + } else if (x === "hi") { + "eq zero"; + } else if (x === `there ${ 1 + 1 }`) { + "eq two"; + } else if (f(x)) { + "f(x) truthy"; + } else if (x.prop) { + "has prop"; + } else if (x[0]) { + "has first child"; + } diff --git a/test/fixtures/match/basic/actual.js b/test/fixtures/match/basic/actual.js new file mode 100644 index 0000000..4f81cd7 --- /dev/null +++ b/test/fixtures/match/basic/actual.js @@ -0,0 +1,7 @@ +z = match x: + | 1: + "one" + | 2: + "two" + | else: + "idk" diff --git a/test/fixtures/match/basic/expected.js b/test/fixtures/match/basic/expected.js new file mode 100644 index 0000000..d1f0004 --- /dev/null +++ b/test/fixtures/match/basic/expected.js @@ -0,0 +1,9 @@ +const z = (_it => { + if (_it === 1) { + return "one"; + } else if (_it === 2) { + return "two"; + } else { + return "idk"; + } +})(x); diff --git a/test/fixtures/match/no-else/actual.js b/test/fixtures/match/no-else/actual.js new file mode 100644 index 0000000..348bdce --- /dev/null +++ b/test/fixtures/match/no-else/actual.js @@ -0,0 +1,5 @@ +match x: + | 1: + "one" + | 2: + "two" diff --git a/test/fixtures/match/no-else/expected.js b/test/fixtures/match/no-else/expected.js new file mode 100644 index 0000000..4083116 --- /dev/null +++ b/test/fixtures/match/no-else/expected.js @@ -0,0 +1,5 @@ +if (x === 1) { + "one"; +} else if (x === 2) { + "two"; +} diff --git a/test/fixtures/match/single-else-expr/actual.js b/test/fixtures/match/single-else-expr/actual.js new file mode 100644 index 0000000..9715f57 --- /dev/null +++ b/test/fixtures/match/single-else-expr/actual.js @@ -0,0 +1,4 @@ +z = match x: + | else: + try {} finally {} + "idk" diff --git a/test/fixtures/match/single-else-expr/expected.js b/test/fixtures/match/single-else-expr/expected.js new file mode 100644 index 0000000..0c81fe4 --- /dev/null +++ b/test/fixtures/match/single-else-expr/expected.js @@ -0,0 +1,6 @@ +const z = (_it => { + { + try {} finally {} + return "idk"; + } +})(x); diff --git a/test/fixtures/match/single-else-stmt/actual.js b/test/fixtures/match/single-else-stmt/actual.js new file mode 100644 index 0000000..e850ffb --- /dev/null +++ b/test/fixtures/match/single-else-stmt/actual.js @@ -0,0 +1,4 @@ +match x: + | else: + try {} finally {} + "idk" diff --git a/test/fixtures/match/single-else-stmt/expected.js b/test/fixtures/match/single-else-stmt/expected.js new file mode 100644 index 0000000..d1febc5 --- /dev/null +++ b/test/fixtures/match/single-else-stmt/expected.js @@ -0,0 +1,4 @@ +{ + try {} finally {} + "idk"; +} From a4f700cf95c1271aa9a97e68ad001e59819eebd1 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Thu, 8 Jun 2017 15:24:53 -0700 Subject: [PATCH 2/7] wip --- src/index.js | 145 ++++++++++++++++++-- src/stdlib.js | 21 +++ test/fixtures/match/arr-pattern/actual.js | 18 +++ test/fixtures/match/arr-pattern/exec.js | 92 +++++++++++++ test/fixtures/match/arr-pattern/expected.js | 27 ++++ test/fixtures/match/obj-pattern/actual.js | 14 ++ test/fixtures/match/obj-pattern/exec.js | 36 +++++ test/fixtures/match/obj-pattern/expected.js | 31 +++++ 8 files changed, 376 insertions(+), 8 deletions(-) create mode 100644 test/fixtures/match/arr-pattern/actual.js create mode 100644 test/fixtures/match/arr-pattern/exec.js create mode 100644 test/fixtures/match/arr-pattern/expected.js create mode 100644 test/fixtures/match/obj-pattern/actual.js create mode 100644 test/fixtures/match/obj-pattern/exec.js create mode 100644 test/fixtures/match/obj-pattern/expected.js diff --git a/src/index.js b/src/index.js index 5365e68..c225454 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ import { parse } from "babylon-lightscript"; -import { defaultImports, lightscriptImports, lodashImports } from "./stdlib"; +import { defaultImports, lightscriptImports, lodashImports, runtimeHelpers } from "./stdlib"; export default function (babel) { const { types: t } = babel; @@ -580,6 +580,15 @@ export default function (babel) { } } + function collectRuntimeHelper(path, helperName) { + const programScope = path.scope.getProgramParent(); + const helpers = programScope.lscRuntimeHelpers; + if (!helpers[helperName]) { + helpers[helperName] = programScope.generateUidIdentifier(helperName); + } + return helpers[helperName]; + } + function makeInlineStdlibFn(inlineFnName) { const fnId = t.identifier(inlineFnName); const aParam = t.identifier("a"); @@ -608,6 +617,16 @@ export default function (babel) { ])); } + function insertAfterImports(path, nodes) { + // insert inline fns before the first statement which isn't an import statement + for (const p of path.get("body")) { + if (!p.isImportDeclaration()) { + p.insertBefore(nodes); + break; + } + } + } + function insertStdlibImports(path, imports: Imports, useRequire) { const declarations = []; const inlines = []; @@ -653,14 +672,21 @@ export default function (babel) { for (const inlineFnName of inlines) { inlineDeclarations.push(makeInlineStdlibFn(inlineFnName)); } - // insert inline fns before the first statement which isn't an import statement - for (const p of path.get("body")) { - if (!p.isImportDeclaration()) { - p.insertBefore(inlineDeclarations); - break; - } - } + insertAfterImports(path, inlineDeclarations); + } + } + + function insertRuntimeHelpers(path) { + const helpers = []; + for (const helperName in path.scope.lscRuntimeHelpers) { + const fn = runtimeHelpers[helperName]; + const uid = path.scope.lscRuntimeHelpers[helperName]; + const fnAST = babel.template(fn.toString())({ + [helperName]: uid, + }); + helpers.push(fnAST); } + insertAfterImports(path, helpers); } function generateForInIterator(path, type: "array" | "object") { @@ -827,6 +853,103 @@ export default function (babel) { } } + function extendAndChain(andChainPath, condition) { + if (!andChainPath.node) { + andChainPath.replaceWith(condition); + } else { + andChainPath.replaceWith(t.logicalExpression("&&", andChainPath.node, condition)); + } + } + + function buildAnd(left, right) { + if (left && right) { + return t.logicalExpression("&&", left, right); + } else if (left) { + return left; + } else if (right) { + return right; + } else { + return t.booleanLiteral(true); + } + } + + function buildTestForBinding(test, bindingPath, argRef) { + if (bindingPath.isObjectPattern()) { + const isObjUid = collectRuntimeHelper(bindingPath, "hasProps"); + + const propsToCheck = []; + const childPatterns = []; // list of [propName, path] tuples + for (const propPath of bindingPath.get("properties")) { + const propName = propPath.get("key").node.name; + + if (propPath.get("value").isAssignmentPattern()) { + if (propPath.get("value.left").isPattern()) { + childPatterns.push([propName, propPath.get("value.left"), propPath.get("value.right").node]); + } + } else { + const propStr = t.stringLiteral(propName); + propsToCheck.push(propStr); + + if (propPath.get("value").isPattern()) { + childPatterns.push([propName, propPath.get("value")]); + } + } + } + + const isObjCall = t.callExpression(isObjUid, [argRef, t.arrayExpression(propsToCheck)]); + test = buildAnd(test, isObjCall); + + for (const [ propName, childPatternPath, defaultObj = null ] of childPatterns) { + const propertyArgRef = t.memberExpression(argRef, t.identifier(propName)); + + if (defaultObj) { + test = buildAnd(test, t.logicalExpression("||", + buildTestForBinding(null, childPatternPath, propertyArgRef), + buildTestForBinding(null, childPatternPath, defaultObj) + )); + } else { + test = buildTestForBinding(test, childPatternPath, propertyArgRef); + } + } + } else if (bindingPath.isArrayPattern()) { + const hasLengthUid = collectRuntimeHelper(bindingPath, "hasLength"); + + const childPatterns = []; // list of [index, path] tuples. + let minLength = 0; + let maxLength = 0; + bindingPath.get("elements").forEach((elemPath, i) => { + if (elemPath.isAssignmentPattern()) { + ++maxLength; + if (elemPath.get("left").isPattern()) { + childPatterns.push([i, elemPath.get("left")]); + } + } else if (elemPath.isRestElement()) { + maxLength = null; + } else { + ++minLength; + ++maxLength; + if (elemPath.isPattern()) { + childPatterns.push([i, elemPath]); + } + } + }); + + const hasLengthCall = t.callExpression(hasLengthUid, [ + argRef, + t.numericLiteral(minLength), + maxLength === null ? null : t.numericLiteral(maxLength) + ].filter(x => x !== null)); + test = buildAnd(test, hasLengthCall); + + for (const [index, childPatternPath] of childPatterns) { + const elementArgRef = t.memberExpression(argRef, t.numericLiteral(index), true); + test = buildTestForBinding(test, childPatternPath, elementArgRef); + } + } else throw new TypeError(`Expected Pattern, got ${bindingPath.node.type}`); + + return test; + } + function transformMatchCases(argRef, cases) { return cases.reduce((rootIf, path) => { @@ -843,6 +966,10 @@ export default function (babel) { // add binding (and always use block bodies) ensureBlockBody(path, "consequent"); if (path.node.binding) { + const bindingTest = buildTestForBinding(null, path.get("binding"), argRef); + const testWithBindingTest = buildAnd(path.get("test").node, bindingTest) + path.get("test").replaceWith(testWithBindingTest); + const bindingDecl = t.variableDeclaration("const", [ t.variableDeclarator(path.node.binding, argRef) ]); @@ -1128,6 +1255,7 @@ export default function (babel) { const stdlib: Stdlib = initializeStdlib(state.opts); const useRequire = state.opts.stdlib && state.opts.stdlib.require === true; const imports: Imports = {}; + path.scope.lscRuntimeHelpers = {}; path.traverse({ @@ -1451,6 +1579,7 @@ export default function (babel) { }); insertStdlibImports(path, imports, useRequire); + insertRuntimeHelpers(path); } return { diff --git a/src/stdlib.js b/src/stdlib.js index 42197c2..434342e 100644 --- a/src/stdlib.js +++ b/src/stdlib.js @@ -11,6 +11,27 @@ export const lightscriptImports = { "bitwiseZeroFillRightShift": "inline", }; +export const runtimeHelpers = { + hasProps: function hasProps(obj, props) { + return ( + obj != null && + (typeof obj === "object" || typeof obj === "function") && + props.filter(prop => prop in obj).length === props.length + ); + }, + hasLength: function hasLength(arr, minLength, maxLength) { + minLength = minLength || 0; + maxLength = maxLength != null ? maxLength : Number.MAX_SAFE_INTEGER; + return ( + arr != null && + typeof arr !== "function" && + arr.length === arr.length|0 && + arr.length >= minLength && + arr.length <= maxLength + ); + }, +}; + export const everyLodashMethod = [ "add", "after", diff --git a/test/fixtures/match/arr-pattern/actual.js b/test/fixtures/match/arr-pattern/actual.js new file mode 100644 index 0000000..8a4c92d --- /dev/null +++ b/test/fixtures/match/arr-pattern/actual.js @@ -0,0 +1,18 @@ +match x: + | []: + "empty" + | [ a, b ]: + a - b + | [ a, b = 2 ]: + a + b - 2 + | [ a, ...b ]: + b.concat(a) + | [ + [ + b + d = 'e' + ] + [ g, , h ] + ...j + ]: + [b, d, g, ...j].join('') diff --git a/test/fixtures/match/arr-pattern/exec.js b/test/fixtures/match/arr-pattern/exec.js new file mode 100644 index 0000000..db39428 --- /dev/null +++ b/test/fixtures/match/arr-pattern/exec.js @@ -0,0 +1,92 @@ +assert.equal( + "empty", + match []: + | []: + "empty" +) +assert.equal( + "empty", + match []: + | []: + "empty" +) +assert.equal( + undefined, + match []: + | [a]: + a + 1 +) +assert.equal( + undefined, + match [1]: + | [a, b]: + a + b +) +assert.equal( + 5, + match [1]: + | [a, b = 4]: + a + b +) +assert.equal( + 3, + match [1, 2]: + | [a, b]: + a + b +) +assert.equal( + 4, + match [1, 2, 3]: + | [a,, b]: + a + b +) +assert.equal( + undefined, + match [1, 2]: + | [a,, b]: + a + b +) +assert.deepEqual( + [2, 3, 1], + match [1, 2, 3]: + | [a, ...b]: + b.concat(a) +) +assert.deepEqual( + [1, 4], + match [4]: + | [a, b = 1, ...c]: + c.concat([b, a]) +) +assert.deepEqual( + [6, 7, 5, 4], + match [4, 5, 6, 7]: + | [a, b = 1, ...c]: + c.concat([b, a]) +) +assert.deepEqual( + [1, 2, 4, 6, 7, 8], + match [[1], [4, 5, 6], 7, 8]: + | [ + [ + b + d = 2 + ] + [ g, , h ] + ...j + ]: + [b, d, g, h, ...j] +) +assert.deepEqual( + undefined, + match [[1], [4, 5], 7, 8]: + | [ + [ + b + d = 2 + ] + [ g, , h ] + ...j + ]: + [b, d, g, h, ...j] +) diff --git a/test/fixtures/match/arr-pattern/expected.js b/test/fixtures/match/arr-pattern/expected.js new file mode 100644 index 0000000..97e2a6c --- /dev/null +++ b/test/fixtures/match/arr-pattern/expected.js @@ -0,0 +1,27 @@ +function _hasLength(arr, minLength, maxLength) { + minLength = minLength || 0; + maxLength = maxLength != null ? maxLength : Number.MAX_SAFE_INTEGER; + return arr != null && typeof arr !== "function" && arr.length === arr.length | 0 && arr.length >= minLength && arr.length <= maxLength; +} + +if (_hasLength(x, 0, 0)) { + const [] = x; + + "empty"; +} else if (_hasLength(x, 2, 2)) { + const [a, b] = x; + + a - b; +} else if (_hasLength(x, 1, 2)) { + const [a, b = 2] = x; + + a + b - 2; +} else if (_hasLength(x, 1)) { + const [a, ...b] = x; + + b.concat(a); +} else if (_hasLength(x, 2) && _hasLength(x[0], 1, 2) && _hasLength(x[1], 3, 3)) { + const [[b, d = 'e'], [g,, h], ...j] = x; + + [b, d, g, ...j].join(''); +} diff --git a/test/fixtures/match/obj-pattern/actual.js b/test/fixtures/match/obj-pattern/actual.js new file mode 100644 index 0000000..5ca3636 --- /dev/null +++ b/test/fixtures/match/obj-pattern/actual.js @@ -0,0 +1,14 @@ +match x: + | { a }: + a + | { a, b }: + a + b + | { a, b = 1 }: + a + b + | { a, b: { ba, bb = 1 }, c: { ca, cb: { cba } } }: + a + ba + bb + ca + cba + | 1 or 2 with { a, b: { c } }: + a + c + | { a: { b: { c } } = otherObj }: + c + //TODO: | { a, ...b }: b diff --git a/test/fixtures/match/obj-pattern/exec.js b/test/fixtures/match/obj-pattern/exec.js new file mode 100644 index 0000000..65fbdb6 --- /dev/null +++ b/test/fixtures/match/obj-pattern/exec.js @@ -0,0 +1,36 @@ +assert.equal( + 1, + match { a: 1 }: + | { a }: + a +) +assert.equal( + undefined, + match { b: 1 }: + | { a }: + a +) +assert.equal( + 3, + match { a: 1 }: + | { a, b = 2 }: + a + b +) +assert.equal( + undefined, + match { b: 1 }: + | { a, b = 2 }: + a + b +) +assert.equal( + 3, + match { a: 1, b: { c: 2 } }: + | { a, b: { c } }: + a + c +) +assert.equal( + undefined, + match { a: 1 }: + | { a, b: { c } }: + a + c +) diff --git a/test/fixtures/match/obj-pattern/expected.js b/test/fixtures/match/obj-pattern/expected.js new file mode 100644 index 0000000..2302b8c --- /dev/null +++ b/test/fixtures/match/obj-pattern/expected.js @@ -0,0 +1,31 @@ +function _hasProps(obj, props) { + return obj != null && (typeof obj === "object" || typeof obj === "function") && props.filter(function (prop) { + return prop in obj; + }).length === props.length; +} + +if (_hasProps(x, ["a"])) { + const { a } = x; + + a; +} else if (_hasProps(x, ["a", "b"])) { + const { a, b } = x; + + a + b; +} else if (_hasProps(x, ["a"])) { + const { a, b = 1 } = x; + + a + b; +} else if (_hasProps(x, ["a", "b", "c"]) && _hasProps(x.b, ["ba"]) && _hasProps(x.c, ["ca", "cb"]) && _hasProps(x.c.cb, ["cba"])) { + const { a, b: { ba, bb = 1 }, c: { ca, cb: { cba } } } = x; + + a + ba + bb + ca + cba; +} else if ((x === 1 || x === 2) && _hasProps(x, ["a", "b"]) && _hasProps(x.b, ["c"])) { + const { a, b: { c } } = x; + + a + c; +} else if (_hasProps(x, []) && (_hasProps(x.a, ["b"]) && _hasProps(x.a.b, ["c"]) || _hasProps(otherObj, ["b"]) && _hasProps(otherObj.b, ["c"]))) { + const { a: { b: { c } } = otherObj } = x; + + c; +} //TODO: | { a, ...b }: b From 4c3d2df95adec550f6c449348a86aa9d846eaa47 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Thu, 8 Jun 2017 22:15:04 -0700 Subject: [PATCH 3/7] wip more tests --- .../match/basic-value-checks/actual.js | 24 ++++++++++---- .../match/basic-value-checks/expected.js | 32 +++++++++++-------- test/fixtures/match/not/actual.js | 5 +++ test/fixtures/match/not/expected.js | 5 +++ 4 files changed, 47 insertions(+), 19 deletions(-) create mode 100644 test/fixtures/match/not/actual.js create mode 100644 test/fixtures/match/not/expected.js diff --git a/test/fixtures/match/basic-value-checks/actual.js b/test/fixtures/match/basic-value-checks/actual.js index 0a15e2b..8a68ae6 100644 --- a/test/fixtures/match/basic-value-checks/actual.js +++ b/test/fixtures/match/basic-value-checks/actual.js @@ -1,7 +1,19 @@ match x: - | 1: "lt one" - | "hi": "eq zero" - | `there ${1 + 1}`: "eq two" - | ~f(): "f(x) truthy" - | .prop: "has prop" - | .0: "has first child" + | 1 or 0.1 or 0x11 or +1 or -1: + it + | "hi": + it + | `there ${1 + 1}`: + it + | /\s+/: + it + | Number or Boolean or String: + it + | Array or Object or Map or Foo: + it + | null or undefined: + it + | x or +x: + it + | not 1 or not x: + it diff --git a/test/fixtures/match/basic-value-checks/expected.js b/test/fixtures/match/basic-value-checks/expected.js index 9cef0c8..6dec644 100644 --- a/test/fixtures/match/basic-value-checks/expected.js +++ b/test/fixtures/match/basic-value-checks/expected.js @@ -1,13 +1,19 @@ -if (x === 1) { - "lt one"; - } else if (x === "hi") { - "eq zero"; - } else if (x === `there ${ 1 + 1 }`) { - "eq two"; - } else if (f(x)) { - "f(x) truthy"; - } else if (x.prop) { - "has prop"; - } else if (x[0]) { - "has first child"; - } +if (x === 1 || x === 0.1 || x === 0x11 || x === +1 || x === -1) { + it; +} else if (x === "hi") { + it; +} else if (x === `there ${ 1 + 1 }`) { + it; +} else if (/\s+/.test(x)) { + it; +} else if (typeof x === "number" || typeof x === "boolean" || typeof x === "string") { + it; +} else if (x instanceof Array || x instanceof Object || x instanceof Map || x instanceof Foo) { + it; +} else if (x === null || x === undefined) { + it; +} else if (x || +x) { + it; +} else if (!(x === 1) || !x) { + it; +} diff --git a/test/fixtures/match/not/actual.js b/test/fixtures/match/not/actual.js new file mode 100644 index 0000000..6148821 --- /dev/null +++ b/test/fixtures/match/not/actual.js @@ -0,0 +1,5 @@ +match x: + | not 1 or not Number or not "hi": + it + | not x or not 1 + 1 or not foo(bar): + it diff --git a/test/fixtures/match/not/expected.js b/test/fixtures/match/not/expected.js new file mode 100644 index 0000000..8cb3a7c --- /dev/null +++ b/test/fixtures/match/not/expected.js @@ -0,0 +1,5 @@ +if (!(x === 1) || !(typeof x === "number") || !(x === "hi")) { + it; +} else if (!x || !1 + 1 || !foo(bar)) { + it; +} \ No newline at end of file From 53951930236b6248da388f5ca95af51d93a78e37 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Fri, 9 Jun 2017 12:46:33 -0700 Subject: [PATCH 4/7] Replace PlaceholderExpression with it --- src/index.js | 32 +++++++++++-------- test/fixtures/match/arr-pattern/actual.js | 10 +++--- test/fixtures/match/arr-pattern/exec.js | 26 +++++++-------- .../match/as-call-expression/actual.js | 7 ++++ .../match/as-call-expression/expected.js | 15 +++++++++ .../match/as-call-statement/actual.js | 7 ++++ .../match/as-call-statement/expected.js | 15 +++++++++ .../match/as-identifier-expression/actual.js | 7 ++++ .../as-identifier-expression/expected.js | 15 +++++++++ .../actual.js | 3 ++ .../expected.js | 7 ++++ .../actual.js | 3 ++ .../expected.js | 5 +++ .../match/as-identifier-statement/actual.js | 7 ++++ .../match/as-identifier-statement/expected.js | 15 +++++++++ .../match/as-it-call-expression/actual.js | 7 ++++ .../match/as-it-call-expression/expected.js | 15 +++++++++ .../match/as-it-call-statement/actual.js | 7 ++++ .../match/as-it-call-statement/expected.js | 15 +++++++++ .../as-it-identifier-expression/actual.js | 7 ++++ .../as-it-identifier-expression/expected.js | 15 +++++++++ .../as-it-identifier-statement/actual.js | 7 ++++ .../as-it-identifier-statement/expected.js | 15 +++++++++ .../match/basic-placeholder/actual.js | 7 ---- .../match/basic-placeholder/expected.js | 13 -------- .../match/basic-statement-call/actual.js | 7 ++++ .../match/basic-statement-call/expected.js | 9 ++++++ .../match/basic-value-checks/expected.js | 20 ++++++------ test/fixtures/match/basic/expected.js | 6 ++-- test/fixtures/match/not/actual.js | 2 +- test/fixtures/match/not/expected.js | 6 ++-- test/fixtures/match/obj-pattern/actual.js | 12 +++---- test/fixtures/match/obj-pattern/exec.js | 12 +++---- test/fixtures/match/obj-pattern/expected.js | 2 +- .../match/single-else-expr/expected.js | 2 +- 35 files changed, 279 insertions(+), 81 deletions(-) create mode 100644 test/fixtures/match/as-call-expression/actual.js create mode 100644 test/fixtures/match/as-call-expression/expected.js create mode 100644 test/fixtures/match/as-call-statement/actual.js create mode 100644 test/fixtures/match/as-call-statement/expected.js create mode 100644 test/fixtures/match/as-identifier-expression/actual.js create mode 100644 test/fixtures/match/as-identifier-expression/expected.js create mode 100644 test/fixtures/match/as-identifier-statement-body-reference/actual.js create mode 100644 test/fixtures/match/as-identifier-statement-body-reference/expected.js create mode 100644 test/fixtures/match/as-identifier-statement-not-referenced/actual.js create mode 100644 test/fixtures/match/as-identifier-statement-not-referenced/expected.js create mode 100644 test/fixtures/match/as-identifier-statement/actual.js create mode 100644 test/fixtures/match/as-identifier-statement/expected.js create mode 100644 test/fixtures/match/as-it-call-expression/actual.js create mode 100644 test/fixtures/match/as-it-call-expression/expected.js create mode 100644 test/fixtures/match/as-it-call-statement/actual.js create mode 100644 test/fixtures/match/as-it-call-statement/expected.js create mode 100644 test/fixtures/match/as-it-identifier-expression/actual.js create mode 100644 test/fixtures/match/as-it-identifier-expression/expected.js create mode 100644 test/fixtures/match/as-it-identifier-statement/actual.js create mode 100644 test/fixtures/match/as-it-identifier-statement/expected.js delete mode 100644 test/fixtures/match/basic-placeholder/actual.js delete mode 100644 test/fixtures/match/basic-placeholder/expected.js create mode 100644 test/fixtures/match/basic-statement-call/actual.js create mode 100644 test/fixtures/match/basic-statement-call/expected.js diff --git a/src/index.js b/src/index.js index c225454..dffb331 100644 --- a/src/index.js +++ b/src/index.js @@ -950,16 +950,22 @@ export default function (babel) { return test; } + function containsAliasReference(path, alias) { + let containsAlias = false; + path.traverse({ + ReferencedIdentifier(refPath) { + if (refPath.node.name === alias.name) { + containsAlias = true; + refPath.stop(); + } + } + }); + return containsAlias; + } + function transformMatchCases(argRef, cases) { return cases.reduce((rootIf, path) => { - // fill in placeholders - path.get("test").traverse({ - PlaceholderExpression(placeholderPath) { - placeholderPath.replaceWith(argRef); - } - }); - // add in ===, etc transformMatchCaseTest(path.get("test"), argRef); @@ -1243,9 +1249,6 @@ export default function (babel) { // only allowed in MatchCase, so don't alias to Expression }); - definePluginType("PlaceholderExpression", { - aliases: ["Expression"] - }); // traverse as top-level item so as to run before other babel plugins // (and avoid traversing any of their output) @@ -1547,9 +1550,9 @@ export default function (babel) { }, MatchExpression(path) { - const { discriminant } = path.node; + const { discriminant, alias = null } = path.node; - const argRef = path.scope.generateUidIdentifier("it"); + const argRef = alias || t.identifier("it"); const matchBody = transformMatchCases(argRef, path.get("cases")); const iife = t.callExpression( @@ -1561,12 +1564,13 @@ export default function (babel) { MatchStatement(path) { const { discriminant } = path.node; + const alias = path.node.alias || t.identifier("it"); let argRef; - if (t.isIdentifier(discriminant)) { + if (t.isIdentifier(discriminant) && !containsAliasReference(path, alias)) { argRef = discriminant; } else { - argRef = path.scope.generateUidIdentifier("it"); + argRef = alias; path.insertBefore(t.variableDeclaration("const", [ t.variableDeclarator(argRef, discriminant) ])); diff --git a/test/fixtures/match/arr-pattern/actual.js b/test/fixtures/match/arr-pattern/actual.js index 8a4c92d..86e8508 100644 --- a/test/fixtures/match/arr-pattern/actual.js +++ b/test/fixtures/match/arr-pattern/actual.js @@ -1,13 +1,13 @@ match x: - | []: + | with []: "empty" - | [ a, b ]: + | with [ a, b ]: a - b - | [ a, b = 2 ]: + | with [ a, b = 2 ]: a + b - 2 - | [ a, ...b ]: + | with [ a, ...b ]: b.concat(a) - | [ + | with [ [ b d = 'e' diff --git a/test/fixtures/match/arr-pattern/exec.js b/test/fixtures/match/arr-pattern/exec.js index db39428..685bb45 100644 --- a/test/fixtures/match/arr-pattern/exec.js +++ b/test/fixtures/match/arr-pattern/exec.js @@ -1,73 +1,73 @@ assert.equal( "empty", match []: - | []: + | with []: "empty" ) assert.equal( "empty", match []: - | []: + | with []: "empty" ) assert.equal( undefined, match []: - | [a]: + | with [a]: a + 1 ) assert.equal( undefined, match [1]: - | [a, b]: + | with [a, b]: a + b ) assert.equal( 5, match [1]: - | [a, b = 4]: + | with [a, b = 4]: a + b ) assert.equal( 3, match [1, 2]: - | [a, b]: + | with [a, b]: a + b ) assert.equal( 4, match [1, 2, 3]: - | [a,, b]: + | with [a,, b]: a + b ) assert.equal( undefined, match [1, 2]: - | [a,, b]: + | with [a,, b]: a + b ) assert.deepEqual( [2, 3, 1], match [1, 2, 3]: - | [a, ...b]: + | with [a, ...b]: b.concat(a) ) assert.deepEqual( [1, 4], match [4]: - | [a, b = 1, ...c]: + | with [a, b = 1, ...c]: c.concat([b, a]) ) assert.deepEqual( [6, 7, 5, 4], match [4, 5, 6, 7]: - | [a, b = 1, ...c]: + | with [a, b = 1, ...c]: c.concat([b, a]) ) assert.deepEqual( [1, 2, 4, 6, 7, 8], match [[1], [4, 5, 6], 7, 8]: - | [ + | with [ [ b d = 2 @@ -80,7 +80,7 @@ assert.deepEqual( assert.deepEqual( undefined, match [[1], [4, 5], 7, 8]: - | [ + | with [ [ b d = 2 diff --git a/test/fixtures/match/as-call-expression/actual.js b/test/fixtures/match/as-call-expression/actual.js new file mode 100644 index 0000000..3022db5 --- /dev/null +++ b/test/fixtures/match/as-call-expression/actual.js @@ -0,0 +1,7 @@ +z = match foo.bar() as y: + | y < 1: "lt one" + | y + 1 == 1: "eq zero" + | y == 2: "eq two" + | y~f(): "f(x) truthy" + | y.prop: "has prop" + | y.0: "has first child" diff --git a/test/fixtures/match/as-call-expression/expected.js b/test/fixtures/match/as-call-expression/expected.js new file mode 100644 index 0000000..9c3eb0f --- /dev/null +++ b/test/fixtures/match/as-call-expression/expected.js @@ -0,0 +1,15 @@ +const z = (y => { + if (y < 1) { + return "lt one"; + } else if (y + 1 === 1) { + return "eq zero"; + } else if (y === 2) { + return "eq two"; + } else if (f(y)) { + return "f(x) truthy"; + } else if (y.prop) { + return "has prop"; + } else if (y[0]) { + return "has first child"; + } +})(foo.bar()); diff --git a/test/fixtures/match/as-call-statement/actual.js b/test/fixtures/match/as-call-statement/actual.js new file mode 100644 index 0000000..fa39259 --- /dev/null +++ b/test/fixtures/match/as-call-statement/actual.js @@ -0,0 +1,7 @@ +match foo() as y: + | y < 1: "lt one" + | y + 1 == 1: "eq zero" + | y == 2: "eq two" + | y~f(): "f(y) truthy" + | y.prop: "has prop" + | y.0: "has first child" diff --git a/test/fixtures/match/as-call-statement/expected.js b/test/fixtures/match/as-call-statement/expected.js new file mode 100644 index 0000000..2495206 --- /dev/null +++ b/test/fixtures/match/as-call-statement/expected.js @@ -0,0 +1,15 @@ +const y = foo(); + +if (y < 1) { + "lt one"; + } else if (y + 1 === 1) { + "eq zero"; + } else if (y === 2) { + "eq two"; + } else if (f(y)) { + "f(y) truthy"; + } else if (y.prop) { + "has prop"; + } else if (y[0]) { + "has first child"; + } diff --git a/test/fixtures/match/as-identifier-expression/actual.js b/test/fixtures/match/as-identifier-expression/actual.js new file mode 100644 index 0000000..b0a69e6 --- /dev/null +++ b/test/fixtures/match/as-identifier-expression/actual.js @@ -0,0 +1,7 @@ +z = match x as y: + | y < 1: "lt one" + | y + 1 == 1: "eq zero" + | y == 2: "eq two" + | y~f(): "f(x) truthy" + | y.prop: "has prop" + | y.0: "has first child" diff --git a/test/fixtures/match/as-identifier-expression/expected.js b/test/fixtures/match/as-identifier-expression/expected.js new file mode 100644 index 0000000..7ad5dd7 --- /dev/null +++ b/test/fixtures/match/as-identifier-expression/expected.js @@ -0,0 +1,15 @@ +const z = (y => { + if (y < 1) { + return "lt one"; + } else if (y + 1 === 1) { + return "eq zero"; + } else if (y === 2) { + return "eq two"; + } else if (f(y)) { + return "f(x) truthy"; + } else if (y.prop) { + return "has prop"; + } else if (y[0]) { + return "has first child"; + } +})(x); diff --git a/test/fixtures/match/as-identifier-statement-body-reference/actual.js b/test/fixtures/match/as-identifier-statement-body-reference/actual.js new file mode 100644 index 0000000..51b13d9 --- /dev/null +++ b/test/fixtures/match/as-identifier-statement-body-reference/actual.js @@ -0,0 +1,3 @@ +match x: + | 1: "foo" + | "1": it diff --git a/test/fixtures/match/as-identifier-statement-body-reference/expected.js b/test/fixtures/match/as-identifier-statement-body-reference/expected.js new file mode 100644 index 0000000..0d3448c --- /dev/null +++ b/test/fixtures/match/as-identifier-statement-body-reference/expected.js @@ -0,0 +1,7 @@ +const it = x; + +if (it === 1) { + "foo"; + } else if (it === "1") { + it; + } diff --git a/test/fixtures/match/as-identifier-statement-not-referenced/actual.js b/test/fixtures/match/as-identifier-statement-not-referenced/actual.js new file mode 100644 index 0000000..de807c5 --- /dev/null +++ b/test/fixtures/match/as-identifier-statement-not-referenced/actual.js @@ -0,0 +1,3 @@ +match x: + | 1: "foo" + | "1": "bar" diff --git a/test/fixtures/match/as-identifier-statement-not-referenced/expected.js b/test/fixtures/match/as-identifier-statement-not-referenced/expected.js new file mode 100644 index 0000000..eb590d2 --- /dev/null +++ b/test/fixtures/match/as-identifier-statement-not-referenced/expected.js @@ -0,0 +1,5 @@ +if (x === 1) { + "foo"; + } else if (x === "1") { + "bar"; + } diff --git a/test/fixtures/match/as-identifier-statement/actual.js b/test/fixtures/match/as-identifier-statement/actual.js new file mode 100644 index 0000000..c9c7d02 --- /dev/null +++ b/test/fixtures/match/as-identifier-statement/actual.js @@ -0,0 +1,7 @@ +match x as y: + | y < 1: "lt one" + | y + 1 == 1: "eq zero" + | y == 2: "eq two" + | y~f(): "f(x) truthy" + | y.prop: "has prop" + | y.0: "has first child" diff --git a/test/fixtures/match/as-identifier-statement/expected.js b/test/fixtures/match/as-identifier-statement/expected.js new file mode 100644 index 0000000..ab33968 --- /dev/null +++ b/test/fixtures/match/as-identifier-statement/expected.js @@ -0,0 +1,15 @@ +const y = x; + +if (y < 1) { + "lt one"; + } else if (y + 1 === 1) { + "eq zero"; + } else if (y === 2) { + "eq two"; + } else if (f(y)) { + "f(x) truthy"; + } else if (y.prop) { + "has prop"; + } else if (y[0]) { + "has first child"; + } diff --git a/test/fixtures/match/as-it-call-expression/actual.js b/test/fixtures/match/as-it-call-expression/actual.js new file mode 100644 index 0000000..6b68dac --- /dev/null +++ b/test/fixtures/match/as-it-call-expression/actual.js @@ -0,0 +1,7 @@ +z = match foo.bar(): + | it < 1: "lt one" + | it + 1 == 1: "eq zero" + | it == 2: "eq two" + | it~f(): "f(x) truthy" + | it.prop: "has prop" + | it.0: "has first child" diff --git a/test/fixtures/match/as-it-call-expression/expected.js b/test/fixtures/match/as-it-call-expression/expected.js new file mode 100644 index 0000000..0b417c8 --- /dev/null +++ b/test/fixtures/match/as-it-call-expression/expected.js @@ -0,0 +1,15 @@ +const z = (it => { + if (it < 1) { + return "lt one"; + } else if (it + 1 === 1) { + return "eq zero"; + } else if (it === 2) { + return "eq two"; + } else if (f(it)) { + return "f(x) truthy"; + } else if (it.prop) { + return "has prop"; + } else if (it[0]) { + return "has first child"; + } +})(foo.bar()); diff --git a/test/fixtures/match/as-it-call-statement/actual.js b/test/fixtures/match/as-it-call-statement/actual.js new file mode 100644 index 0000000..0097bbb --- /dev/null +++ b/test/fixtures/match/as-it-call-statement/actual.js @@ -0,0 +1,7 @@ +match foo(): + | it < 1: "lt one" + | it + 1 == 1: "eq zero" + | it == 2: "eq two" + | it~f(): "f(x) truthy" + | it.prop: "has prop" + | it.0: "has first child" diff --git a/test/fixtures/match/as-it-call-statement/expected.js b/test/fixtures/match/as-it-call-statement/expected.js new file mode 100644 index 0000000..9215777 --- /dev/null +++ b/test/fixtures/match/as-it-call-statement/expected.js @@ -0,0 +1,15 @@ +const it = foo(); + +if (it < 1) { + "lt one"; + } else if (it + 1 === 1) { + "eq zero"; + } else if (it === 2) { + "eq two"; + } else if (f(it)) { + "f(x) truthy"; + } else if (it.prop) { + "has prop"; + } else if (it[0]) { + "has first child"; + } diff --git a/test/fixtures/match/as-it-identifier-expression/actual.js b/test/fixtures/match/as-it-identifier-expression/actual.js new file mode 100644 index 0000000..c7779e8 --- /dev/null +++ b/test/fixtures/match/as-it-identifier-expression/actual.js @@ -0,0 +1,7 @@ +z = match x: + | it < 1: "lt one" + | it + 1 == 1: "eq zero" + | it == 2: "eq two" + | it~f(): "f(x) truthy" + | it.prop: "has prop" + | it.0: "has first child" diff --git a/test/fixtures/match/as-it-identifier-expression/expected.js b/test/fixtures/match/as-it-identifier-expression/expected.js new file mode 100644 index 0000000..2382825 --- /dev/null +++ b/test/fixtures/match/as-it-identifier-expression/expected.js @@ -0,0 +1,15 @@ +const z = (it => { + if (it < 1) { + return "lt one"; + } else if (it + 1 === 1) { + return "eq zero"; + } else if (it === 2) { + return "eq two"; + } else if (f(it)) { + return "f(x) truthy"; + } else if (it.prop) { + return "has prop"; + } else if (it[0]) { + return "has first child"; + } +})(x); diff --git a/test/fixtures/match/as-it-identifier-statement/actual.js b/test/fixtures/match/as-it-identifier-statement/actual.js new file mode 100644 index 0000000..b778a3f --- /dev/null +++ b/test/fixtures/match/as-it-identifier-statement/actual.js @@ -0,0 +1,7 @@ +match x: + | it < 1: "lt one" + | it + 1 == 1: "eq zero" + | it == 2: "eq two" + | it~f(): "f(x) truthy" + | it.prop: "has prop" + | it.0: "has first child" diff --git a/test/fixtures/match/as-it-identifier-statement/expected.js b/test/fixtures/match/as-it-identifier-statement/expected.js new file mode 100644 index 0000000..2187c8c --- /dev/null +++ b/test/fixtures/match/as-it-identifier-statement/expected.js @@ -0,0 +1,15 @@ +const it = x; + +if (it < 1) { + "lt one"; + } else if (it + 1 === 1) { + "eq zero"; + } else if (it === 2) { + "eq two"; + } else if (f(it)) { + "f(x) truthy"; + } else if (it.prop) { + "has prop"; + } else if (it[0]) { + "has first child"; + } diff --git a/test/fixtures/match/basic-placeholder/actual.js b/test/fixtures/match/basic-placeholder/actual.js deleted file mode 100644 index 61c0908..0000000 --- a/test/fixtures/match/basic-placeholder/actual.js +++ /dev/null @@ -1,7 +0,0 @@ -match x: - | < 1: "lt one" - | + 1 == 1: "eq zero" - | == 2: "eq two" - | ~f(): "f(x) truthy" - | .prop: "has prop" - | .0: "has first child" diff --git a/test/fixtures/match/basic-placeholder/expected.js b/test/fixtures/match/basic-placeholder/expected.js deleted file mode 100644 index d0d1511..0000000 --- a/test/fixtures/match/basic-placeholder/expected.js +++ /dev/null @@ -1,13 +0,0 @@ -if (x < 1) { - "lt one"; - } else if (x + 1 === 1) { - "eq zero"; - } else if (x === 2) { - "eq two"; - } else if (f(x)) { - "f(x) truthy"; - } else if (x.prop) { - "has prop"; - } else if (x[0]) { - "has first child"; - } diff --git a/test/fixtures/match/basic-statement-call/actual.js b/test/fixtures/match/basic-statement-call/actual.js new file mode 100644 index 0000000..5de670d --- /dev/null +++ b/test/fixtures/match/basic-statement-call/actual.js @@ -0,0 +1,7 @@ +match foo(): + | 1: + "one" + | 2: + "two" + | else: + "idk" diff --git a/test/fixtures/match/basic-statement-call/expected.js b/test/fixtures/match/basic-statement-call/expected.js new file mode 100644 index 0000000..de54b42 --- /dev/null +++ b/test/fixtures/match/basic-statement-call/expected.js @@ -0,0 +1,9 @@ +const it = foo(); + +if (it === 1) { + "one"; +} else if (it === 2) { + "two"; +} else { + "idk"; +} diff --git a/test/fixtures/match/basic-value-checks/expected.js b/test/fixtures/match/basic-value-checks/expected.js index 6dec644..4dae010 100644 --- a/test/fixtures/match/basic-value-checks/expected.js +++ b/test/fixtures/match/basic-value-checks/expected.js @@ -1,19 +1,21 @@ -if (x === 1 || x === 0.1 || x === 0x11 || x === +1 || x === -1) { +const it = x; + +if (it === 1 || it === 0.1 || it === 0x11 || it === +1 || it === -1) { it; -} else if (x === "hi") { +} else if (it === "hi") { it; -} else if (x === `there ${ 1 + 1 }`) { +} else if (it === `there ${ 1 + 1 }`) { it; -} else if (/\s+/.test(x)) { +} else if (/\s+/.test(it)) { it; -} else if (typeof x === "number" || typeof x === "boolean" || typeof x === "string") { +} else if (typeof it === "number" || typeof it === "boolean" || typeof it === "string") { it; -} else if (x instanceof Array || x instanceof Object || x instanceof Map || x instanceof Foo) { +} else if (it instanceof Array || it instanceof Object || it instanceof Map || it instanceof Foo) { it; -} else if (x === null || x === undefined) { +} else if (it === null || it === undefined) { it; } else if (x || +x) { it; -} else if (!(x === 1) || !x) { +} else if (!(it === 1) || !x) { it; -} +} \ No newline at end of file diff --git a/test/fixtures/match/basic/expected.js b/test/fixtures/match/basic/expected.js index d1f0004..6c0a0db 100644 --- a/test/fixtures/match/basic/expected.js +++ b/test/fixtures/match/basic/expected.js @@ -1,7 +1,7 @@ -const z = (_it => { - if (_it === 1) { +const z = (it => { + if (it === 1) { return "one"; - } else if (_it === 2) { + } else if (it === 2) { return "two"; } else { return "idk"; diff --git a/test/fixtures/match/not/actual.js b/test/fixtures/match/not/actual.js index 6148821..91a9037 100644 --- a/test/fixtures/match/not/actual.js +++ b/test/fixtures/match/not/actual.js @@ -1,5 +1,5 @@ match x: | not 1 or not Number or not "hi": it - | not x or not 1 + 1 or not foo(bar): + | not it or not 1 + 1 or not foo(bar): it diff --git a/test/fixtures/match/not/expected.js b/test/fixtures/match/not/expected.js index 8cb3a7c..ed7793b 100644 --- a/test/fixtures/match/not/expected.js +++ b/test/fixtures/match/not/expected.js @@ -1,5 +1,7 @@ -if (!(x === 1) || !(typeof x === "number") || !(x === "hi")) { +const it = x; + +if (!(it === 1) || !(typeof it === "number") || !(it === "hi")) { it; -} else if (!x || !1 + 1 || !foo(bar)) { +} else if (!it || !1 + 1 || !foo(bar)) { it; } \ No newline at end of file diff --git a/test/fixtures/match/obj-pattern/actual.js b/test/fixtures/match/obj-pattern/actual.js index 5ca3636..8ec8e06 100644 --- a/test/fixtures/match/obj-pattern/actual.js +++ b/test/fixtures/match/obj-pattern/actual.js @@ -1,14 +1,14 @@ match x: - | { a }: + | with { a }: a - | { a, b }: + | with { a, b }: a + b - | { a, b = 1 }: + | with { a, b = 1 }: a + b - | { a, b: { ba, bb = 1 }, c: { ca, cb: { cba } } }: + | with { a, b: { ba, bb = 1 }, c: { ca, cb: { cba } } }: a + ba + bb + ca + cba | 1 or 2 with { a, b: { c } }: a + c - | { a: { b: { c } } = otherObj }: + | with { a: { b: { c } } = otherObj }: c - //TODO: | { a, ...b }: b + //TODO: test `| { a, ...b }: b` diff --git a/test/fixtures/match/obj-pattern/exec.js b/test/fixtures/match/obj-pattern/exec.js index 65fbdb6..a3b272b 100644 --- a/test/fixtures/match/obj-pattern/exec.js +++ b/test/fixtures/match/obj-pattern/exec.js @@ -1,36 +1,36 @@ assert.equal( 1, match { a: 1 }: - | { a }: + | with { a }: a ) assert.equal( undefined, match { b: 1 }: - | { a }: + | with { a }: a ) assert.equal( 3, match { a: 1 }: - | { a, b = 2 }: + | with { a, b = 2 }: a + b ) assert.equal( undefined, match { b: 1 }: - | { a, b = 2 }: + | with { a, b = 2 }: a + b ) assert.equal( 3, match { a: 1, b: { c: 2 } }: - | { a, b: { c } }: + | with { a, b: { c } }: a + c ) assert.equal( undefined, match { a: 1 }: - | { a, b: { c } }: + | with { a, b: { c } }: a + c ) diff --git a/test/fixtures/match/obj-pattern/expected.js b/test/fixtures/match/obj-pattern/expected.js index 2302b8c..f9a9691 100644 --- a/test/fixtures/match/obj-pattern/expected.js +++ b/test/fixtures/match/obj-pattern/expected.js @@ -28,4 +28,4 @@ if (_hasProps(x, ["a"])) { const { a: { b: { c } } = otherObj } = x; c; -} //TODO: | { a, ...b }: b +} //TODO: test `| { a, ...b }: b` diff --git a/test/fixtures/match/single-else-expr/expected.js b/test/fixtures/match/single-else-expr/expected.js index 0c81fe4..80f4483 100644 --- a/test/fixtures/match/single-else-expr/expected.js +++ b/test/fixtures/match/single-else-expr/expected.js @@ -1,4 +1,4 @@ -const z = (_it => { +const z = (it => { { try {} finally {} return "idk"; From 8cebb95822bb321a8d94601bc1d1d905c263aeb9 Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Fri, 9 Jun 2017 16:32:32 -0700 Subject: [PATCH 5/7] Disallow object and array literals, cleanup hasProps --- src/index.js | 9 +++++- src/stdlib.js | 14 ++++++---- test/fixtures/match/arr-pattern/expected.js | 8 ++---- .../fixtures/match/illegal-arr-expr/actual.js | 2 ++ .../match/illegal-arr-expr/options.json | 3 ++ .../fixtures/match/illegal-obj-expr/actual.js | 2 ++ .../match/illegal-obj-expr/options.json | 3 ++ .../match/obj-arr-pattern-mixed/actual.js | 3 ++ .../match/obj-arr-pattern-mixed/exec.js | 28 +++++++++++++++++++ .../match/obj-arr-pattern-mixed/expected.js | 9 ++++++ test/fixtures/match/obj-pattern/expected.js | 20 ++++++------- 11 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/match/illegal-arr-expr/actual.js create mode 100644 test/fixtures/match/illegal-arr-expr/options.json create mode 100644 test/fixtures/match/illegal-obj-expr/actual.js create mode 100644 test/fixtures/match/illegal-obj-expr/options.json create mode 100644 test/fixtures/match/obj-arr-pattern-mixed/actual.js create mode 100644 test/fixtures/match/obj-arr-pattern-mixed/exec.js create mode 100644 test/fixtures/match/obj-arr-pattern-mixed/expected.js diff --git a/src/index.js b/src/index.js index dffb331..2def571 100644 --- a/src/index.js +++ b/src/index.js @@ -684,6 +684,8 @@ export default function (babel) { const fnAST = babel.template(fn.toString())({ [helperName]: uid, }); + fnAST._compact = true; + fnAST._generated = true; helpers.push(fnAST); } insertAfterImports(path, helpers); @@ -850,6 +852,11 @@ export default function (babel) { } else if (path.isLiteral() || isUndefined(path) || isSignedNumber(path)) { const isEq = t.binaryExpression("===", argRef, path.node); path.replaceWith(isEq); + } else if (path.isObjectExpression() || path.isArrayExpression()) { + throw path.buildCodeFrameError( + "LightScript does not yet support matching on object or array literals. " + + "To destructure, use `with` (eg; `| with { foo, bar }: foo + bar`)." + ); } } @@ -896,7 +903,7 @@ export default function (babel) { } } - const isObjCall = t.callExpression(isObjUid, [argRef, t.arrayExpression(propsToCheck)]); + const isObjCall = t.callExpression(isObjUid, [argRef, ...propsToCheck]); test = buildAnd(test, isObjCall); for (const [ propName, childPatternPath, defaultObj = null ] of childPatterns) { diff --git a/src/stdlib.js b/src/stdlib.js index 434342e..fecdb00 100644 --- a/src/stdlib.js +++ b/src/stdlib.js @@ -12,12 +12,14 @@ export const lightscriptImports = { }; export const runtimeHelpers = { - hasProps: function hasProps(obj, props) { - return ( - obj != null && - (typeof obj === "object" || typeof obj === "function") && - props.filter(prop => prop in obj).length === props.length - ); + hasProps: function hasProps(obj) { + if (obj == null) return false; + if (typeof obj !== "object" && typeof obj !== "function") return false; + let i = arguments.length; + while (--i > 0) { + if (!(arguments[i] in obj)) return false; + } + return true; }, hasLength: function hasLength(arr, minLength, maxLength) { minLength = minLength || 0; diff --git a/test/fixtures/match/arr-pattern/expected.js b/test/fixtures/match/arr-pattern/expected.js index 97e2a6c..dd49872 100644 --- a/test/fixtures/match/arr-pattern/expected.js +++ b/test/fixtures/match/arr-pattern/expected.js @@ -1,8 +1,4 @@ -function _hasLength(arr, minLength, maxLength) { - minLength = minLength || 0; - maxLength = maxLength != null ? maxLength : Number.MAX_SAFE_INTEGER; - return arr != null && typeof arr !== "function" && arr.length === arr.length | 0 && arr.length >= minLength && arr.length <= maxLength; -} +function _hasLength(arr, minLength, maxLength) { minLength = minLength || 0; maxLength = maxLength != null ? maxLength : Number.MAX_SAFE_INTEGER; return arr != null && typeof arr !== "function" && arr.length === arr.length | 0 && arr.length >= minLength && arr.length <= maxLength; } if (_hasLength(x, 0, 0)) { const [] = x; @@ -24,4 +20,4 @@ if (_hasLength(x, 0, 0)) { const [[b, d = 'e'], [g,, h], ...j] = x; [b, d, g, ...j].join(''); -} +} \ No newline at end of file diff --git a/test/fixtures/match/illegal-arr-expr/actual.js b/test/fixtures/match/illegal-arr-expr/actual.js new file mode 100644 index 0000000..cc430b1 --- /dev/null +++ b/test/fixtures/match/illegal-arr-expr/actual.js @@ -0,0 +1,2 @@ +match x: + | 1 or [a, b]: "not allowed" diff --git a/test/fixtures/match/illegal-arr-expr/options.json b/test/fixtures/match/illegal-arr-expr/options.json new file mode 100644 index 0000000..ebbcb4a --- /dev/null +++ b/test/fixtures/match/illegal-arr-expr/options.json @@ -0,0 +1,3 @@ +{ + "throws": "LightScript does not yet support matching on object or array literals. To destructure, use `with` (eg; `| with { foo, bar }: foo + bar`)." +} diff --git a/test/fixtures/match/illegal-obj-expr/actual.js b/test/fixtures/match/illegal-obj-expr/actual.js new file mode 100644 index 0000000..b5d9aab --- /dev/null +++ b/test/fixtures/match/illegal-obj-expr/actual.js @@ -0,0 +1,2 @@ +match x: + | 1 or { a, b: 1 }: "not allowed" diff --git a/test/fixtures/match/illegal-obj-expr/options.json b/test/fixtures/match/illegal-obj-expr/options.json new file mode 100644 index 0000000..ebbcb4a --- /dev/null +++ b/test/fixtures/match/illegal-obj-expr/options.json @@ -0,0 +1,3 @@ +{ + "throws": "LightScript does not yet support matching on object or array literals. To destructure, use `with` (eg; `| with { foo, bar }: foo + bar`)." +} diff --git a/test/fixtures/match/obj-arr-pattern-mixed/actual.js b/test/fixtures/match/obj-arr-pattern-mixed/actual.js new file mode 100644 index 0000000..215ea74 --- /dev/null +++ b/test/fixtures/match/obj-arr-pattern-mixed/actual.js @@ -0,0 +1,3 @@ +match x: + | with { a, aa: [b, { c = 1 }, ...d] }: + d.concat(a + b + c) diff --git a/test/fixtures/match/obj-arr-pattern-mixed/exec.js b/test/fixtures/match/obj-arr-pattern-mixed/exec.js new file mode 100644 index 0000000..1601ab2 --- /dev/null +++ b/test/fixtures/match/obj-arr-pattern-mixed/exec.js @@ -0,0 +1,28 @@ +m(x) -> match x: + | with { a, aa: [b, { c = 1 }, ...d] }: + d.concat(a + b + c) + +assert.equal( + undefined, + m() +) +assert.equal( + undefined, + m({ a: 1 }) +) +assert.equal( + undefined, + m({ a: 1, aa: [ 2 ] }) +) +assert.deepEqual( + [4], + m({ a: 1, aa: [ 2, [ 3 ] ] }) // womp, arrays are objects +) +assert.deepEqual( + [5], + m({ a: 1, aa: [ 2, { c: 2 } ] }) +) +assert.deepEqual( + [7, 5], + m({ a: 1, aa: [ 2, { c: 2 }, 7 ] }) +) diff --git a/test/fixtures/match/obj-arr-pattern-mixed/expected.js b/test/fixtures/match/obj-arr-pattern-mixed/expected.js new file mode 100644 index 0000000..71e95db --- /dev/null +++ b/test/fixtures/match/obj-arr-pattern-mixed/expected.js @@ -0,0 +1,9 @@ +function _hasProps(obj) { if (obj == null) return false; if (typeof obj !== "object" && typeof obj !== "function") return false; var i = arguments.length; while (--i > 0) { if (!(arguments[i] in obj)) return false; } return true; } + +function _hasLength(arr, minLength, maxLength) { minLength = minLength || 0; maxLength = maxLength != null ? maxLength : Number.MAX_SAFE_INTEGER; return arr != null && typeof arr !== "function" && arr.length === arr.length | 0 && arr.length >= minLength && arr.length <= maxLength; } + +if (_hasProps(x, "a", "aa") && _hasLength(x.aa, 2) && _hasProps(x.aa[1])) { + const { a, aa: [b, { c = 1 }, ...d] } = x; + + d.concat(a + b + c); +} \ No newline at end of file diff --git a/test/fixtures/match/obj-pattern/expected.js b/test/fixtures/match/obj-pattern/expected.js index f9a9691..5b13690 100644 --- a/test/fixtures/match/obj-pattern/expected.js +++ b/test/fixtures/match/obj-pattern/expected.js @@ -1,31 +1,27 @@ -function _hasProps(obj, props) { - return obj != null && (typeof obj === "object" || typeof obj === "function") && props.filter(function (prop) { - return prop in obj; - }).length === props.length; -} +function _hasProps(obj) { if (obj == null) return false; if (typeof obj !== "object" && typeof obj !== "function") return false; var i = arguments.length; while (--i > 0) { if (!(arguments[i] in obj)) return false; } return true; } -if (_hasProps(x, ["a"])) { +if (_hasProps(x, "a")) { const { a } = x; a; -} else if (_hasProps(x, ["a", "b"])) { +} else if (_hasProps(x, "a", "b")) { const { a, b } = x; a + b; -} else if (_hasProps(x, ["a"])) { +} else if (_hasProps(x, "a")) { const { a, b = 1 } = x; a + b; -} else if (_hasProps(x, ["a", "b", "c"]) && _hasProps(x.b, ["ba"]) && _hasProps(x.c, ["ca", "cb"]) && _hasProps(x.c.cb, ["cba"])) { +} else if (_hasProps(x, "a", "b", "c") && _hasProps(x.b, "ba") && _hasProps(x.c, "ca", "cb") && _hasProps(x.c.cb, "cba")) { const { a, b: { ba, bb = 1 }, c: { ca, cb: { cba } } } = x; a + ba + bb + ca + cba; -} else if ((x === 1 || x === 2) && _hasProps(x, ["a", "b"]) && _hasProps(x.b, ["c"])) { +} else if ((x === 1 || x === 2) && _hasProps(x, "a", "b") && _hasProps(x.b, "c")) { const { a, b: { c } } = x; a + c; -} else if (_hasProps(x, []) && (_hasProps(x.a, ["b"]) && _hasProps(x.a.b, ["c"]) || _hasProps(otherObj, ["b"]) && _hasProps(otherObj.b, ["c"]))) { +} else if (_hasProps(x) && (_hasProps(x.a, "b") && _hasProps(x.a.b, "c") || _hasProps(otherObj, "b") && _hasProps(otherObj.b, "c"))) { const { a: { b: { c } } = otherObj } = x; c; -} //TODO: test `| { a, ...b }: b` +} //TODO: test `| { a, ...b }: b` \ No newline at end of file From 350454b3a39a2c36dba613126a97f56ee295bf9b Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Sat, 10 Jun 2017 00:25:56 -0700 Subject: [PATCH 6/7] Prevent sibling 'it' --- src/index.js | 15 ++++++++++++--- .../match/illegal-sibling-no-as/actual.js | 5 +++++ .../match/illegal-sibling-no-as/options.json | 3 +++ test/fixtures/match/nested-it/actual.js | 7 +++++++ test/fixtures/match/nested-it/expected.js | 18 ++++++++++++++++++ test/fixtures/match/sibling-as/actual.js | 5 +++++ test/fixtures/match/sibling-as/expected.js | 11 +++++++++++ 7 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/match/illegal-sibling-no-as/actual.js create mode 100644 test/fixtures/match/illegal-sibling-no-as/options.json create mode 100644 test/fixtures/match/nested-it/actual.js create mode 100644 test/fixtures/match/nested-it/expected.js create mode 100644 test/fixtures/match/sibling-as/actual.js create mode 100644 test/fixtures/match/sibling-as/expected.js diff --git a/src/index.js b/src/index.js index 2def571..c45a717 100644 --- a/src/index.js +++ b/src/index.js @@ -1009,6 +1009,14 @@ export default function (babel) { }, null); } + function prependDeclaration(path, id, init, kind = "const") { + path.insertBefore(t.variableDeclaration(kind, [ + t.variableDeclarator(id, init) + ])); + const constPath = path.getSibling(path.key - 1); + path.scope.registerBinding(kind, constPath); + } + // TYPE DEFINITIONS definePluginType("ForInArrayStatement", { visitor: ["idx", "elem", "array", "body"], @@ -1578,9 +1586,10 @@ export default function (babel) { argRef = discriminant; } else { argRef = alias; - path.insertBefore(t.variableDeclaration("const", [ - t.variableDeclarator(argRef, discriminant) - ])); + if (path.scope.hasOwnBinding(argRef.name)) { + throw path.buildCodeFrameError("Cannot re-use `it` binding in the same scope. Try a new shorthand name (eg; `match foo as x:`)."); + } + prependDeclaration(path, argRef, discriminant); } const matchBody = transformMatchCases(argRef, path.get("cases")); diff --git a/test/fixtures/match/illegal-sibling-no-as/actual.js b/test/fixtures/match/illegal-sibling-no-as/actual.js new file mode 100644 index 0000000..00273c1 --- /dev/null +++ b/test/fixtures/match/illegal-sibling-no-as/actual.js @@ -0,0 +1,5 @@ +match foo(): + | 1: "ok" + +match bar(): + | 2: "oops" diff --git a/test/fixtures/match/illegal-sibling-no-as/options.json b/test/fixtures/match/illegal-sibling-no-as/options.json new file mode 100644 index 0000000..1eea700 --- /dev/null +++ b/test/fixtures/match/illegal-sibling-no-as/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Cannot re-use `it` binding in the same scope. Try a new shorthand name (eg; `match foo as x:`)." +} diff --git a/test/fixtures/match/nested-it/actual.js b/test/fixtures/match/nested-it/actual.js new file mode 100644 index 0000000..5770bbd --- /dev/null +++ b/test/fixtures/match/nested-it/actual.js @@ -0,0 +1,7 @@ +match foo(): + | 1: + match it: + | 2: it + | with { x }: + match x: + | 2: it diff --git a/test/fixtures/match/nested-it/expected.js b/test/fixtures/match/nested-it/expected.js new file mode 100644 index 0000000..83dfd10 --- /dev/null +++ b/test/fixtures/match/nested-it/expected.js @@ -0,0 +1,18 @@ +function _hasProps(obj) { if (obj == null) return false; if (typeof obj !== "object" && typeof obj !== "function") return false; var i = arguments.length; while (--i > 0) { if (!(arguments[i] in obj)) return false; } return true; } + +const it = foo(); + +if (it === 1) { + const it = it; + + if (it === 2) { + it; + } +} else if (_hasProps(it, "x")) { + const { x } = it; + const it = x; + + if (it === 2) { + it; + } +} \ No newline at end of file diff --git a/test/fixtures/match/sibling-as/actual.js b/test/fixtures/match/sibling-as/actual.js new file mode 100644 index 0000000..8e8e8b4 --- /dev/null +++ b/test/fixtures/match/sibling-as/actual.js @@ -0,0 +1,5 @@ +match foo(): + | 1: "ok" + +match bar() as x: + | 2: "ok 2" diff --git a/test/fixtures/match/sibling-as/expected.js b/test/fixtures/match/sibling-as/expected.js new file mode 100644 index 0000000..721faba --- /dev/null +++ b/test/fixtures/match/sibling-as/expected.js @@ -0,0 +1,11 @@ +const it = foo(); + +if (it === 1) { + "ok"; + } + +const x = bar(); + +if (x === 2) { + "ok 2"; + } \ No newline at end of file From 1e720c158804b725d5eee440ff4cbd104e74390a Mon Sep 17 00:00:00 2001 From: Alex Rattray Date: Sat, 10 Jun 2017 12:05:11 -0700 Subject: [PATCH 7/7] Allow sibling matches to use it --- src/index.js | 28 +++++++++++-------- .../match/illegal-sibling-no-as/options.json | 3 -- test/fixtures/match/implicit-return/actual.js | 9 ++++++ .../match/implicit-return/expected.js | 16 +++++++++++ test/fixtures/match/nested-it/expected.js | 20 +++++++------ test/fixtures/match/shadow-it/actual.js | 6 ++++ test/fixtures/match/shadow-it/exec.js | 8 ++++++ test/fixtures/match/shadow-it/expected.js | 12 ++++++++ .../sibling-no-as-implicit-returns/actual.js | 6 ++++ .../expected.js | 15 ++++++++++ .../actual.js | 0 test/fixtures/match/sibling-no-as/expected.js | 13 +++++++++ 12 files changed, 113 insertions(+), 23 deletions(-) delete mode 100644 test/fixtures/match/illegal-sibling-no-as/options.json create mode 100644 test/fixtures/match/implicit-return/actual.js create mode 100644 test/fixtures/match/implicit-return/expected.js create mode 100644 test/fixtures/match/shadow-it/actual.js create mode 100644 test/fixtures/match/shadow-it/exec.js create mode 100644 test/fixtures/match/shadow-it/expected.js create mode 100644 test/fixtures/match/sibling-no-as-implicit-returns/actual.js create mode 100644 test/fixtures/match/sibling-no-as-implicit-returns/expected.js rename test/fixtures/match/{illegal-sibling-no-as => sibling-no-as}/actual.js (100%) create mode 100644 test/fixtures/match/sibling-no-as/expected.js diff --git a/src/index.js b/src/index.js index c45a717..e4659d0 100644 --- a/src/index.js +++ b/src/index.js @@ -1009,7 +1009,7 @@ export default function (babel) { }, null); } - function prependDeclaration(path, id, init, kind = "const") { + function insertDeclarationBefore(path, id, init, kind = "const") { path.insertBefore(t.variableDeclaration(kind, [ t.variableDeclarator(id, init) ])); @@ -1581,19 +1581,23 @@ export default function (babel) { const { discriminant } = path.node; const alias = path.node.alias || t.identifier("it"); - let argRef; - if (t.isIdentifier(discriminant) && !containsAliasReference(path, alias)) { - argRef = discriminant; - } else { - argRef = alias; - if (path.scope.hasOwnBinding(argRef.name)) { - throw path.buildCodeFrameError("Cannot re-use `it` binding in the same scope. Try a new shorthand name (eg; `match foo as x:`)."); - } - prependDeclaration(path, argRef, discriminant); - } - + const argRef = t.isIdentifier(discriminant) && !containsAliasReference(path, alias) + ? discriminant + : alias; const matchBody = transformMatchCases(argRef, path.get("cases")); + path.replaceWith(matchBody); + + // insert eg; `const it = foo()` above the body, + // possibly wrapping both in an anonymous block. + if (argRef !== discriminant) { + if (path.scope.hasBinding(alias.name)) { + // wrap in anonymous block + path.replaceWith(t.blockStatement([path.node])); + path = path.get("body.0"); + } + insertDeclarationBefore(path, argRef, discriminant); + } }, }); diff --git a/test/fixtures/match/illegal-sibling-no-as/options.json b/test/fixtures/match/illegal-sibling-no-as/options.json deleted file mode 100644 index 1eea700..0000000 --- a/test/fixtures/match/illegal-sibling-no-as/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "Cannot re-use `it` binding in the same scope. Try a new shorthand name (eg; `match foo as x:`)." -} diff --git a/test/fixtures/match/implicit-return/actual.js b/test/fixtures/match/implicit-return/actual.js new file mode 100644 index 0000000..21be344 --- /dev/null +++ b/test/fixtures/match/implicit-return/actual.js @@ -0,0 +1,9 @@ +f() -> + match foo(): + | 1: + "ok" + | 2: + bar() + match baz(): + | 3: + qux() diff --git a/test/fixtures/match/implicit-return/expected.js b/test/fixtures/match/implicit-return/expected.js new file mode 100644 index 0000000..d859902 --- /dev/null +++ b/test/fixtures/match/implicit-return/expected.js @@ -0,0 +1,16 @@ +function f() { + const it = foo(); + + if (it === 1) { + return "ok"; + } else if (it === 2) { + bar(); + { + const it = baz(); + + if (it === 3) { + return qux(); + } + } + } +} \ No newline at end of file diff --git a/test/fixtures/match/nested-it/expected.js b/test/fixtures/match/nested-it/expected.js index 83dfd10..16efb21 100644 --- a/test/fixtures/match/nested-it/expected.js +++ b/test/fixtures/match/nested-it/expected.js @@ -3,16 +3,20 @@ function _hasProps(obj) { if (obj == null) return false; if (typeof obj !== "obj const it = foo(); if (it === 1) { - const it = it; + { + const it = it; - if (it === 2) { - it; - } + if (it === 2) { + it; + } + } } else if (_hasProps(it, "x")) { const { x } = it; - const it = x; + { + const it = x; - if (it === 2) { - it; - } + if (it === 2) { + it; + } + } } \ No newline at end of file diff --git a/test/fixtures/match/shadow-it/actual.js b/test/fixtures/match/shadow-it/actual.js new file mode 100644 index 0000000..0295238 --- /dev/null +++ b/test/fixtures/match/shadow-it/actual.js @@ -0,0 +1,6 @@ +it = "good" +f() -> + match "bad": + | true: true + + it diff --git a/test/fixtures/match/shadow-it/exec.js b/test/fixtures/match/shadow-it/exec.js new file mode 100644 index 0000000..3911e73 --- /dev/null +++ b/test/fixtures/match/shadow-it/exec.js @@ -0,0 +1,8 @@ +it = "good" +f() -> + match "bad": + | true: true + + it + +assert.equal("good", f()) diff --git a/test/fixtures/match/shadow-it/expected.js b/test/fixtures/match/shadow-it/expected.js new file mode 100644 index 0000000..ac23c90 --- /dev/null +++ b/test/fixtures/match/shadow-it/expected.js @@ -0,0 +1,12 @@ +const it = "good"; + +function f() { + { + const it = "bad"; + + if (it === true) { + true; + } + } + return it; +} \ No newline at end of file diff --git a/test/fixtures/match/sibling-no-as-implicit-returns/actual.js b/test/fixtures/match/sibling-no-as-implicit-returns/actual.js new file mode 100644 index 0000000..995b453 --- /dev/null +++ b/test/fixtures/match/sibling-no-as-implicit-returns/actual.js @@ -0,0 +1,6 @@ +f() -> + match foo(): + | 1: "ok" + + match bar(): + | 2: "oops" diff --git a/test/fixtures/match/sibling-no-as-implicit-returns/expected.js b/test/fixtures/match/sibling-no-as-implicit-returns/expected.js new file mode 100644 index 0000000..41827b9 --- /dev/null +++ b/test/fixtures/match/sibling-no-as-implicit-returns/expected.js @@ -0,0 +1,15 @@ +function f() { + const it = foo(); + + if (it === 1) { + "ok"; + } + + { + const it = bar(); + + if (it === 2) { + return "oops"; + } + } +} diff --git a/test/fixtures/match/illegal-sibling-no-as/actual.js b/test/fixtures/match/sibling-no-as/actual.js similarity index 100% rename from test/fixtures/match/illegal-sibling-no-as/actual.js rename to test/fixtures/match/sibling-no-as/actual.js diff --git a/test/fixtures/match/sibling-no-as/expected.js b/test/fixtures/match/sibling-no-as/expected.js new file mode 100644 index 0000000..934e5c4 --- /dev/null +++ b/test/fixtures/match/sibling-no-as/expected.js @@ -0,0 +1,13 @@ +const it = foo(); + +if (it === 1) { + "ok"; + } + +{ + const it = bar(); + + if (it === 2) { + "oops"; + } +} \ No newline at end of file