mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
Add Implicit Async Functions (#3757)
* changed jison acceptable versions * added await support * wrong function bug fix * added tests for async/await * invalid to have await, yield(from) in same function * changed error handling and tests * bug fix * made error handling test more rigorous * consolidated harmony test files * added async constructor support and tests * removed .orig files * Fixed browser testing issue * Minor cleanup * Async test-suite and Cake support, simplified/removed funky tests * Skip async/await tests when not supported in runtime * cleanup * Replaced polyfill with native JS async/await * Oops * Make 'async' reserved word * Remove all async polyfills * fix merge conflict * make async testing opt-in * restore test, remove confusing polyfill language * Revert changes to test runners * Only run async tests where async/await is supported (Node 7+ with --harmony, for now) * remove 'async' from JS reserved words * The async tests should use their own special async-capable version of `global.test`, which is only loaded for the async tests and only loaded by async-capable environments * Reverting rename of `async`, it’s not a reserved word so there’s no longer a need for this change * async test refactoring and additions * oops * sync * better error reporting for `await` * more stuff geoffrey wants * fixed litcoffee tests * change test title
This commit is contained in:
parent
a1bcf7f1d9
commit
496fd5d3d3
13 changed files with 541 additions and 213 deletions
20
Cakefile
20
Cakefile
|
@ -228,10 +228,14 @@ task 'bench', 'quick benchmark of compilation time', ->
|
|||
# Run the CoffeeScript test suite.
|
||||
runTests = (CoffeeScript) ->
|
||||
CoffeeScript.register()
|
||||
startTime = Date.now()
|
||||
currentFile = null
|
||||
passedTests = 0
|
||||
failures = []
|
||||
startTime = Date.now()
|
||||
|
||||
# These are attached to `global` so that they’re accessible from within
|
||||
# `test/async.coffee`, which has an async-capable version of
|
||||
# `global.test`.
|
||||
global.currentFile = null
|
||||
global.passedTests = 0
|
||||
global.failures = []
|
||||
|
||||
global[name] = func for name, func of require 'assert'
|
||||
|
||||
|
@ -288,10 +292,10 @@ runTests = (CoffeeScript) ->
|
|||
# Run every test in the `test` folder, recording failures.
|
||||
files = fs.readdirSync 'test'
|
||||
|
||||
# Ignore generators test file if generators are not available
|
||||
generatorsAreAvailable = '--harmony' in process.execArgv or
|
||||
'--harmony-generators' in process.execArgv
|
||||
files.splice files.indexOf('generators.coffee'), 1 if not generatorsAreAvailable
|
||||
# Ignore async test file if async/await is not available
|
||||
asyncSupported = parseInt(process.versions.node.split('.')[0]) >= 7 and
|
||||
('--harmony' in process.execArgv or '--harmony-async-await' in process.execArgv)
|
||||
files.splice files.indexOf('async.coffee'), 1 unless asyncSupported
|
||||
|
||||
for file in files when helpers.isCoffee file
|
||||
literate = helpers.isLiterate file
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -41,7 +41,8 @@
|
|||
return $1.push($3);
|
||||
}), o('Body TERMINATOR')
|
||||
],
|
||||
Line: [o('Expression'), o('Statement'), o('YieldReturn')],
|
||||
Line: [o('Expression'), o('Statement'), o('FuncDirective')],
|
||||
FuncDirective: [o('YieldReturn'), o('AwaitReturn')],
|
||||
Statement: [
|
||||
o('Return'), o('Comment'), o('STATEMENT', function() {
|
||||
return new StatementLiteral($1);
|
||||
|
@ -154,6 +155,13 @@
|
|||
return new YieldReturn;
|
||||
})
|
||||
],
|
||||
AwaitReturn: [
|
||||
o('AWAIT RETURN Expression', function() {
|
||||
return new AwaitReturn($3);
|
||||
}), o('AWAIT RETURN', function() {
|
||||
return new AwaitReturn;
|
||||
})
|
||||
],
|
||||
Comment: [
|
||||
o('HERECOMMENT', function() {
|
||||
return new Comment($1);
|
||||
|
@ -702,6 +710,8 @@
|
|||
return new Op('+', $2);
|
||||
}), {
|
||||
prec: 'UNARY_MATH'
|
||||
}), o('AWAIT Expression', function() {
|
||||
return new Op($1, $2);
|
||||
}), o('-- SimpleAssignable', function() {
|
||||
return new Op('--', $2);
|
||||
}), o('++ SimpleAssignable', function() {
|
||||
|
@ -754,7 +764,7 @@
|
|||
]
|
||||
};
|
||||
|
||||
operators = [['left', '.', '?.', '::', '?::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY'], ['right', '**'], ['right', 'UNARY_MATH'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', '&'], ['left', '^'], ['left', '|'], ['left', '&&'], ['left', '||'], ['left', 'BIN?'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', 'YIELD'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'IMPORT', 'EXPORT'], ['left', 'POST_IF']];
|
||||
operators = [['left', '.', '?.', '::', '?::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY'], ['right', 'AWAIT'], ['right', '**'], ['right', 'UNARY_MATH'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', '&'], ['left', '^'], ['left', '|'], ['left', '&&'], ['left', '||'], ['left', 'BIN?'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', 'YIELD'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'IMPORT', 'EXPORT'], ['left', 'POST_IF']];
|
||||
|
||||
tokens = [];
|
||||
|
||||
|
|
|
@ -947,7 +947,7 @@
|
|||
|
||||
exports.isUnassignable = isUnassignable;
|
||||
|
||||
JS_KEYWORDS = ['true', 'false', 'null', 'this', 'new', 'delete', 'typeof', 'in', 'instanceof', 'return', 'throw', 'break', 'continue', 'debugger', 'yield', 'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally', 'class', 'extends', 'super', 'import', 'export', 'default'];
|
||||
JS_KEYWORDS = ['true', 'false', 'null', 'this', 'new', 'delete', 'typeof', 'in', 'instanceof', 'return', 'throw', 'break', 'continue', 'debugger', 'yield', 'await', 'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally', 'class', 'extends', 'super', 'import', 'export', 'default'];
|
||||
|
||||
COFFEE_KEYWORDS = ['undefined', 'Infinity', 'NaN', 'then', 'unless', 'until', 'loop', 'of', 'by', 'when'];
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Generated by CoffeeScript 2.0.0-alpha
|
||||
(function() {
|
||||
var Access, Arr, Assign, Base, Block, BooleanLiteral, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, SuperCall, Switch, TAB, THIS, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, isUnassignable, locationDataToString, merge, multident, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility,
|
||||
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, SuperCall, Switch, TAB, THIS, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, isUnassignable, locationDataToString, merge, multident, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility,
|
||||
extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
||||
hasProp = {}.hasOwnProperty,
|
||||
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
|
||||
|
@ -87,7 +87,7 @@
|
|||
};
|
||||
|
||||
Base.prototype.compileClosure = function(o) {
|
||||
var args, argumentsNode, func, jumpNode, meth, parts, ref3;
|
||||
var args, argumentsNode, func, jumpNode, meth, parts, ref3, ref4;
|
||||
if (jumpNode = this.jumps()) {
|
||||
jumpNode.error('cannot use a pure statement in an expression');
|
||||
}
|
||||
|
@ -105,9 +105,14 @@
|
|||
func = new Value(func, [new Access(new PropertyName(meth))]);
|
||||
}
|
||||
parts = (new Call(func, args)).compileNode(o);
|
||||
if (func.isGenerator || ((ref3 = func.base) != null ? ref3.isGenerator : void 0)) {
|
||||
parts.unshift(this.makeCode("(yield* "));
|
||||
parts.push(this.makeCode(")"));
|
||||
switch (false) {
|
||||
case !(func.isGenerator || ((ref3 = func.base) != null ? ref3.isGenerator : void 0)):
|
||||
parts.unshift(this.makeCode("(yield* "));
|
||||
parts.push(this.makeCode(")"));
|
||||
break;
|
||||
case !(func.isAsync || ((ref4 = func.base) != null ? ref4.isAsync : void 0)):
|
||||
parts.unshift(this.makeCode("(await "));
|
||||
parts.push(this.makeCode(")"));
|
||||
}
|
||||
return parts;
|
||||
};
|
||||
|
@ -779,6 +784,24 @@
|
|||
|
||||
})(Return);
|
||||
|
||||
exports.AwaitReturn = AwaitReturn = (function(superClass1) {
|
||||
extend1(AwaitReturn, superClass1);
|
||||
|
||||
function AwaitReturn() {
|
||||
return AwaitReturn.__super__.constructor.apply(this, arguments);
|
||||
}
|
||||
|
||||
AwaitReturn.prototype.compileNode = function(o) {
|
||||
if (o.scope.parent == null) {
|
||||
this.error('await can only occur inside functions');
|
||||
}
|
||||
return AwaitReturn.__super__.compileNode.apply(this, arguments);
|
||||
};
|
||||
|
||||
return AwaitReturn;
|
||||
|
||||
})(Return);
|
||||
|
||||
exports.Value = Value = (function(superClass1) {
|
||||
extend1(Value, superClass1);
|
||||
|
||||
|
@ -2368,8 +2391,18 @@
|
|||
this.params = params || [];
|
||||
this.body = body || new Block;
|
||||
this.bound = tag === 'boundfunc';
|
||||
this.isGenerator = !!this.body.contains(function(node) {
|
||||
return (node instanceof Op && node.isYield()) || node instanceof YieldReturn;
|
||||
this.isGenerator = false;
|
||||
this.isAsync = false;
|
||||
this.body.traverseChildren(false, (node) => {
|
||||
if ((node instanceof Op && node.isYield()) || node instanceof YieldReturn) {
|
||||
this.isGenerator = true;
|
||||
}
|
||||
if ((node instanceof Op && node.isAwait()) || node instanceof AwaitReturn) {
|
||||
this.isAsync = true;
|
||||
}
|
||||
if (this.isGenerator && this.isAsync) {
|
||||
return node.error("function can't contain both yield and await");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2482,6 +2515,9 @@
|
|||
this.body.makeReturn();
|
||||
}
|
||||
code = '';
|
||||
if (this.isAsync) {
|
||||
code += 'async ';
|
||||
}
|
||||
if (!this.bound) {
|
||||
code += 'function';
|
||||
if (this.isGenerator) {
|
||||
|
@ -2849,6 +2885,10 @@
|
|||
return this.isUnary() && ((ref3 = this.operator) === '+' || ref3 === '-') && this.first instanceof Value && this.first.isNumber();
|
||||
};
|
||||
|
||||
Op.prototype.isAwait = function() {
|
||||
return this.operator === 'await';
|
||||
};
|
||||
|
||||
Op.prototype.isYield = function() {
|
||||
var ref3;
|
||||
return (ref3 = this.operator) === 'yield' || ref3 === 'yield*';
|
||||
|
@ -2940,8 +2980,8 @@
|
|||
this.first.error(message);
|
||||
}
|
||||
}
|
||||
if (this.isYield()) {
|
||||
return this.compileYield(o);
|
||||
if (this.isYield() || this.isAwait()) {
|
||||
return this.compileContinuation(o);
|
||||
}
|
||||
if (this.isUnary()) {
|
||||
return this.compileUnary(o);
|
||||
|
@ -3018,12 +3058,12 @@
|
|||
return this.joinFragmentArrays(parts, '');
|
||||
};
|
||||
|
||||
Op.prototype.compileYield = function(o) {
|
||||
Op.prototype.compileContinuation = function(o) {
|
||||
var op, parts, ref3, ref4;
|
||||
parts = [];
|
||||
op = this.operator;
|
||||
if (o.scope.parent == null) {
|
||||
this.error('yield can only occur inside functions');
|
||||
this.error(this.operator + " can only occur inside functions");
|
||||
}
|
||||
if (((ref3 = o.scope.method) != null ? ref3.bound : void 0) && o.scope.method.isGenerator) {
|
||||
this.error('yield cannot occur inside bound (fat arrow) functions');
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -501,7 +501,7 @@
|
|||
|
||||
IMPLICIT_FUNC = ['IDENTIFIER', 'PROPERTY', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS'];
|
||||
|
||||
IMPLICIT_CALL = ['IDENTIFIER', 'PROPERTY', 'NUMBER', 'INFINITY', 'NAN', 'STRING', 'STRING_START', 'REGEX', 'REGEX_START', 'JS', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'UNDEFINED', 'NULL', 'BOOL', 'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'];
|
||||
IMPLICIT_CALL = ['IDENTIFIER', 'PROPERTY', 'NUMBER', 'INFINITY', 'NAN', 'STRING', 'STRING_START', 'REGEX', 'REGEX_START', 'JS', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'UNDEFINED', 'NULL', 'BOOL', 'UNARY', 'YIELD', 'AWAIT', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'];
|
||||
|
||||
IMPLICIT_UNSPACED_CALL = ['+', '-'];
|
||||
|
||||
|
|
|
@ -89,7 +89,12 @@ grammar =
|
|||
Line: [
|
||||
o 'Expression'
|
||||
o 'Statement'
|
||||
o 'FuncDirective'
|
||||
]
|
||||
|
||||
FuncDirective: [
|
||||
o 'YieldReturn'
|
||||
o 'AwaitReturn'
|
||||
]
|
||||
|
||||
# Pure statements which cannot be expressions.
|
||||
|
@ -219,6 +224,12 @@ grammar =
|
|||
o 'YIELD RETURN', -> new YieldReturn
|
||||
]
|
||||
|
||||
AwaitReturn: [
|
||||
o 'AWAIT RETURN Expression', -> new AwaitReturn $3
|
||||
o 'AWAIT RETURN', -> new AwaitReturn
|
||||
]
|
||||
|
||||
|
||||
# A block comment.
|
||||
Comment: [
|
||||
o 'HERECOMMENT', -> new Comment $1
|
||||
|
@ -644,6 +655,8 @@ grammar =
|
|||
o '- Expression', (-> new Op '-', $2), prec: 'UNARY_MATH'
|
||||
o '+ Expression', (-> new Op '+', $2), prec: 'UNARY_MATH'
|
||||
|
||||
o 'AWAIT Expression', -> new Op $1 , $2
|
||||
|
||||
o '-- SimpleAssignable', -> new Op '--', $2
|
||||
o '++ SimpleAssignable', -> new Op '++', $2
|
||||
o 'SimpleAssignable --', -> new Op '--', $1, null, true
|
||||
|
@ -698,6 +711,7 @@ operators = [
|
|||
['nonassoc', '++', '--']
|
||||
['left', '?']
|
||||
['right', 'UNARY']
|
||||
['right', 'AWAIT']
|
||||
['right', '**']
|
||||
['right', 'UNARY_MATH']
|
||||
['left', 'MATH']
|
||||
|
|
|
@ -834,7 +834,7 @@ exports.isUnassignable = isUnassignable
|
|||
JS_KEYWORDS = [
|
||||
'true', 'false', 'null', 'this'
|
||||
'new', 'delete', 'typeof', 'in', 'instanceof'
|
||||
'return', 'throw', 'break', 'continue', 'debugger', 'yield'
|
||||
'return', 'throw', 'break', 'continue', 'debugger', 'yield', 'await'
|
||||
'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally'
|
||||
'class', 'extends', 'super'
|
||||
'import', 'export', 'default'
|
||||
|
|
|
@ -90,9 +90,14 @@ exports.Base = class Base
|
|||
meth = 'call'
|
||||
func = new Value func, [new Access new PropertyName meth]
|
||||
parts = (new Call func, args).compileNode o
|
||||
if func.isGenerator or func.base?.isGenerator
|
||||
parts.unshift @makeCode "(yield* "
|
||||
parts.push @makeCode ")"
|
||||
|
||||
switch
|
||||
when func.isGenerator or func.base?.isGenerator
|
||||
parts.unshift @makeCode "(yield* "
|
||||
parts.push @makeCode ")"
|
||||
when func.isAsync or func.base?.isAsync
|
||||
parts.unshift @makeCode "(await "
|
||||
parts.push @makeCode ")"
|
||||
parts
|
||||
|
||||
# If the code generation wishes to use the result of a complex expression
|
||||
|
@ -491,6 +496,14 @@ exports.YieldReturn = class YieldReturn extends Return
|
|||
@error 'yield can only occur inside functions'
|
||||
super
|
||||
|
||||
|
||||
exports.AwaitReturn = class AwaitReturn extends Return
|
||||
compileNode: (o) ->
|
||||
unless o.scope.parent?
|
||||
@error 'await can only occur inside functions'
|
||||
super
|
||||
|
||||
|
||||
#### Value
|
||||
|
||||
# A value, variable or literal or parenthesized, indexed or dotted into,
|
||||
|
@ -1589,8 +1602,16 @@ exports.Code = class Code extends Base
|
|||
@params = params or []
|
||||
@body = body or new Block
|
||||
@bound = tag is 'boundfunc'
|
||||
@isGenerator = !!@body.contains (node) ->
|
||||
(node instanceof Op and node.isYield()) or node instanceof YieldReturn
|
||||
@isGenerator = no
|
||||
@isAsync = no
|
||||
|
||||
@body.traverseChildren no, (node) =>
|
||||
if (node instanceof Op and node.isYield()) or node instanceof YieldReturn
|
||||
@isGenerator = yes
|
||||
if (node instanceof Op and node.isAwait()) or node instanceof AwaitReturn
|
||||
@isAsync = yes
|
||||
if @isGenerator and @isAsync
|
||||
node.error "function can't contain both yield and await"
|
||||
|
||||
children: ['params', 'body']
|
||||
|
||||
|
@ -1710,6 +1731,7 @@ exports.Code = class Code extends Base
|
|||
|
||||
# Assemble the output
|
||||
code = ''
|
||||
code += 'async ' if @isAsync
|
||||
unless @bound
|
||||
code += 'function'
|
||||
code += '*' if @isGenerator # Arrow functions can’t be generators
|
||||
|
@ -1964,6 +1986,9 @@ exports.Op = class Op extends Base
|
|||
@isUnary() and @operator in ['+', '-'] and
|
||||
@first instanceof Value and @first.isNumber()
|
||||
|
||||
isAwait: ->
|
||||
@operator is 'await'
|
||||
|
||||
isYield: ->
|
||||
@operator in ['yield', 'yield*']
|
||||
|
||||
|
@ -2034,9 +2059,9 @@ exports.Op = class Op extends Base
|
|||
if @operator in ['--', '++']
|
||||
message = isUnassignable @first.unwrapAll().value
|
||||
@first.error message if message
|
||||
return @compileYield o if @isYield()
|
||||
return @compileUnary o if @isUnary()
|
||||
return @compileChain o if isChain
|
||||
return @compileContinuation o if @isYield() or @isAwait()
|
||||
return @compileUnary o if @isUnary()
|
||||
return @compileChain o if isChain
|
||||
switch @operator
|
||||
when '?' then @compileExistence o
|
||||
when '**' then @compilePower o
|
||||
|
@ -2089,11 +2114,11 @@ exports.Op = class Op extends Base
|
|||
parts.reverse() if @flip
|
||||
@joinFragmentArrays parts, ''
|
||||
|
||||
compileYield: (o) ->
|
||||
compileContinuation: (o) ->
|
||||
parts = []
|
||||
op = @operator
|
||||
unless o.scope.parent?
|
||||
@error 'yield can only occur inside functions'
|
||||
@error "#{@operator} can only occur inside functions"
|
||||
if o.scope.method?.bound and o.scope.method.isGenerator
|
||||
@error 'yield cannot occur inside bound (fat arrow) functions'
|
||||
if 'expression' in Object.keys(@first) and not (@first instanceof Throw)
|
||||
|
|
|
@ -503,7 +503,7 @@ IMPLICIT_CALL = [
|
|||
'STRING', 'STRING_START', 'REGEX', 'REGEX_START', 'JS'
|
||||
'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS'
|
||||
'UNDEFINED', 'NULL', 'BOOL'
|
||||
'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW'
|
||||
'UNARY', 'YIELD', 'AWAIT', 'UNARY_MATH', 'SUPER', 'THROW'
|
||||
'@', '->', '=>', '[', '(', '{', '--', '++'
|
||||
]
|
||||
|
||||
|
|
193
test/async.coffee
Normal file
193
test/async.coffee
Normal file
|
@ -0,0 +1,193 @@
|
|||
# Functions that contain the `await` keyword will compile into async
|
||||
# functions, which currently only Node 7+ in harmony mode can even
|
||||
# evaluate, much less run. Therefore we need to prevent runtimes
|
||||
# which will choke on such code from even loading it. This file is
|
||||
# only loaded by async-capable environments, so we redefine `test`
|
||||
# here even though it is based on `test` defined in `Cakefile`.
|
||||
# It replaces `test` for this file, and adds to the tracked
|
||||
# `passedTests` and `failures` arrays which are global objects.
|
||||
test = (description, fn) ->
|
||||
try
|
||||
fn.test = {description, currentFile}
|
||||
await fn.call(fn)
|
||||
++passedTests
|
||||
catch e
|
||||
failures.push
|
||||
filename: currentFile
|
||||
error: e
|
||||
description: description if description?
|
||||
source: fn.toString() if fn.toString?
|
||||
|
||||
|
||||
# always fulfills
|
||||
winning = (val) -> Promise.resolve val
|
||||
|
||||
# always is rejected
|
||||
failing = (val) -> Promise.reject new Error val
|
||||
|
||||
|
||||
test "async as argument", ->
|
||||
ok ->
|
||||
await winning()
|
||||
|
||||
test "explicit async", ->
|
||||
a = do ->
|
||||
await return 5
|
||||
eq a.constructor, Promise
|
||||
a.then (val) ->
|
||||
eq val, 5
|
||||
|
||||
test "implicit async", ->
|
||||
a = do ->
|
||||
x = await winning(5)
|
||||
y = await winning(4)
|
||||
z = await winning(3)
|
||||
[x, y, z]
|
||||
|
||||
eq a.constructor, Promise
|
||||
|
||||
test "async return value (implicit)", ->
|
||||
out = null
|
||||
a = ->
|
||||
x = await winning(5)
|
||||
y = await winning(4)
|
||||
z = await winning(3)
|
||||
[x, y, z]
|
||||
|
||||
b = do ->
|
||||
out = await a()
|
||||
|
||||
b.then ->
|
||||
arrayEq out, [5, 4, 3]
|
||||
|
||||
test "async return value (explicit)", ->
|
||||
out = null
|
||||
a = ->
|
||||
await return [5, 2, 3]
|
||||
|
||||
b = do ->
|
||||
out = await a()
|
||||
|
||||
b.then ->
|
||||
arrayEq out, [5, 2, 3]
|
||||
|
||||
|
||||
test "async parameters", ->
|
||||
[out1, out2] = [null, null]
|
||||
a = (a, [b, c])->
|
||||
arr = [a]
|
||||
arr.push b
|
||||
arr.push c
|
||||
await return arr
|
||||
|
||||
b = (a, b, c = 5)->
|
||||
arr = [a]
|
||||
arr.push b
|
||||
arr.push c
|
||||
await return arr
|
||||
|
||||
c = do ->
|
||||
out1 = await a(5, [4, 3])
|
||||
out2 = await b(4, 4)
|
||||
|
||||
c.then ->
|
||||
arrayEq out1, [5, 4, 3]
|
||||
arrayEq out2, [4, 4, 5]
|
||||
|
||||
test "async `this` scoping", ->
|
||||
bnd = null
|
||||
ubnd = null
|
||||
nst = null
|
||||
obj =
|
||||
bound: ->
|
||||
return do =>
|
||||
await return this
|
||||
unbound: ->
|
||||
return do ->
|
||||
await return this
|
||||
nested: ->
|
||||
return do =>
|
||||
await do =>
|
||||
await do =>
|
||||
await return this
|
||||
|
||||
promise = do ->
|
||||
bnd = await obj.bound()
|
||||
ubnd = await obj.unbound()
|
||||
nst = await obj.nested()
|
||||
|
||||
promise.then ->
|
||||
eq bnd, obj
|
||||
ok ubnd isnt obj
|
||||
eq nst, obj
|
||||
|
||||
test "await precedence", ->
|
||||
out = null
|
||||
|
||||
fn = (win, fail) ->
|
||||
win(3)
|
||||
|
||||
promise = do ->
|
||||
# assert precedence between unary (new) and power (**) operators
|
||||
out = 1 + await new Promise(fn) ** 2
|
||||
|
||||
promise.then ->
|
||||
eq out, 10
|
||||
|
||||
test "`await` inside IIFEs", ->
|
||||
[x, y, z] = new Array(3)
|
||||
|
||||
a = do ->
|
||||
x = switch (4) # switch 4
|
||||
when 2
|
||||
await winning(1)
|
||||
when 4
|
||||
await winning(5)
|
||||
when 7
|
||||
await winning(2)
|
||||
|
||||
y = try
|
||||
text = "this should be caught"
|
||||
throw new Error(text)
|
||||
await winning(1)
|
||||
catch e
|
||||
await winning(4)
|
||||
|
||||
z = for i in [0..5]
|
||||
a = i * i
|
||||
await winning(a)
|
||||
|
||||
a.then ->
|
||||
eq x, 5
|
||||
eq y, 4
|
||||
|
||||
arrayEq z, [0, 1, 4, 9, 16, 25]
|
||||
|
||||
test "error handling", ->
|
||||
res = null
|
||||
val = 0
|
||||
a = ->
|
||||
try
|
||||
await failing("fail")
|
||||
catch e
|
||||
val = 7 # to assure the catch block runs
|
||||
return e
|
||||
|
||||
b = do ->
|
||||
res = await a()
|
||||
|
||||
b.then ->
|
||||
eq val, 7
|
||||
|
||||
ok res.message?
|
||||
eq res.message, "fail"
|
||||
|
||||
test "await expression evaluates to argument if not A+", ->
|
||||
eq(await 4, 4)
|
||||
|
||||
|
||||
test "implicit call with `await`", ->
|
||||
addOne = (arg) -> arg + 1
|
||||
|
||||
a = addOne await 3
|
||||
eq a, 4
|
|
@ -1169,3 +1169,34 @@ test "CoffeeScript keywords cannot be used as local names in import list aliases
|
|||
import { bar as unless, baz as bar } from 'lib'
|
||||
^^^^^^
|
||||
'''
|
||||
|
||||
test "function cannot contain both `await` and `yield`", ->
|
||||
assertErrorFormat '''
|
||||
f = () ->
|
||||
yield 5
|
||||
await a
|
||||
''', '''
|
||||
[stdin]:3:3: error: function can't contain both yield and await
|
||||
await a
|
||||
^^^^^^^
|
||||
'''
|
||||
|
||||
test "function cannot contain both `await` and `yield from`", ->
|
||||
assertErrorFormat '''
|
||||
f = () ->
|
||||
yield from a
|
||||
await b
|
||||
''', '''
|
||||
[stdin]:3:3: error: function can't contain both yield and await
|
||||
await b
|
||||
^^^^^^^
|
||||
'''
|
||||
|
||||
test "Cannnot have `await` outside a function", ->
|
||||
assertErrorFormat '''
|
||||
await 1
|
||||
''', '''
|
||||
[stdin]:1:1: error: await can only occur inside functions
|
||||
await 1
|
||||
^^^^^^^
|
||||
'''
|
||||
|
|
Loading…
Reference in a new issue