├── .gitignore ├── LICENSE ├── README.md ├── acorn-es7-plugin.js ├── acorn-v3.js ├── acorn-v4.js ├── package.json └── test ├── babel.js ├── mocha.opts ├── package.json ├── test-es5.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015,2016 Matt Woolf (MatAtBread, MatAtWork) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM](https://nodei.co/npm/acorn-es7-plugin.png?downloads=true&downloadRank=true)](https://nodei.co/npm/acorn-es7-plugin/) 2 | 3 | acorn-es7-plugin 4 | ====== 5 | 6 | acorn-es7-plugin is a plugin for the [Acorn](https://github.com/marijnh/acorn) parser that generates ESTrees following the ['experimental' specification](https://github.com/estree/estree/blob/master/experimental/async-functions.md) for asynchronous functions. 7 | 8 | npm install --save acorn-es7-plugin 9 | 10 | Usage 11 | ===== 12 | 13 | Adding the plugin 14 | 15 | // Require acorn as usual 16 | var acorn = require("acorn"); 17 | // Add the es7-plugin 18 | require('./acorn-es7-plugin')(acorn) ; 19 | 20 | Using the plugin 21 | 22 | var code = "async function x(){ if (x) return await(x-1) ; return 0 ; }\n"; 23 | var ast = acorn.parse(code,{ 24 | // Specify use of the plugin 25 | plugins:{asyncawait:true}, 26 | // Specify the ecmaVersion 27 | ecmaVersion:7 28 | }) ; 29 | // Show the AST 30 | console.log(JSON.stringify(ast,null,2)) ; 31 | 32 | Output: 33 | 34 | { 35 | "type": "Program", 36 | "body": [ 37 | { 38 | "type": "FunctionDeclaration", 39 | "id": { 40 | "type": "Identifier", 41 | "name": "x" 42 | }, 43 | "generator": false, 44 | "expression": false, 45 | "params": [], 46 | "body": { 47 | "type": "BlockStatement", 48 | "body": [ 49 | { 50 | "type": "IfStatement", 51 | "test": { 52 | "type": "Identifier", 53 | "name": "x" 54 | }, 55 | "consequent": { 56 | "type": "ReturnStatement", 57 | "argument": { 58 | "type": "AwaitExpression", 59 | "operator": "await", 60 | "argument": { 61 | "type": "BinaryExpression", 62 | "left": { 63 | "type": "Identifier", 64 | "name": "x" 65 | }, 66 | "operator": "-", 67 | "right": { 68 | "type": "Literal", 69 | "value": 1, 70 | "raw": "1" 71 | } 72 | } 73 | } 74 | }, 75 | "alternate": null 76 | }, 77 | { 78 | "type": "ReturnStatement", 79 | "argument": { 80 | "type": "Literal", 81 | "value": 0, 82 | "raw": "0" 83 | } 84 | } 85 | ] 86 | }, 87 | "async": true 88 | } 89 | ], 90 | "sourceType": "script" 91 | } 92 | 93 | Options & Compliance 94 | ==================== 95 | The parser attempts to enforce strict contextualisation of `async` and `await`. Specifically, `async` is only a keyword if it precedes a function declaration, function expression or arrow function. `await` is only a keyword inside an `async` function. Outside of these contexts, both tokens are treated as identifiers (as they were in ES6 and earlier). 96 | 97 | When using the plugin, you can supply an object in place of the 'true' flag with the following options. 98 | 99 | | flag | meaning | 100 | |------|---------| 101 | | awaitAnywhere | If `await` is used outside of an async function and could not be an identifier, generate an AwaitExpression node. This typically means you can use `await` anywhere _except_ when its argument would require parentheses, as this parses to a call to 'await(....)'. Should not be used with inAsyncFunction option | 102 | | inAsyncFunction | Parse the code as if it is the body of an `async function`. This means `await` cannot be an identifier and is always an AwaitExpression, even if the argument is parenthesized. Should not be used with the awaitAnywhere option | 103 | | asyncExits | Allow the additional sequences `async return ` and `async throw `. These sequences are used with [nodent](https://github.com/MatAtBread/nodent). In each case, as with async functions, a standard ReturnStatement (or ThrowStatement) node is generated, with an additional member 'async' set to true. 104 | 105 | The parser also accepts async getters in object literals and classes, which is currently disallowed by the ES7 specification. 106 | 107 | Changelog 108 | ========= 109 | 110 | 30-Mar-17: v1.1.7 111 | 112 | - Fix parsing of IIAFE `(async function (){ }())` with acorn v3. 113 | 114 | 07-Oct-16: v1.1.1 115 | 116 | - Fix disambiguation of async, get and set tokens (and add tests) when defining object properties: 117 | 118 | | Code | Interpretation 119 | |-----------------------|-------------------------| 120 | | `get async(){}` | Is a standard ES6 getter called 'async' 121 | | `set async(v){}` | Is a standard ES6 setter called 'async' 122 | | `async get(...){}` | Is a standard ES7 async method called 'get' 123 | | `async set(...){}` | Is a standard ES7 async method called 'set' 124 | | `async get x(){}` | Is an extension that defines an async getter called 'x' 125 | | `get async x(){}` | Is an extension that defines an async getter called 'x', but is deprecated (`async` should proceed `get`) 126 | 127 | In previous version of the plugin, the standard ES7 constructs were hidden by the plugin extensions and a SyntaxError was incorrectly thrown 128 | 129 | 25-Sep-16: v1.1.0 130 | 131 | - Update to work with acorn v4 if present. Note that `async` and `await` are fully parsed by acorn v4. The only use case for the plugin with acorn v4 is with the flags above which are enable specific parsing modes. 132 | 133 | 24-Sep-16: v1.0.18 134 | 135 | - Correctly parse `async(()=>0)` as a call to the Identifer 'async', not a failed attempt to define an async arrow. 136 | 137 | 20-Jul-16: v1.0.17 138 | 139 | - Correctly set non-standard "range" property on async nodes, as used by Webpack 140 | 141 | 27-Jun-16: v1.0.15 142 | 143 | - Fix issue parsing async methods in classes and object literals which (incorrectly) required the `awaitAnywhere` option ([see https://github.com/MatAtBread/acorn-es7-plugin/issues/12](https://github.com/MatAtBread/acorn-es7-plugin/issues/12)) 144 | 145 | 03-May-16: v1.0.14 146 | 147 | - Correctly parse async statements containing comments. 148 | - Implement the option inAsyncFunction 149 | 150 | 03-May-16: v1.0.13 151 | 152 | - Correctly parse the statement `export async function name(){...}` as _async function name(){...}_ is a valid named declaration. 153 | 154 | 26-Feb-16: v1.0.12 155 | 156 | - Updated to return the original acorn object on installation. See https://github.com/MatAtBread/acorn-es7-plugin/pull/4 157 | 158 | 19-Dec-15: v1.0.11 159 | 160 | - Generate error if 'await' is used as an identifier within an async function. 161 | 162 | 10-Dec-15: v1.0.10 163 | 164 | - Update the plugin code to remove 'async' and 'await' from the super-strict keyword tests introduced in acorn v2.6.x that generate parse errors before the plugin gets a chance to manage them. 165 | -------------------------------------------------------------------------------- /acorn-es7-plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = function(acorn) { 2 | switch (parseInt(acorn.version)) { 3 | case 2: 4 | case 3: 5 | acorn.plugins.asyncawait = require('./acorn-v3') ; 6 | break ; 7 | case 4: 8 | acorn.plugins.asyncawait = require('./acorn-v4') ; 9 | break ; 10 | case 5: 11 | acorn.plugins.asyncawait = require('./acorn-v4') ; 12 | break ; 13 | default: 14 | throw new Error("acorn-es7-plugin requires Acorn v2, 3, 4 or 5") ; 15 | } 16 | return acorn 17 | } 18 | -------------------------------------------------------------------------------- /acorn-v3.js: -------------------------------------------------------------------------------- 1 | var NotAsync = {} ; 2 | var asyncExit = /^async[\t ]+(return|throw)/ ; 3 | var asyncFunction = /^async[\t ]+function/ ; 4 | var atomOrPropertyOrLabel = /^\s*[():;]/ ; 5 | var removeComments = /([^\n])\/\*(\*(?!\/)|[^\n*])*\*\/([^\n])/g ; 6 | var matchAsyncGet = /\s*(get|set)\s*\(/ ; 7 | 8 | function hasLineTerminatorBeforeNext(st, since) { 9 | return st.lineStart >= since; 10 | } 11 | 12 | function test(regex,st,noComment) { 13 | var src = st.input.slice(st.start) ; 14 | if (noComment) { 15 | src = src.replace(removeComments,"$1 $3") ; 16 | } 17 | return regex.test(src); 18 | } 19 | 20 | /* Create a new parser derived from the specified parser, so that in the 21 | * event of an error we can back out and try again */ 22 | function subParse(parser, pos, extensions,parens) { 23 | var p = new parser.constructor(parser.options, parser.input, pos); 24 | if (extensions) 25 | for (var k in extensions) 26 | p[k] = extensions[k] ; 27 | 28 | var src = parser ; 29 | var dest = p ; 30 | ['inFunction','inAsyncFunction','inAsync','inGenerator','inModule'].forEach(function(k){ 31 | if (k in src) 32 | dest[k] = src[k] ; 33 | }) ; 34 | if (parens) 35 | p.options.preserveParens = true ; 36 | p.nextToken(); 37 | return p; 38 | } 39 | 40 | //TODO: Implement option noAsyncGetters 41 | 42 | function asyncAwaitPlugin (parser,options){ 43 | var es7check = function(){} ; 44 | 45 | parser.extend("initialContext",function(base){ 46 | return function(){ 47 | if (this.options.ecmaVersion < 7) { 48 | es7check = function(node) { 49 | parser.raise(node.start,"async/await keywords only available when ecmaVersion>=7") ; 50 | } ; 51 | } 52 | this.reservedWords = new RegExp(this.reservedWords.toString().replace(/await|async/g,"").replace("|/","/").replace("/|","/").replace("||","|")) ; 53 | this.reservedWordsStrict = new RegExp(this.reservedWordsStrict.toString().replace(/await|async/g,"").replace("|/","/").replace("/|","/").replace("||","|")) ; 54 | this.reservedWordsStrictBind = new RegExp(this.reservedWordsStrictBind.toString().replace(/await|async/g,"").replace("|/","/").replace("/|","/").replace("||","|")) ; 55 | this.inAsyncFunction = options.inAsyncFunction ; 56 | if (options.awaitAnywhere && options.inAsyncFunction) 57 | parser.raise(node.start,"The options awaitAnywhere and inAsyncFunction are mutually exclusive") ; 58 | 59 | return base.apply(this,arguments); 60 | } 61 | }) ; 62 | 63 | parser.extend("shouldParseExportStatement",function(base){ 64 | return function(){ 65 | if (this.type.label==='name' && this.value==='async' && test(asyncFunction,this)) { 66 | return true ; 67 | } 68 | return base.apply(this,arguments) ; 69 | } 70 | }) ; 71 | 72 | parser.extend("parseStatement",function(base){ 73 | return function (declaration, topLevel) { 74 | var start = this.start; 75 | var startLoc = this.startLoc; 76 | if (this.type.label==='name') { 77 | if (test(asyncFunction,this,true)) { 78 | var wasAsync = this.inAsyncFunction ; 79 | try { 80 | this.inAsyncFunction = true ; 81 | this.next() ; 82 | var r = this.parseStatement(declaration, topLevel) ; 83 | r.async = true ; 84 | r.start = start; 85 | r.loc && (r.loc.start = startLoc); 86 | r.range && (r.range[0] = start); 87 | return r ; 88 | } finally { 89 | this.inAsyncFunction = wasAsync ; 90 | } 91 | } else if ((typeof options==="object" && options.asyncExits) && test(asyncExit,this)) { 92 | // NON-STANDARD EXTENSION iff. options.asyncExits is set, the 93 | // extensions 'async return ?' and 'async throw ?' 94 | // are enabled. In each case they are the standard ESTree nodes 95 | // with the flag 'async:true' 96 | this.next() ; 97 | var r = this.parseStatement(declaration, topLevel) ; 98 | r.async = true ; 99 | r.start = start; 100 | r.loc && (r.loc.start = startLoc); 101 | r.range && (r.range[0] = start); 102 | return r ; 103 | } 104 | } 105 | return base.apply(this,arguments); 106 | } 107 | }) ; 108 | 109 | parser.extend("parseIdent",function(base){ 110 | return function(liberal){ 111 | var id = base.apply(this,arguments); 112 | if (this.inAsyncFunction && id.name==='await') { 113 | if (arguments.length===0) { 114 | this.raise(id.start,"'await' is reserved within async functions") ; 115 | } 116 | } 117 | return id ; 118 | } 119 | }) ; 120 | 121 | parser.extend("parseExprAtom",function(base){ 122 | return function(refShorthandDefaultPos){ 123 | var start = this.start ; 124 | var startLoc = this.startLoc; 125 | var rhs,r = base.apply(this,arguments); 126 | if (r.type==='Identifier') { 127 | if (r.name==='async' && !hasLineTerminatorBeforeNext(this, r.end)) { 128 | // Is this really an async function? 129 | var isAsync = this.inAsyncFunction ; 130 | try { 131 | this.inAsyncFunction = true ; 132 | var pp = this ; 133 | var inBody = false ; 134 | 135 | var parseHooks = { 136 | parseFunctionBody:function(node,isArrowFunction){ 137 | try { 138 | var wasInBody = inBody ; 139 | inBody = true ; 140 | return pp.parseFunctionBody.apply(this,arguments) ; 141 | } finally { 142 | inBody = wasInBody ; 143 | } 144 | }, 145 | raise:function(){ 146 | try { 147 | return pp.raise.apply(this,arguments) ; 148 | } catch(ex) { 149 | throw inBody?ex:NotAsync ; 150 | } 151 | } 152 | } ; 153 | 154 | rhs = subParse(this,this.start,parseHooks,true).parseExpression() ; 155 | if (rhs.type==='SequenceExpression') 156 | rhs = rhs.expressions[0] ; 157 | if (rhs.type === 'CallExpression') 158 | rhs = rhs.callee ; 159 | if (rhs.type==='FunctionExpression' || rhs.type==='FunctionDeclaration' || rhs.type==='ArrowFunctionExpression') { 160 | // Because we don't know if the top level parser supprts preserveParens, we have to re-parse 161 | // without it set 162 | rhs = subParse(this,this.start,parseHooks).parseExpression() ; 163 | if (rhs.type==='SequenceExpression') 164 | rhs = rhs.expressions[0] ; 165 | if (rhs.type === 'CallExpression') 166 | rhs = rhs.callee ; 167 | 168 | rhs.async = true ; 169 | rhs.start = start; 170 | rhs.loc && (rhs.loc.start = startLoc); 171 | rhs.range && (rhs.range[0] = start); 172 | this.pos = rhs.end; 173 | this.end = rhs.end ; 174 | this.endLoc = rhs.endLoc ; 175 | this.next(); 176 | es7check(rhs) ; 177 | return rhs ; 178 | } 179 | } catch (ex) { 180 | if (ex!==NotAsync) 181 | throw ex ; 182 | } 183 | finally { 184 | this.inAsyncFunction = isAsync ; 185 | } 186 | } 187 | else if (r.name==='await') { 188 | var n = this.startNodeAt(r.start, r.loc && r.loc.start); 189 | if (this.inAsyncFunction) { 190 | rhs = this.parseExprSubscripts() ; 191 | n.operator = 'await' ; 192 | n.argument = rhs ; 193 | n = this.finishNodeAt(n,'AwaitExpression', rhs.end, rhs.loc && rhs.loc.end) ; 194 | es7check(n) ; 195 | return n ; 196 | } 197 | // NON-STANDARD EXTENSION iff. options.awaitAnywhere is true, 198 | // an 'AwaitExpression' is allowed anywhere the token 'await' 199 | // could not be an identifier with the name 'await'. 200 | 201 | // Look-ahead to see if this is really a property or label called async or await 202 | if (this.input.slice(r.end).match(atomOrPropertyOrLabel)) { 203 | if (!options.awaitAnywhere && this.options.sourceType === 'module') 204 | return this.raise(r.start,"'await' is reserved within modules") ; 205 | return r ; // This is a valid property name or label 206 | } 207 | 208 | if (typeof options==="object" && options.awaitAnywhere) { 209 | start = this.start ; 210 | rhs = subParse(this,start-4).parseExprSubscripts() ; 211 | if (rhs.end<=start) { 212 | rhs = subParse(this,start).parseExprSubscripts() ; 213 | n.operator = 'await' ; 214 | n.argument = rhs ; 215 | n = this.finishNodeAt(n,'AwaitExpression', rhs.end, rhs.loc && rhs.loc.end) ; 216 | this.pos = rhs.end; 217 | this.end = rhs.end ; 218 | this.endLoc = rhs.endLoc ; 219 | this.next(); 220 | es7check(n) ; 221 | return n ; 222 | } 223 | } 224 | 225 | if (!options.awaitAnywhere && this.options.sourceType === 'module') 226 | return this.raise(r.start,"'await' is reserved within modules") ; 227 | } 228 | } 229 | return r ; 230 | } 231 | }) ; 232 | 233 | parser.extend('finishNodeAt',function(base){ 234 | return function(node,type,pos,loc) { 235 | if (node.__asyncValue) { 236 | delete node.__asyncValue ; 237 | node.value.async = true ; 238 | } 239 | return base.apply(this,arguments) ; 240 | } 241 | }) ; 242 | 243 | parser.extend('finishNode',function(base){ 244 | return function(node,type) { 245 | if (node.__asyncValue) { 246 | delete node.__asyncValue ; 247 | node.value.async = true ; 248 | } 249 | return base.apply(this,arguments) ; 250 | } 251 | }) ; 252 | 253 | var allowedPropSpecifiers = { 254 | get:true, 255 | set:true, 256 | async:true 257 | }; 258 | 259 | parser.extend("parsePropertyName",function(base){ 260 | return function (prop) { 261 | var prevName = prop.key && prop.key.name ; 262 | var key = base.apply(this,arguments) ; 263 | if (key.type === "Identifier" && (key.name === "async") && !hasLineTerminatorBeforeNext(this, key.end)) { 264 | // Look-ahead to see if this is really a property or label called async or await 265 | if (!this.input.slice(key.end).match(atomOrPropertyOrLabel)){ 266 | // Cheese - eliminate the cases 'async get(){}' and async set(){}' 267 | if (matchAsyncGet.test(this.input.slice(key.end))) { 268 | key = base.apply(this,arguments) ; 269 | prop.__asyncValue = true ; 270 | } else { 271 | es7check(prop) ; 272 | if (prop.kind === 'set') 273 | this.raise(key.start,"'set (value)' cannot be be async") ; 274 | 275 | key = base.apply(this,arguments) ; 276 | if (key.type==='Identifier') { 277 | if (key.name==='set') 278 | this.raise(key.start,"'set (value)' cannot be be async") ; 279 | } 280 | prop.__asyncValue = true ; 281 | } 282 | } 283 | } 284 | return key; 285 | }; 286 | }) ; 287 | 288 | parser.extend("parseClassMethod",function(base){ 289 | return function (classBody, method, isGenerator) { 290 | var wasAsync ; 291 | if (method.__asyncValue) { 292 | if (method.kind==='constructor') 293 | this.raise(method.start,"class constructor() cannot be be async") ; 294 | wasAsync = this.inAsyncFunction ; 295 | this.inAsyncFunction = true ; 296 | } 297 | var r = base.apply(this,arguments) ; 298 | this.inAsyncFunction = wasAsync ; 299 | return r ; 300 | } 301 | }) ; 302 | 303 | parser.extend("parseMethod",function(base){ 304 | return function (isGenerator) { 305 | var wasAsync ; 306 | if (this.__currentProperty && this.__currentProperty.__asyncValue) { 307 | wasAsync = this.inAsyncFunction ; 308 | this.inAsyncFunction = true ; 309 | } 310 | var r = base.apply(this,arguments) ; 311 | this.inAsyncFunction = wasAsync ; 312 | return r ; 313 | } 314 | }) ; 315 | 316 | parser.extend("parsePropertyValue",function(base){ 317 | return function (prop, isPattern, isGenerator, startPos, startLoc, refDestructuringErrors) { 318 | var prevProp = this.__currentProperty ; 319 | this.__currentProperty = prop ; 320 | var wasAsync ; 321 | if (prop.__asyncValue) { 322 | wasAsync = this.inAsyncFunction ; 323 | this.inAsyncFunction = true ; 324 | } 325 | var r = base.apply(this,arguments) ; 326 | this.inAsyncFunction = wasAsync ; 327 | this.__currentProperty = prevProp ; 328 | return r ; 329 | } 330 | }) ; 331 | } 332 | 333 | module.exports = asyncAwaitPlugin ; 334 | -------------------------------------------------------------------------------- /acorn-v4.js: -------------------------------------------------------------------------------- 1 | var asyncExit = /^async[\t ]+(return|throw)/ ; 2 | var atomOrPropertyOrLabel = /^\s*[):;]/ ; 3 | var removeComments = /([^\n])\/\*(\*(?!\/)|[^\n*])*\*\/([^\n])/g ; 4 | 5 | function hasLineTerminatorBeforeNext(st, since) { 6 | return st.lineStart >= since; 7 | } 8 | 9 | function test(regex,st,noComment) { 10 | var src = st.input.slice(st.start) ; 11 | if (noComment) { 12 | src = src.replace(removeComments,"$1 $3") ; 13 | } 14 | return regex.test(src); 15 | } 16 | 17 | /* Create a new parser derived from the specified parser, so that in the 18 | * event of an error we can back out and try again */ 19 | function subParse(parser, pos, extensions) { 20 | var p = new parser.constructor(parser.options, parser.input, pos); 21 | if (extensions) 22 | for (var k in extensions) 23 | p[k] = extensions[k] ; 24 | 25 | var src = parser ; 26 | var dest = p ; 27 | ['inFunction','inAsync','inGenerator','inModule'].forEach(function(k){ 28 | if (k in src) 29 | dest[k] = src[k] ; 30 | }) ; 31 | p.nextToken(); 32 | return p; 33 | } 34 | 35 | function asyncAwaitPlugin (parser,options){ 36 | if (!options || typeof options !== "object") 37 | options = {} ; 38 | 39 | parser.extend("parse",function(base){ 40 | return function(){ 41 | this.inAsync = options.inAsyncFunction ; 42 | if (options.awaitAnywhere && options.inAsyncFunction) 43 | parser.raise(node.start,"The options awaitAnywhere and inAsyncFunction are mutually exclusive") ; 44 | 45 | return base.apply(this,arguments); 46 | } 47 | }) ; 48 | 49 | parser.extend("parseStatement",function(base){ 50 | return function (declaration, topLevel) { 51 | var start = this.start; 52 | var startLoc = this.startLoc; 53 | if (this.type.label==='name') { 54 | if ((options.asyncExits) && test(asyncExit,this)) { 55 | // TODO: Ensure this function is itself nested in an async function or Method 56 | this.next() ; 57 | 58 | var r = this.parseStatement(declaration, topLevel) ; 59 | r.async = true ; 60 | r.start = start; 61 | r.loc && (r.loc.start = startLoc); 62 | r.range && (r.range[0] = start); 63 | return r ; 64 | } 65 | } 66 | return base.apply(this,arguments); 67 | } 68 | }) ; 69 | 70 | parser.extend("parseIdent",function(base){ 71 | return function(liberal) { 72 | if (this.options.sourceType==='module' && this.options.ecmaVersion >= 8 && options.awaitAnywhere) 73 | return base.call(this,true) ; // Force liberal mode if awaitAnywhere is set 74 | return base.apply(this,arguments) ; 75 | } 76 | }) ; 77 | 78 | parser.extend("parseExprAtom",function(base){ 79 | var NotAsync = {}; 80 | return function(refShorthandDefaultPos){ 81 | var start = this.start ; 82 | var startLoc = this.startLoc; 83 | 84 | var rhs,r = base.apply(this,arguments); 85 | 86 | if (r.type==='Identifier') { 87 | if (r.name==='await' && !this.inAsync) { 88 | if (options.awaitAnywhere) { 89 | var n = this.startNodeAt(r.start, r.loc && r.loc.start); 90 | 91 | start = this.start ; 92 | 93 | var parseHooks = { 94 | raise:function(){ 95 | try { 96 | return pp.raise.apply(this,arguments) ; 97 | } catch(ex) { 98 | throw /*inBody?ex:*/NotAsync ; 99 | } 100 | } 101 | } ; 102 | 103 | try { 104 | rhs = subParse(this,start-4,parseHooks).parseExprSubscripts() ; 105 | if (rhs.end<=start) { 106 | rhs = subParse(this,start,parseHooks).parseExprSubscripts() ; 107 | n.argument = rhs ; 108 | n = this.finishNodeAt(n,'AwaitExpression', rhs.end, rhs.loc && rhs.loc.end) ; 109 | this.pos = rhs.end; 110 | this.end = rhs.end ; 111 | this.endLoc = rhs.endLoc ; 112 | this.next(); 113 | return n ; 114 | } 115 | } catch (ex) { 116 | if (ex===NotAsync) 117 | return r ; 118 | throw ex ; 119 | } 120 | } 121 | } 122 | } 123 | return r ; 124 | } 125 | }) ; 126 | 127 | var allowedPropValues = { 128 | undefined:true, 129 | get:true, 130 | set:true, 131 | static:true, 132 | async:true, 133 | constructor:true 134 | }; 135 | parser.extend("parsePropertyName",function(base){ 136 | return function (prop) { 137 | var prevName = prop.key && prop.key.name ; 138 | var key = base.apply(this,arguments) ; 139 | if (this.value==='get') { 140 | prop.__maybeStaticAsyncGetter = true ; 141 | } 142 | var next ; 143 | if (allowedPropValues[this.value]) 144 | return key ; 145 | 146 | if (key.type === "Identifier" && (key.name === "async" || prevName === "async") && !hasLineTerminatorBeforeNext(this, key.end) 147 | // Look-ahead to see if this is really a property or label called async or await 148 | && !this.input.slice(key.end).match(atomOrPropertyOrLabel)) { 149 | if (prop.kind === 'set' || key.name === 'set') 150 | this.raise(key.start,"'set (value)' cannot be be async") ; 151 | else { 152 | this.__isAsyncProp = true ; 153 | key = base.apply(this,arguments) ; 154 | if (key.type==='Identifier') { 155 | if (key.name==='set') 156 | this.raise(key.start,"'set (value)' cannot be be async") ; 157 | } 158 | } 159 | } else { 160 | delete prop.__maybeStaticAsyncGetter ; 161 | } 162 | return key; 163 | }; 164 | }) ; 165 | 166 | parser.extend("parseClassMethod",function(base){ 167 | return function (classBody, method, isGenerator) { 168 | var r = base.apply(this,arguments) ; 169 | if (method.__maybeStaticAsyncGetter) { 170 | delete method.__maybeStaticAsyncGetter ; 171 | if (method.key.name!=='get') 172 | method.kind = "get" ; 173 | } 174 | return r ; 175 | } 176 | }) ; 177 | 178 | 179 | parser.extend("parseFunctionBody",function(base){ 180 | return function (node, isArrowFunction) { 181 | var wasAsync = this.inAsync ; 182 | if (this.__isAsyncProp) { 183 | node.async = true ; 184 | this.inAsync = true ; 185 | delete this.__isAsyncProp ; 186 | } 187 | var r = base.apply(this,arguments) ; 188 | this.inAsync = wasAsync ; 189 | return r ; 190 | } 191 | }) ; 192 | } 193 | 194 | module.exports = asyncAwaitPlugin ; 195 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "matatbread" 4 | }, 5 | "bugs": { 6 | "url": "https://github.com/MatAtBread/acorn-es7-plugin/issues" 7 | }, 8 | "description": "A plugin for the Acorn parser that understands the ES7 keywords async and await", 9 | "homepage": "https://github.com/MatAtBread/acorn-es7-plugin#readme", 10 | "keywords": [ 11 | "acorn", 12 | "parser", 13 | "es7", 14 | "async", 15 | "await" 16 | ], 17 | "license": "MIT", 18 | "main": "acorn-es7-plugin.js", 19 | "name": "acorn-es7-plugin", 20 | "readmeFilename": "README.md", 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/MatAtBread/acorn-es7-plugin.git" 24 | }, 25 | "scripts": { 26 | "test": "cd test ; npm i ; npm test" 27 | }, 28 | "version": "1.1.7" 29 | } 30 | -------------------------------------------------------------------------------- /test/babel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | try { 4 | // If this doesn't throw, then arrow functions are supported natively. 5 | // Do not require babel (for speed). 6 | eval('var x = () => {};'); 7 | } catch (e) { 8 | require('babel-core/register')({ 9 | only: /test.js$/, 10 | presets: ['es2015'] 11 | }); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:babel.js -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acorn-es7-plugin-test", 3 | "version": "0.0.5", 4 | "description": "Tests for acorn-es7-plugin", 5 | "main": "nothing-here", 6 | "scripts": { 7 | "test": "npm i acorn@3 ; mocha --opts ./mocha.opts ; node test-es5.js ; npm i acorn@4 ; mocha --opts ./mocha.opts ; node test-es5.js ; npm i acorn@5 ; mocha --opts ./mocha.opts ; node test-es5.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/MatAtBread/acorn-es7-plugin.git" 12 | }, 13 | "keywords": [ 14 | "acorn", 15 | "parser", 16 | "es7", 17 | "async", 18 | "await" 19 | ], 20 | "author": "matatbread", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/MatAtBread/acorn-es7-plugin/issues" 24 | }, 25 | "homepage": "https://github.com/MatAtBread/acorn-es7-plugin#readme", 26 | "devDependencies": { 27 | "babel-core": "^6.0.20", 28 | "babel-preset-es2015": "^6.0.15", 29 | "estraverse": "^4.1.1", 30 | "mocha": "^2.3.3", 31 | "colors": "^1.1.2", 32 | "xtend": "^4.0.1" 33 | }, 34 | "dependencies": { 35 | "babel-core": "^6.14.0", 36 | "babel-preset-es2015": "^6.14.0", 37 | "colors": "^1.1.2", 38 | "estraverse": "^4.2.0", 39 | "mocha": "^2.5.3", 40 | "xtend": "^4.0.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/test-es5.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* Simple test script that doesn't need mocha or similar - it just parses stuff and checks the returned AST */ 3 | var acorn = require('acorn'); 4 | var colors = require('colors'); 5 | require('../')(acorn); 6 | function parse(code, pluginOptions, scriptType) { 7 | if (Array.isArray(code)) { 8 | code = code.join('\n'); 9 | } 10 | return acorn.parse(code, { 11 | sourceType: scriptType, 12 | ecmaVersion: 8, 13 | locations: true, 14 | ranges: true, 15 | plugins: { 16 | asyncawait: pluginOptions || {} 17 | } 18 | }); 19 | } 20 | 21 | function isIdentThenFnDecl(ast) { 22 | return ast.body[0].type === 'ExpressionStatement' && ast.body[0].expression.type === 'Identifier' && ast.body[0].expression.name === 'async' && !ast.body[1].async === true && ast.body[1].type == "FunctionDeclaration"; 23 | } 24 | 25 | function isAsyncFnDecl(ast) { 26 | return ast.body[0].async === true && ast.body[0].type === "FunctionDeclaration"; 27 | } 28 | 29 | function isAsyncFnExpr(ast) { 30 | return ast.body[0].expression.async === true && ast.body[0].expression.type === "ArrowFunctionExpression"; 31 | } 32 | 33 | function isExprType(type) { 34 | return function (ast, sourceType) { 35 | return ast.body[0].type === 'ExpressionStatement' && ast.body[0].expression.type === type; 36 | }; 37 | } 38 | 39 | var tests = [ 40 | /* Standard behaviours */ 41 | { 42 | desc: "Simple async function", 43 | code: "async function x() { return undefined; }", 44 | pass: function (ast) { 45 | return ast.body[0].async === true; 46 | } 47 | },{ 48 | desc: "Simple async function expression", 49 | code: "(async function (){ })", 50 | pass: function (ast) { 51 | return ast.body[0].expression.async === true; 52 | } 53 | },{ 54 | desc: "Async function expression call (1)", 55 | code: "(async function (){ }())", 56 | pass: function (ast) { 57 | return ast.body[0].expression.callee.async === true; 58 | } 59 | },{ 60 | desc: "Async function expression call (2)", 61 | code: "(async function (){ })()", 62 | pass: function (ast) { 63 | return ast.body[0].expression.callee.async === true; 64 | } 65 | },{ 66 | desc: "Await in async is AwaitExpression", 67 | code: "async function x() { await(undefined); await undefined ; }", 68 | pass: function (ast) { 69 | return ast.body[0].body.body[0].expression.type === 'AwaitExpression' && ast.body[0].body.body[1].expression.type === 'AwaitExpression'; 70 | } 71 | },{ 72 | desc: "Await in function is identifier in 'script', illegal in 'module'", 73 | code: "function x() { await(undefined); }", 74 | pass: function (ast,scriptType) { 75 | return scriptType === 'script'?ast.body[0].body.body[0].expression.callee.name === 'await':ast.indexOf("(1:15)")>=0; 76 | } 77 | },{ 78 | desc: "Async method {code}", 79 | code: "var a = {async x(){}}", 80 | pass: function (ast) { 81 | return ast.body[0].declarations[0].init.properties[0].value.async; 82 | } 83 | },{ 84 | desc: "Async arrow", 85 | code: "var a = async()=>0", 86 | pass: function (ast) { 87 | return ast.body[0].declarations[0].init.async; 88 | } 89 | },{ 90 | desc: "Abbreviated async arrow", 91 | code: "var a = async b=>-b", 92 | pass: function (ast) { 93 | return ast.body[0].declarations[0].init.async; 94 | } 95 | },{ 96 | desc: "Parenthesized async arrow is a call", 97 | code: "var a = async(b=>0)", 98 | pass: function (ast) { 99 | return ast.body[0].declarations[0].init.type==='CallExpression'; 100 | } 101 | },{ 102 | desc: "Await declaration fails in async function", 103 | code: "async function x() { var await; }", 104 | pass: function (ex, scriptType) { 105 | return ex.indexOf("(1:25)")>=0 106 | } 107 | },{ 108 | desc: "Await function declaration fails in async function", 109 | code: "async function x() { function await() {} }", 110 | pass: function (ex, scriptType) { 111 | return ex.indexOf("(1:30)")>=0 112 | } 113 | },{ 114 | desc: "Await reference fails in async function", 115 | code: "async function x() { return 1+await; }", 116 | pass: function (ex) { 117 | return !!ex.match(/\(1:3[05]\)/); 118 | } 119 | },{ 120 | desc: "{code} is an async FunctionExpression", 121 | code: "async ()=>0", 122 | pass: isAsyncFnExpr 123 | },{ 124 | desc: "{code} is a CallExpression", 125 | code: "async(()=>0)", 126 | pass: isExprType('CallExpression') 127 | },{ 128 | desc: "{code} is an async FunctionDeclaration", 129 | code: "async /* a */ function x(){}", 130 | pass: isAsyncFnDecl 131 | },{ 132 | desc: "{code} is a reference to 'async' and a sync FunctionDeclaration", 133 | code: "async /*\n*/function x(){}", 134 | pass: isIdentThenFnDecl 135 | },{ 136 | desc: "{code} is a reference to 'async' and a sync FunctionDeclaration", 137 | code: "async /* a */\nfunction x(){}", 138 | pass: isIdentThenFnDecl 139 | },{ 140 | desc: "{code} is a reference to 'async' and a sync FunctionDeclaration", 141 | code: "async\nfunction x(){}", 142 | pass: isIdentThenFnDecl 143 | },{ 144 | desc: "{code} is a reference to 'async' and a sync FunctionDeclaration", 145 | code: "async //\nfunction x(){}", 146 | pass: isIdentThenFnDecl 147 | },{ 148 | desc: "{code} is a reference to 'async' and a sync FunctionDeclaration", 149 | code: "async /*\n*/\nfunction x(){}", 150 | pass: isIdentThenFnDecl 151 | },{ 152 | desc: "{code} is a SyntaxError (when inAsyncFunction and awaitAnywhere option are defaults)", 153 | code: "await x", 154 | pass: function (ex, sourceType) { 155 | return sourceType==='module' ? !!ex.match(/\(1:0\)/) : ex === "Unexpected token (1:6)"; 156 | } 157 | },{ 158 | desc: "{code} is a CallExpression in scripts, and a SyntaxError in modules", 159 | code: "await(x)", 160 | pass: function(ast,sourceType) { 161 | return sourceType==='module'?!!ast.match(/\(1:0\)/) :isExprType('CallExpression')(ast) 162 | } 163 | },{ 164 | desc: "Async method 'constructor' is valid", 165 | code: "var a = {async constructor(){}}", 166 | pass: function (ast) { 167 | var props = ast.body[0].declarations[0].init.properties ; 168 | return (props[0].kind === 'init' && props[0].key.name==='constructor' && props[0].value.async) 169 | } 170 | },{ 171 | desc: "Async class constructor fails", 172 | code: "class a {async constructor(){}}", 173 | pass: function (ex) { 174 | return !!ex.match(/class constructor\(\) cannot be be async \(1:(15|9)\)/) || ex === "Constructor can't be an async method (1:15)"; 175 | } 176 | },{ 177 | desc: "Async setter fails", 178 | code: "var a = {async set x(y){}}", 179 | pass: function (ex) { 180 | return ex === "'set (value)' cannot be be async (1:15)" || ex === "Unexpected token (1:19)"; 181 | } 182 | },{ 183 | desc: "Deprecated async setter fails (use 'async set x')", 184 | code: "var a = {set async x(y){}}", 185 | pass: function (ex) { 186 | return ex === "'set (value)' cannot be be async (1:13)" || ex === "Unexpected token (1:19)"; 187 | } 188 | },{ 189 | desc: "{code} getters/setters are not async", 190 | code: "var a = {get x(){},set y(z){}}", 191 | pass: function (ast) { 192 | var props = ast.body[0].declarations[0].init.properties ; 193 | return (props[0].kind === 'get' && props[0].key.name==='x' && !props[0].value.async) 194 | && (props[1].kind === 'set' && props[1].key.name==='y' && !props[1].value.async); 195 | } 196 | },{ 197 | desc: "{code} are methods, not getters/setters", 198 | code: "var a = {async get(){},async set(){}}", 199 | pass: function (ast) { 200 | var props = ast.body[0].declarations[0].init.properties ; 201 | return (props[0].kind === 'init' && props[0].key.name==='get' && props[0].value.async) 202 | && (props[1].kind === 'init' && props[1].key.name==='set' && props[1].value.async); 203 | } 204 | },{ 205 | desc: "In {code}, x is an sync getter", 206 | code: "class a {get x(){}}", 207 | pass: function (ast) { 208 | return ast.body[0].body.body[0].kind==="get" && !ast.body[0].body.body[0].value.async && !ast.body[0].body.body[0].static ; 209 | } 210 | },{ 211 | desc: "In {code}, x is an static sync getter", 212 | code: "class a {static get x(){}}", 213 | pass: function (ast) { 214 | return ast.body[0].body.body[0].kind==="get" && !ast.body[0].body.body[0].value.async && ast.body[0].body.body[0].static ; 215 | } 216 | },{ 217 | desc: "In {code}, x is an static sync method", 218 | code: "class a {static async x(){}}", 219 | pass: function (ast) { 220 | return ast.body[0].body.body[0].kind==="method" && ast.body[0].body.body[0].value.async && ast.body[0].body.body[0].static ; 221 | } 222 | },{ 223 | desc: "{code} are a getters/setters, not methods", 224 | code: "var a = {get async(){},set async(x){}}", 225 | pass: function (ast) { 226 | var props = ast.body[0].declarations[0].init.properties ; 227 | return (props[0].kind === 'get' && props[0].key.name==='async' && !props[0].value.async) 228 | && (props[1].kind === 'set' && props[1].key.name==='async' && !props[1].value.async); 229 | } 230 | }, 231 | /* Extended syntax behaviour for Nodent */ 232 | { 233 | desc: "Nodent:".grey+" In {code}, get is a static method", 234 | code: "class Foo { static get(v) {} }", 235 | pass: function (ast) { 236 | return ast.body[0].body.body[0].type==='MethodDefinition' 237 | && ast.body[0].body.body[0].key.name === 'get' 238 | && ast.body[0].body.body[0].kind === "method" 239 | && ast.body[0].body.body[0].static; 240 | } 241 | },{ 242 | desc: "Nodent:".grey+" In {code}, get is a non-static method", 243 | code: "class Foo { get(v) {} }", 244 | pass: function (ast) { 245 | return ast.body[0].body.body[0].type==='MethodDefinition' 246 | && ast.body[0].body.body[0].key.name === 'get' 247 | && ast.body[0].body.body[0].kind === "method" 248 | && !ast.body[0].body.body[0].static; 249 | } 250 | },{ 251 | desc: "Nodent:".grey+" In {code}, get is a non-static getter", 252 | code: "class Foo { get get() {} }", 253 | pass: function (ast) { 254 | return ast.body[0].body.body[0].type==='MethodDefinition' 255 | && ast.body[0].body.body[0].key.name === 'get' 256 | && ast.body[0].body.body[0].kind === "get" 257 | && !ast.body[0].body.body[0].static; 258 | } 259 | },{ 260 | desc: "Nodent:".grey+" In {code}, x is an async getter", 261 | code: "var a = {async get x(){ await(0) }}", 262 | pass: function (ast) { 263 | return ast.body[0].declarations[0].init.properties[0].value.async 264 | && ast.body[0].declarations[0].init.properties[0].value.body.body[0].expression.type==='AwaitExpression'; 265 | } 266 | },{ 267 | desc: "Nodent:".grey+" In {code} (deprecated), x is an async getter", 268 | code: "var a = {get async x(){ await 0 }}", 269 | pass: function (ast) { 270 | return ast.body[0].declarations[0].init.properties[0].value.async 271 | && ast.body[0].declarations[0].init.properties[0].value.body.body[0].expression.type==='AwaitExpression'; 272 | } 273 | },{ 274 | desc: "Nodent:".grey+" In {code} (deprecated), x is an async getter", 275 | code: "var a = {get async x(){ await(0) }}", 276 | pass: function (ast) { 277 | return ast.body[0].declarations[0].init.properties[0].value.async 278 | && ast.body[0].declarations[0].init.properties[0].value.body.body[0].expression.type==='AwaitExpression'; 279 | } 280 | },{ 281 | desc: "Nodent:".grey+" In {code}, x is an async getter", 282 | code: "class a {async get x(){ await 0 }}", 283 | pass: function (ast) { 284 | return ast.body[0].body.body[0].value.async 285 | && ast.body[0].body.body[0].value.body.body[0].expression.type==='AwaitExpression'; 286 | } 287 | },{ 288 | desc: "Nodent:".grey+" In {code}, x is an async getter", 289 | code: "class a {async get x(){ await(0) }}", 290 | pass: function (ast) { 291 | return ast.body[0].body.body[0].value.async 292 | && ast.body[0].body.body[0].value.body.body[0].expression.type==='AwaitExpression'; 293 | } 294 | },{ 295 | desc: "Nodent:".grey+" In {code} (deprecated), x is an async getter", 296 | code: "class a {get async x(){ await 0 }}", 297 | pass: function (ast) { 298 | return ast.body[0].body.body[0].value.async 299 | && ast.body[0].body.body[0].value.body.body[0].expression.type==='AwaitExpression'; 300 | } 301 | },{ 302 | desc: "Nodent:".grey+" In {code} (deprecated), x is an async getter", 303 | code: "class a {get async x(){ await(0) }}", 304 | pass: function (ast) { 305 | return ast.body[0].body.body[0].value.async 306 | && ast.body[0].body.body[0].value.body.body[0].expression.type==='AwaitExpression'; 307 | } 308 | },{ 309 | desc: "Nodent:".grey+" In {code}, x is an static async getter", 310 | code: "class a {static async get x(){}}", 311 | pass: function (ast) { 312 | return ast.body[0].body.body[0].kind==="get" && ast.body[0].body.body[0].value.async && ast.body[0].body.body[0].static ; 313 | } 314 | },{ 315 | desc: "Nodent:".grey+" In {code} (deprecated), x is an static async getter", 316 | code: "class a {static get async x(){}}", 317 | pass: function (ast) { 318 | return ast.body[0].body.body[0].kind==="get" && ast.body[0].body.body[0].value.async && ast.body[0].body.body[0].static ; 319 | } 320 | },{ 321 | desc: "Nodent:".grey+" {code} is an AwaitExpression when inAsyncFunction option is true", 322 | code: "await(x)", 323 | options: { 324 | inAsyncFunction: true 325 | }, 326 | pass: isExprType('AwaitExpression') 327 | },{ 328 | desc: "Nodent:".grey+" {code} is an AwaitExpression when inAsyncFunction option is true", 329 | code: "await x", 330 | options: { 331 | inAsyncFunction: true 332 | }, 333 | pass: isExprType('AwaitExpression') 334 | },{ 335 | desc: "Nodent:".grey+" {code} is a CallExpression when awaitAnywhere option is true", 336 | code: "await(x)", 337 | options: { 338 | awaitAnywhere: true 339 | }, 340 | pass: isExprType('CallExpression') 341 | },{ 342 | desc: "Nodent:".grey+" {code} is an AwaitExpression when awaitAnywhere option is true", 343 | code: "await x", 344 | options: { 345 | awaitAnywhere: true 346 | }, 347 | pass: isExprType('AwaitExpression') 348 | }]; 349 | // TODO: Add tests for asyncExits, noAsyncGetters 350 | 351 | var out = { 352 | true: "pass".green, 353 | false: "fail".red 354 | }; 355 | var testNumber = +process.argv[2] || 0; 356 | if (testNumber) { 357 | tests = [tests[testNumber - 1]]; 358 | } else { 359 | testNumber += 1; 360 | } 361 | var results = { 362 | true: 0, 363 | false: 0 364 | }; 365 | 366 | tests.forEach(function (test, idx) { 367 | ['script','module'].forEach(function(scriptType){ 368 | var code = test.code.replace(/\n/g, ' '); 369 | var desc = test.desc.replace('{code}', code.yellow); 370 | var pass = function () { 371 | var p = test.pass.apply(this, arguments); 372 | results[p] += 1; 373 | return p; 374 | }; 375 | var prefix = idx + testNumber + " (" + scriptType + ", acorn v" + acorn.version+")\t" ; 376 | try { 377 | console.log(prefix, desc, out[pass(parse(test.code, test.options, scriptType),scriptType)]); 378 | } catch (ex) { 379 | try { 380 | console.log(prefix, desc, ex.message.cyan, out[pass(ex.message,scriptType)]); 381 | } catch (ex) { 382 | console.log(prefix, desc, ex.message.magenta, out[false]); 383 | results.false += 1; 384 | } 385 | } 386 | }); 387 | }) ; 388 | console.log(''); 389 | if (results.true) 390 | console.log((results.true + " of " + tests.length*2 + " tests passed").green); 391 | if (results.false) { 392 | console.log((results.false + " of " + tests.length*2 + " tests failed").red); 393 | var exit = new Error("Test failed") ; 394 | exit.stack = "" ; 395 | throw exit ; 396 | } 397 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var acorn = require('acorn'); 4 | require('../')(acorn); 5 | var estraverse = require('estraverse'); 6 | var xtend = require('xtend'); 7 | var assert = require('assert'); 8 | 9 | function find (type, ast, skip) { 10 | skip = skip || 0; 11 | var skipped = 0; 12 | 13 | var found; 14 | 15 | estraverse.traverse(ast, { 16 | enter: (node) => { 17 | if (found) { 18 | return estraverse.VisitorOption.Skip; 19 | } 20 | if (node.type == type) { 21 | if (skipped === skip) { 22 | found = node; 23 | return estraverse.VisitorOption.Skip; 24 | } 25 | skipped++; 26 | } 27 | } 28 | }); 29 | 30 | if (!found) { 31 | throw new Error('did not find AwaitExpression (skipped ' + skipped + '/' + skip + ')'); 32 | } 33 | 34 | return found; 35 | } 36 | 37 | function extendOptions(pluginOptions, acornOptions) { 38 | return xtend({ 39 | sourceType: 'module', 40 | ecmaVersion: 8, 41 | locations: true, 42 | ranges: true, 43 | plugins: {asyncawait: pluginOptions || pluginOptions !== false} 44 | }, acornOptions); 45 | } 46 | 47 | function parse(code, pluginOptions, acornOptions) { 48 | if (Array.isArray(code)) { 49 | code = code.join('\n'); 50 | } 51 | var options = extendOptions(pluginOptions, acornOptions); 52 | return acorn.parse(code, options); 53 | } 54 | 55 | describe('async', () => { 56 | describe ('function declaration', () => { 57 | var node; 58 | 59 | describe('-', () => { 60 | beforeEach(() => { 61 | node = find( 62 | 'FunctionDeclaration', 63 | parse([ 64 | 'async function foo() {', 65 | ' x = await bar()', 66 | '}' 67 | ]) 68 | ); 69 | }); 70 | 71 | it('marks the node as async', () => 72 | assert(node.async) 73 | ); 74 | 75 | it('finds correct start position', () => 76 | assert.strictEqual(node.start, 0) 77 | ); 78 | 79 | it('finds correct end position', () => 80 | assert.strictEqual(node.end, 42) 81 | ); 82 | 83 | it('finds correct start line/column', () => 84 | assert.deepEqual(node.loc.start, { 85 | line: 1, 86 | column: 0 87 | }) 88 | ); 89 | 90 | it('finds correct end line/column', () => 91 | assert.deepEqual(node.loc.end, { 92 | line: 3, 93 | column: 1 94 | }) 95 | ); 96 | }); 97 | 98 | var assertFindsIdentifierExpressionStatement = (ast) => { 99 | node = find('ExpressionStatement', ast); 100 | assert.strictEqual(node.expression.type, 'Identifier'); 101 | assert.strictEqual(node.expression.name, 'async'); 102 | assert.deepEqual(node.expression.loc, { 103 | start: { 104 | line: 1, 105 | column: 0 106 | }, 107 | end: { 108 | line: 1, 109 | column: 5 110 | } 111 | }); 112 | }; 113 | 114 | describe('linefeed after async (simple)', () => { 115 | var ast; 116 | beforeEach(() => { 117 | ast = parse([ 118 | 'async \t\t ', 119 | 'function foo() {', 120 | '}' 121 | ]); 122 | }); 123 | 124 | it('finds Identifier ExpressionStatement', () => { 125 | assertFindsIdentifierExpressionStatement(ast); 126 | }); 127 | 128 | it('does not mark FunctionDeclaration as async', () => { 129 | node = find('FunctionDeclaration', ast); 130 | assert(!node.async, 'Expected node.async to be false'); 131 | }); 132 | }); 133 | 134 | describe('linefeed after async (single line comment)', () => { 135 | var ast; 136 | beforeEach(() => { 137 | ast = parse([ 138 | 'async // flag enables async completion', 139 | 'function foo() {', 140 | '}' 141 | ]); 142 | }); 143 | 144 | it('finds Identifier ExpressionStatement', () => { 145 | assertFindsIdentifierExpressionStatement(ast); 146 | }); 147 | 148 | it('does not mark FunctionDeclaration as async', () => { 149 | node = find('FunctionDeclaration', ast); 150 | assert(!node.async, 'Expected node.async to be false'); 151 | }); 152 | }); 153 | 154 | describe('linefeed after async (multiline comment) function', () => { 155 | var ast; 156 | beforeEach(() => { 157 | ast = parse([ 158 | 'async /* flag enables async completion', 159 | ' of the callback */function foo() {', 160 | '}' 161 | ]); 162 | }); 163 | 164 | it('finds Identifier ExpressionStatement', () => { 165 | assertFindsIdentifierExpressionStatement(ast); 166 | }); 167 | 168 | it('does not mark FunctionDeclaration as async', () => { 169 | node = find('FunctionDeclaration', ast); 170 | assert(!node.async, 'Expected node.async to be false'); 171 | }); 172 | }); 173 | }); 174 | 175 | describe ('function expression', () => { 176 | var node, code; 177 | 178 | describe('-', () => { 179 | beforeEach(() => { 180 | code = [ 181 | 'foo = async function () {', 182 | ' x = await bar()', 183 | '}' 184 | ]; 185 | node = find( 186 | 'FunctionExpression', 187 | parse(code) 188 | ); 189 | }); 190 | 191 | it('marks the node as async', () => 192 | assert(node.async) 193 | ); 194 | 195 | it('finds correct start position', () => 196 | assert.strictEqual(node.start, 6) 197 | ); 198 | 199 | it('finds correct end position', () => 200 | assert.strictEqual(node.end, code.join('\n').length) 201 | ); 202 | 203 | it('finds correct start line/column', () => 204 | assert.deepEqual(node.loc.start, { 205 | line: 1, 206 | column: 6 207 | }) 208 | ); 209 | 210 | it('finds correct end line/column', () => 211 | assert.deepEqual(node.loc.end, { 212 | line: 3, 213 | column: 1 214 | }) 215 | ); 216 | }); 217 | 218 | var assertFindsIdentifierAssignmentExpressionRHS = (ast) => { 219 | node = find('AssignmentExpression', ast); 220 | assert.strictEqual(node.right.type, 'Identifier'); 221 | assert.strictEqual(node.right.name, 'async'); 222 | assert.deepEqual(node.right.loc, { 223 | start: { 224 | line: 1, 225 | column: 6 226 | }, 227 | end: { 228 | line: 1, 229 | column: 11 230 | } 231 | }); 232 | }; 233 | 234 | describe('linefeed after async (simple)', () => { 235 | var ast; 236 | beforeEach(() => { 237 | ast = parse([ 238 | 'foo = async \t\t ', 239 | ', function() {', 240 | '}' 241 | ]); 242 | }); 243 | 244 | it('finds Identifier ExpressionStatement', () => { 245 | assertFindsIdentifierAssignmentExpressionRHS(ast); 246 | }); 247 | 248 | it('does not mark FunctionExpression as async', () => { 249 | node = find('FunctionExpression', ast); 250 | assert(!node.async, 'Expected node.async to be false'); 251 | }); 252 | }); 253 | 254 | describe('linefeed after async (single line comment)', () => { 255 | var ast; 256 | beforeEach(() => { 257 | ast = parse([ 258 | 'foo = async // flag enables async completion', 259 | ', function() {', 260 | '}' 261 | ]); 262 | }); 263 | 264 | it('finds Identifier ExpressionStatement', () => { 265 | assertFindsIdentifierAssignmentExpressionRHS(ast); 266 | }); 267 | 268 | it('does not mark FunctionExpression as async', () => { 269 | node = find('FunctionExpression', ast); 270 | assert(!node.async, 'Expected node.async to be false'); 271 | }); 272 | }); 273 | 274 | describe('linefeed after async (multiline comment), function', () => { 275 | var ast; 276 | beforeEach(() => { 277 | ast = parse([ 278 | 'foo = async /* flag enables async completion', 279 | ' of the callback */, function() {', 280 | '}' 281 | ]); 282 | }); 283 | 284 | it('finds Identifier ExpressionStatement', () => { 285 | assertFindsIdentifierAssignmentExpressionRHS(ast); 286 | }); 287 | 288 | it('does not mark FunctionExpression as async', () => { 289 | node = find('FunctionExpression', ast); 290 | assert(!node.async, 'Expected node.async to be false'); 291 | }); 292 | }); 293 | }); 294 | 295 | describe ('enhanced object literal', () => { 296 | var node, code; 297 | 298 | describe('-', () => { 299 | beforeEach(() => { 300 | code = [ 301 | 'var x = {', 302 | ' async foo() {}', 303 | '};' 304 | ]; 305 | node = find( 306 | // TODO: Is it really supposed to mark the Property async? Why not the FunctionExpression? 307 | 'Property', 308 | parse(code) 309 | ); 310 | }); 311 | 312 | it('marks the node value as async', () => 313 | assert(node.value.async) 314 | ); 315 | 316 | it('does not mark the node as async', () => 317 | assert(!node.async) 318 | ); 319 | 320 | it('finds correct start position', () => 321 | assert.strictEqual(node.start, 12) 322 | ); 323 | 324 | it('finds correct end position', () => 325 | assert.strictEqual(node.end, code[0].length + code[1].length + 1) // + 1 is due to newline char 326 | ); 327 | 328 | it('finds correct start line/column', () => 329 | assert.deepEqual(node.loc.start, { 330 | line: 2, 331 | column: 2 332 | }) 333 | ); 334 | 335 | it('finds correct end line/column', () => 336 | assert.deepEqual(node.loc.end, { 337 | line: 2, 338 | column: 16 339 | }) 340 | ); 341 | }); 342 | 343 | describe('linefeed after async (simple)', () => { 344 | it('fails to parse', () => { 345 | assert.throws(() => parse([ 346 | 'var x = {', 347 | ' async \t\t ', 348 | ' foo() {}', 349 | '};' 350 | ])); 351 | }); 352 | }); 353 | 354 | describe('linefeed after async (single line comment)', () => { 355 | it('fails to parse', () => { 356 | assert.throws(() => parse([ 357 | 'var x = {', 358 | ' async // flag enables async completion', 359 | ' foo() {}', 360 | '};' 361 | ])); 362 | }); 363 | }); 364 | 365 | describe('linefeed after async (multiline comment) illegal decl', () => { 366 | it('finds Identifier ExpressionStatement', () => { 367 | assert.throws(() => parse([ 368 | 'var x = {', 369 | ' async /* flag enables async completion', 370 | ' of the callback */ foo() {}', 371 | '};' 372 | ])); 373 | }); 374 | }); 375 | }); 376 | 377 | describe ('ArrowFunctionExpression', () => { 378 | var node, code; 379 | 380 | describe('-', () => { 381 | beforeEach(() => { 382 | code = 'var x = async () => {}'; 383 | node = find( 384 | 'ArrowFunctionExpression', 385 | parse(code) 386 | ); 387 | }); 388 | 389 | it('marks the node as async', () => 390 | assert(node.async) 391 | ); 392 | 393 | it('finds correct start position', () => 394 | assert.strictEqual(node.start, 8) 395 | ); 396 | 397 | it('finds correct end position', () => 398 | assert.strictEqual(node.end, code.length) 399 | ); 400 | 401 | it('finds correct start line/column', () => 402 | assert.deepEqual(node.loc.start, { 403 | line: 1, 404 | column: 8 405 | }) 406 | ); 407 | 408 | it('finds correct end line/column', () => 409 | assert.deepEqual(node.loc.end, { 410 | line: 1, 411 | column: code.length 412 | }) 413 | ); 414 | }); 415 | 416 | describe('linefeed after async (simple)', () => { 417 | var ast; 418 | beforeEach(() => { 419 | ast = parse([ 420 | 'var x = async \t\t ', 421 | '()' 422 | ]); 423 | }); 424 | 425 | it('fails to parse if linefeed preceeds arrow arguments', () => { 426 | assert.throws(() => parse([ 427 | 'var x = async \t\t ', 428 | '() => {}' 429 | ])); 430 | }); 431 | 432 | it('finds CallExpression with "async" Identifier callee', () => { 433 | node = find('CallExpression', ast); 434 | assert.strictEqual(node.callee.type, 'Identifier'); 435 | assert.strictEqual(node.callee.name, 'async'); 436 | assert.deepEqual(node.callee.loc, { 437 | start: { 438 | line: 1, 439 | column: 8 440 | }, 441 | end: { 442 | line: 1, 443 | column: 13 444 | } 445 | }); 446 | }); 447 | }); 448 | 449 | describe('linefeed after async (single line comment)', () => { 450 | var ast; 451 | beforeEach(() => { 452 | ast = parse([ 453 | 'var x = async // flag enables async completion', 454 | '()' 455 | ]); 456 | }); 457 | 458 | it('fails to parse if linefeed preceeds arrow arguments', () => { 459 | assert.throws(() => parse([ 460 | 'var x = async \t\t ', 461 | '() => {}' 462 | ])); 463 | }); 464 | 465 | it('finds CallExpression with "async" Identifier callee', () => { 466 | node = find('CallExpression', ast); 467 | assert.strictEqual(node.callee.type, 'Identifier'); 468 | assert.strictEqual(node.callee.name, 'async'); 469 | assert.deepEqual(node.callee.loc, { 470 | start: { 471 | line: 1, 472 | column: 8 473 | }, 474 | end: { 475 | line: 1, 476 | column: 13 477 | } 478 | }); 479 | }); 480 | }); 481 | 482 | describe('linefeed after async (multiline comment) arrow decl', () => { 483 | var ast; 484 | beforeEach(() => { 485 | ast = parse([ 486 | 'var x = async /* flag enables async completion', 487 | ' of the callback */()' 488 | ]); 489 | }); 490 | 491 | it('fails to parse if linefeed preceeds arrow arguments', () => { 492 | assert.throws(() => parse([ 493 | 'var x = async /* flag enables async completion', 494 | ' of the callback */() => {}' 495 | ])); 496 | }); 497 | 498 | it('finds CallExpression with "async" Identifier callee', () => { 499 | node = find('CallExpression', ast); 500 | assert.strictEqual(node.callee.type, 'Identifier'); 501 | assert.strictEqual(node.callee.name, 'async'); 502 | assert.deepEqual(node.callee.loc, { 503 | start: { 504 | line: 1, 505 | column: 8 506 | }, 507 | end: { 508 | line: 1, 509 | column: 13 510 | } 511 | }); 512 | }); 513 | }); 514 | }); 515 | }); 516 | 517 | describe('await', () => { 518 | describe('-', () => { 519 | var node; 520 | 521 | beforeEach(() => { 522 | node = find( 523 | 'AwaitExpression', 524 | parse([ 525 | 'async function foo() {', 526 | ' x = await bar()', 527 | '}' 528 | ]) 529 | ); 530 | }); 531 | 532 | it('finds correct start position', () => 533 | assert.strictEqual(node.start, 29) 534 | ); 535 | 536 | it('finds correct end position', () => 537 | assert.strictEqual(node.end, 40) 538 | ); 539 | 540 | it('finds correct start line/column', () => 541 | assert.deepEqual(node.loc.start, { 542 | line: 2, 543 | column: 6 544 | }) 545 | ); 546 | 547 | it('finds correct end line/column', () => 548 | assert.deepEqual(node.loc.end, { 549 | line: 2, 550 | column: 17 551 | }) 552 | ); 553 | }); 554 | 555 | describe('outside a function (awaitAnywhere)', () => { 556 | var node; 557 | 558 | beforeEach(() => { 559 | node = find( 560 | 'AwaitExpression', 561 | parse( 562 | 'x = await bar()', 563 | {awaitAnywhere:true} 564 | ) 565 | ); 566 | }); 567 | 568 | it('finds correct start position', () => 569 | assert.strictEqual(node.start, 4) 570 | ); 571 | 572 | it('finds correct start line/column', () => 573 | assert.deepEqual(node.loc.start, { 574 | line: 1, 575 | column: 4 576 | }) 577 | ); 578 | 579 | it('finds correct end position', () => 580 | assert.strictEqual(node.end, 15) 581 | ); 582 | 583 | it('finds correct end line/column', () => 584 | assert.deepEqual(node.loc.end, { 585 | line: 1, 586 | column: 15 587 | }) 588 | ); 589 | }); 590 | }); 591 | --------------------------------------------------------------------------------