Skip to content

Commit

Permalink
feature: goldstein: add support of safe assignment operator (arthurfi…
Browse files Browse the repository at this point in the history
  • Loading branch information
coderaiser committed Sep 6, 2024
1 parent 058c99c commit b2b030b
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 5 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)).
Expand Down
16 changes: 16 additions & 0 deletions packages/goldstein/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions packages/goldstein/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -40,6 +41,7 @@ const defaultKeywords = {
keywordUselessComma,
keywordUselessSemicolon,
keywordAssignFrom,
operatorSafeAssignment,
};

const internals = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const [error, response] ?= await fetch("https://arthur.place");
const [error1, response1] ?= fn("https://arthur.place");
24 changes: 19 additions & 5 deletions packages/keyword-missing-initializer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand All @@ -29,3 +36,10 @@ export default function keywordMissingInitializer(Parser) {
}
};
}

export const MissingInitializer = {
missInitializer(isFor) {
return this.parseMaybeAssign(isFor);
},
};

13 changes: 13 additions & 0 deletions packages/keyword-missing-initializer/index.spec.js
Original file line number Diff line number Diff line change
@@ -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();
});

1 change: 1 addition & 0 deletions packages/operator-safe-assignment/fixture/not-supported.gs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const a ?= 5;
2 changes: 2 additions & 0 deletions packages/operator-safe-assignment/fixture/safe-assignment.gs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const [error, response] ?= await fetch("https://arthur.place");
const [error1, response1] ?= fn("https://arthur.place");
2 changes: 2 additions & 0 deletions packages/operator-safe-assignment/fixture/safe-assignment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const [error, response] = await tryToCatch(fetch, 'https://arthur.place');
const [error1, response1] = tryCatch(fn, 'https://arthur.place');
81 changes: 81 additions & 0 deletions packages/operator-safe-assignment/index.js
Original file line number Diff line number Diff line change
@@ -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);
}
};
}
18 changes: 18 additions & 0 deletions packages/operator-safe-assignment/index.spec.js
Original file line number Diff line number Diff line change
@@ -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();
});

0 comments on commit b2b030b

Please sign in to comment.