diff --git a/README.md b/README.md index 401fe0f..74a61da 100644 --- a/README.md +++ b/README.md @@ -299,6 +299,36 @@ import tryToCatch from 'try-catch'; const [error, result] = await tryToCatch(hello, 'world'); ``` +### [`operator-safe-assignment`](https://github.com/arthurfiorette/proposal-safe-assignment-operator) + +You can use `?=` instead of [`try`](#try): + +```gs +const [error, result] ?= hello('world'); +``` + +Is the same as: + +```js +import tryCatch from 'try-catch'; + +const [error, result] = tryCatch(hello, 'world'); +``` + +and + +```gs +const [error, result] ?= await hello('world'); +``` + +Is the same as: + +```js +import tryToCatch from 'try-catch'; + +const [error, result] = await tryToCatch(hello, 'world'); +``` + ### `should` `should` can be used as an expression (just like [`try`](https://github.com/coderaiser/goldstein/edit/master/README.md#try)). diff --git a/packages/goldstein/index.spec.js b/packages/goldstein/index.spec.js index 895a87c..08e94f8 100644 --- a/packages/goldstein/index.spec.js +++ b/packages/goldstein/index.spec.js @@ -79,6 +79,22 @@ test('goldstein: compile: try await', (t) => { t.end(); }); +test('goldstein: compile: operator: safe-assignment', (t) => { + const result = compile(montag` + const [error, result] ?= await fetch('xx'); + `); + + const expected = montag` + import tryToCatch from 'try-to-catch'; + + const [error, result] = await tryToCatch(fetch, 'xx'); + + `; + + t.equal(result, expected); + t.end(); +}); + test('goldstein: compile: should', (t) => { const result = compile(montag` should hello(a, b, c); diff --git a/packages/goldstein/parser.js b/packages/goldstein/parser.js index 9bf552f..32fd466 100644 --- a/packages/goldstein/parser.js +++ b/packages/goldstein/parser.js @@ -19,6 +19,7 @@ import keywordUselessComma from '../keyword-useless-comma/index.js'; import keywordUselessSemicolon from '../keyword-useless-semicolon/index.js'; import keywordAssignFrom from '../keyword-assign-from/index.js'; import internalParseMaybeAssign from '../internal-parse-maybe-assign/index.js'; +import operatorSafeAssignment from '../operator-safe-assignment/index.js'; const {values} = Object; @@ -40,6 +41,7 @@ const defaultKeywords = { keywordUselessComma, keywordUselessSemicolon, keywordAssignFrom, + operatorSafeAssignment, }; const internals = [ diff --git a/packages/keyword-missing-initializer/fixture/no-safe-assignment.gs b/packages/keyword-missing-initializer/fixture/no-safe-assignment.gs new file mode 100644 index 0000000..8721ade --- /dev/null +++ b/packages/keyword-missing-initializer/fixture/no-safe-assignment.gs @@ -0,0 +1,2 @@ +const [error, response] ?= await fetch("https://arthur.place"); +const [error1, response1] ?= fn("https://arthur.place"); diff --git a/packages/keyword-missing-initializer/index.js b/packages/keyword-missing-initializer/index.js index 89589d7..4fa4d80 100644 --- a/packages/keyword-missing-initializer/index.js +++ b/packages/keyword-missing-initializer/index.js @@ -9,14 +9,21 @@ export default function keywordMissingInitializer(Parser) { const decl = this.startNode(); this.parseVarId(decl, kind); - if (this.eat(tt.eq)) + if (this.eat(tt.question) && this.eat(tt.eq)) { + if (!this.parseSafeAssignment) + this.raise(this.lastTokEnd, `Enable 'operator-safe-assignment' to have ability to use '?='`); + + decl.init = this.parseSafeAssignment(); /* c8 ignore start */ + } else if (this.eat(tt.eq)) { decl.init = this.parseMaybeAssign(isFor); - else if (!allowMissingInitializer && kind === 'const' && !(this.type === tt._in || this.options.ecmaVersion >= 6 && this.isContextual('of'))) - decl.init = this.parseMaybeAssign(isFor); /* c8 ignore start */ - else if (!allowMissingInitializer && decl.id.type !== 'Identifier' && !(isFor && (this.type === tt._in || this.isContextual('of')))) + } else if (!allowMissingInitializer && kind === 'const' && !(this.type === tt._in || this.options.ecmaVersion >= 6 && this.isContextual('of'))) { + decl.init = MissingInitializer.missInitializer.call(this, isFor); + } /* c8 ignore start */ + else if (!allowMissingInitializer && decl.id.type !== 'Identifier' && !(isFor && (this.type === tt._in || this.isContextual('of')))) { this.raise(this.lastTokEnd, 'Complex binding patterns require an initialization value'); - else + } else { decl.init = null; + } /* c8 ignore end */ node.declarations.push(this.finishNode(decl, 'VariableDeclarator')); @@ -29,3 +36,10 @@ export default function keywordMissingInitializer(Parser) { } }; } + +export const MissingInitializer = { + missInitializer(isFor) { + return this.parseMaybeAssign(isFor); + }, +}; + diff --git a/packages/keyword-missing-initializer/index.spec.js b/packages/keyword-missing-initializer/index.spec.js index 0d4eb5b..25b82c2 100644 --- a/packages/keyword-missing-initializer/index.spec.js +++ b/packages/keyword-missing-initializer/index.spec.js @@ -1,9 +1,22 @@ import {createTest} from '../test/index.js'; import keywordBrokenString from './index.js'; +import operatorSafeAssignment from '../operator-safe-assignment/index.js'; const test = createTest(import.meta.url, keywordBrokenString); +const testSafeAssignment = createTest(import.meta.url, ...[keywordBrokenString, operatorSafeAssignment]); test('goldstein: missing-initializer', (t) => { t.compile('missing-initializer'); t.end(); }); + +testSafeAssignment('goldstein: missing-initializer: safeAssignment', (t) => { + t.compile('missing-initializer'); + t.end(); +}); + +test('goldstein: missing-initializer: no safeAssignment', (t) => { + t.raise('no-safe-assignment', `Enable 'operator-safe-assignment' to have ability to use '?=' (1:26)`); + t.end(); +}); + diff --git a/packages/operator-safe-assignment/fixture/not-supported.gs b/packages/operator-safe-assignment/fixture/not-supported.gs new file mode 100644 index 0000000..131eb02 --- /dev/null +++ b/packages/operator-safe-assignment/fixture/not-supported.gs @@ -0,0 +1 @@ +const a ?= 5; \ No newline at end of file diff --git a/packages/operator-safe-assignment/fixture/safe-assignment.gs b/packages/operator-safe-assignment/fixture/safe-assignment.gs new file mode 100644 index 0000000..8721ade --- /dev/null +++ b/packages/operator-safe-assignment/fixture/safe-assignment.gs @@ -0,0 +1,2 @@ +const [error, response] ?= await fetch("https://arthur.place"); +const [error1, response1] ?= fn("https://arthur.place"); diff --git a/packages/operator-safe-assignment/fixture/safe-assignment.js b/packages/operator-safe-assignment/fixture/safe-assignment.js new file mode 100644 index 0000000..b01063f --- /dev/null +++ b/packages/operator-safe-assignment/fixture/safe-assignment.js @@ -0,0 +1,2 @@ +const [error, response] = await tryToCatch(fetch, 'https://arthur.place'); +const [error1, response1] = tryCatch(fn, 'https://arthur.place'); diff --git a/packages/operator-safe-assignment/index.js b/packages/operator-safe-assignment/index.js new file mode 100644 index 0000000..8796839 --- /dev/null +++ b/packages/operator-safe-assignment/index.js @@ -0,0 +1,81 @@ +import {types} from 'putout'; +import {setGoldsteinTry} from '../types/try.js'; +import {MissingInitializer} from '../keyword-missing-initializer/index.js'; +import {tokTypes as tt} from '../operator/index.js'; + +const {assign} = Object; + +const { + isCallExpression, + isAwaitExpression, +} = types; + +export default function operatorSafeAssignment(Parser) { + return class extends Parser { + parseVar(node, isFor, kind, allowMissingInitializer) { + node.declarations = []; + node.kind = kind; + for (;;) { + const decl = this.startNode(); + this.parseVarId(decl, kind); + + if (this.eat(tt.question) && this.eat(tt.eq)) + decl.init = this.parseSafeAssignment(); /* c8 ignore start */ + else if (this.eat(tt.eq)) + decl.init = this.parseMaybeAssign(isFor); + else if (!allowMissingInitializer && kind === 'const' && !(this.type === tt._in || this.options.ecmaVersion >= 6 && this.isContextual('of'))) + decl.init = MissingInitializer.missInitializer.call(this, isFor); + else if (!allowMissingInitializer && decl.id.type !== 'Identifier' && !(isFor && (this.type === tt._in || this.isContextual('of')))) + this.raise(this.lastTokEnd, 'Complex binding patterns require an initialization value'); + else + decl.init = null; + + /* c8 ignore end */ + node.declarations.push(this.finishNode(decl, 'VariableDeclarator')); + + if (!this.eat(tt.comma)) + break; + } + + return node; + } + + parseSafeAssignment() { + const node = super.startNode(); + const expression = this.parseExpression(); + + if (isCallExpression(expression)) + assign(node, { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'tryCatch', + }, + arguments: [ + expression.callee, + ...expression.arguments, + ], + }); + else if (isAwaitExpression(expression)) + assign(node, { + type: 'AwaitExpression', + argument: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'tryToCatch', + }, + arguments: [ + expression.argument.callee, + ...expression.argument.arguments, + ], + }, + }); + else + this.raise(this.start, `After '?=' only 'await' and 'function call' can come`); + + setGoldsteinTry(node); + return super.finishNode(node, node.type); + } + }; +} diff --git a/packages/operator-safe-assignment/index.spec.js b/packages/operator-safe-assignment/index.spec.js new file mode 100644 index 0000000..ef528cc --- /dev/null +++ b/packages/operator-safe-assignment/index.spec.js @@ -0,0 +1,18 @@ +import {createTest} from '../test/index.js'; +import operatorSafeAssignment from './index.js'; +import keywordMissingInitializer from '../keyword-missing-initializer/index.js'; + +const test = createTest(import.meta.url, ...[ + operatorSafeAssignment, + keywordMissingInitializer, +]); + +test('goldstein: operator: safe-assignment', (t) => { + t.compile('safe-assignment'); + t.end(); +}); + +test('goldstein: operator: safe-assignment: not-supported', (t) => { + t.raise('not-supported', `After '?=' only 'await' and 'function call' can come (1:12)`); + t.end(); +});