acorn-v3.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  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. function hasLineTerminatorBeforeNext(st, since) {
  8. return st.lineStart >= since;
  9. }
  10. function test(regex,st,noComment) {
  11. var src = st.input.slice(st.start) ;
  12. if (noComment) {
  13. src = src.replace(removeComments,"$1 $3") ;
  14. }
  15. return regex.test(src);
  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,parens) {
  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. var src = parser ;
  25. var dest = p ;
  26. ['inFunction','inAsyncFunction','inAsync','inGenerator','inModule'].forEach(function(k){
  27. if (k in src)
  28. dest[k] = src[k] ;
  29. }) ;
  30. if (parens)
  31. p.options.preserveParens = true ;
  32. p.nextToken();
  33. return p;
  34. }
  35. //TODO: Implement option noAsyncGetters
  36. function asyncAwaitPlugin (parser,options){
  37. var es7check = function(){} ;
  38. parser.extend("initialContext",function(base){
  39. return function(){
  40. if (this.options.ecmaVersion < 7) {
  41. es7check = function(node) {
  42. parser.raise(node.start,"async/await keywords only available when ecmaVersion>=7") ;
  43. } ;
  44. }
  45. this.reservedWords = new RegExp(this.reservedWords.toString().replace(/await|async/g,"").replace("|/","/").replace("/|","/").replace("||","|")) ;
  46. this.reservedWordsStrict = new RegExp(this.reservedWordsStrict.toString().replace(/await|async/g,"").replace("|/","/").replace("/|","/").replace("||","|")) ;
  47. this.reservedWordsStrictBind = new RegExp(this.reservedWordsStrictBind.toString().replace(/await|async/g,"").replace("|/","/").replace("/|","/").replace("||","|")) ;
  48. this.inAsyncFunction = options.inAsyncFunction ;
  49. if (options.awaitAnywhere && options.inAsyncFunction)
  50. parser.raise(node.start,"The options awaitAnywhere and inAsyncFunction are mutually exclusive") ;
  51. return base.apply(this,arguments);
  52. }
  53. }) ;
  54. parser.extend("shouldParseExportStatement",function(base){
  55. return function(){
  56. if (this.type.label==='name' && this.value==='async' && test(asyncFunction,this)) {
  57. return true ;
  58. }
  59. return base.apply(this,arguments) ;
  60. }
  61. }) ;
  62. parser.extend("parseStatement",function(base){
  63. return function (declaration, topLevel) {
  64. var start = this.start;
  65. var startLoc = this.startLoc;
  66. if (this.type.label==='name') {
  67. if (test(asyncFunction,this,true)) {
  68. var wasAsync = this.inAsyncFunction ;
  69. try {
  70. this.inAsyncFunction = true ;
  71. this.next() ;
  72. var r = this.parseStatement(declaration, topLevel) ;
  73. r.async = true ;
  74. r.start = start;
  75. r.loc && (r.loc.start = startLoc);
  76. r.range && (r.range[0] = start);
  77. return r ;
  78. } finally {
  79. this.inAsyncFunction = wasAsync ;
  80. }
  81. } else if ((typeof options==="object" && options.asyncExits) && test(asyncExit,this)) {
  82. // NON-STANDARD EXTENSION iff. options.asyncExits is set, the
  83. // extensions 'async return <expr>?' and 'async throw <expr>?'
  84. // are enabled. In each case they are the standard ESTree nodes
  85. // with the flag 'async:true'
  86. this.next() ;
  87. var r = this.parseStatement(declaration, topLevel) ;
  88. r.async = true ;
  89. r.start = start;
  90. r.loc && (r.loc.start = startLoc);
  91. r.range && (r.range[0] = start);
  92. return r ;
  93. }
  94. }
  95. return base.apply(this,arguments);
  96. }
  97. }) ;
  98. parser.extend("parseIdent",function(base){
  99. return function(liberal){
  100. var id = base.apply(this,arguments);
  101. if (this.inAsyncFunction && id.name==='await') {
  102. if (arguments.length===0) {
  103. this.raise(id.start,"'await' is reserved within async functions") ;
  104. }
  105. }
  106. return id ;
  107. }
  108. }) ;
  109. parser.extend("parseExprAtom",function(base){
  110. return function(refShorthandDefaultPos){
  111. var start = this.start ;
  112. var startLoc = this.startLoc;
  113. var rhs,r = base.apply(this,arguments);
  114. if (r.type==='Identifier') {
  115. if (r.name==='async' && !hasLineTerminatorBeforeNext(this, r.end)) {
  116. // Is this really an async function?
  117. var isAsync = this.inAsyncFunction ;
  118. try {
  119. this.inAsyncFunction = true ;
  120. var pp = this ;
  121. var inBody = false ;
  122. var parseHooks = {
  123. parseFunctionBody:function(node,isArrowFunction){
  124. try {
  125. var wasInBody = inBody ;
  126. inBody = true ;
  127. return pp.parseFunctionBody.apply(this,arguments) ;
  128. } finally {
  129. inBody = wasInBody ;
  130. }
  131. },
  132. raise:function(){
  133. try {
  134. return pp.raise.apply(this,arguments) ;
  135. } catch(ex) {
  136. throw inBody?ex:NotAsync ;
  137. }
  138. }
  139. } ;
  140. rhs = subParse(this,this.start,parseHooks,true).parseExpression() ;
  141. if (rhs.type==='SequenceExpression')
  142. rhs = rhs.expressions[0] ;
  143. if (rhs.type === 'CallExpression')
  144. rhs = rhs.callee ;
  145. if (rhs.type==='FunctionExpression' || rhs.type==='FunctionDeclaration' || rhs.type==='ArrowFunctionExpression') {
  146. // Because we don't know if the top level parser supprts preserveParens, we have to re-parse
  147. // without it set
  148. rhs = subParse(this,this.start,parseHooks).parseExpression() ;
  149. if (rhs.type==='SequenceExpression')
  150. rhs = rhs.expressions[0] ;
  151. if (rhs.type === 'CallExpression')
  152. rhs = rhs.callee ;
  153. rhs.async = true ;
  154. rhs.start = start;
  155. rhs.loc && (rhs.loc.start = startLoc);
  156. rhs.range && (rhs.range[0] = start);
  157. this.pos = rhs.end;
  158. this.end = rhs.end ;
  159. this.endLoc = rhs.endLoc ;
  160. this.next();
  161. es7check(rhs) ;
  162. return rhs ;
  163. }
  164. } catch (ex) {
  165. if (ex!==NotAsync)
  166. throw ex ;
  167. }
  168. finally {
  169. this.inAsyncFunction = isAsync ;
  170. }
  171. }
  172. else if (r.name==='await') {
  173. var n = this.startNodeAt(r.start, r.loc && r.loc.start);
  174. if (this.inAsyncFunction) {
  175. rhs = this.parseExprSubscripts() ;
  176. n.operator = 'await' ;
  177. n.argument = rhs ;
  178. n = this.finishNodeAt(n,'AwaitExpression', rhs.end, rhs.loc && rhs.loc.end) ;
  179. es7check(n) ;
  180. return n ;
  181. }
  182. // NON-STANDARD EXTENSION iff. options.awaitAnywhere is true,
  183. // an 'AwaitExpression' is allowed anywhere the token 'await'
  184. // could not be an identifier with the name 'await'.
  185. // Look-ahead to see if this is really a property or label called async or await
  186. if (this.input.slice(r.end).match(atomOrPropertyOrLabel)) {
  187. if (!options.awaitAnywhere && this.options.sourceType === 'module')
  188. return this.raise(r.start,"'await' is reserved within modules") ;
  189. return r ; // This is a valid property name or label
  190. }
  191. if (typeof options==="object" && options.awaitAnywhere) {
  192. start = this.start ;
  193. rhs = subParse(this,start-4).parseExprSubscripts() ;
  194. if (rhs.end<=start) {
  195. rhs = subParse(this,start).parseExprSubscripts() ;
  196. n.operator = 'await' ;
  197. n.argument = rhs ;
  198. n = this.finishNodeAt(n,'AwaitExpression', rhs.end, rhs.loc && rhs.loc.end) ;
  199. this.pos = rhs.end;
  200. this.end = rhs.end ;
  201. this.endLoc = rhs.endLoc ;
  202. this.next();
  203. es7check(n) ;
  204. return n ;
  205. }
  206. }
  207. if (!options.awaitAnywhere && this.options.sourceType === 'module')
  208. return this.raise(r.start,"'await' is reserved within modules") ;
  209. }
  210. }
  211. return r ;
  212. }
  213. }) ;
  214. parser.extend('finishNodeAt',function(base){
  215. return function(node,type,pos,loc) {
  216. if (node.__asyncValue) {
  217. delete node.__asyncValue ;
  218. node.value.async = true ;
  219. }
  220. return base.apply(this,arguments) ;
  221. }
  222. }) ;
  223. parser.extend('finishNode',function(base){
  224. return function(node,type) {
  225. if (node.__asyncValue) {
  226. delete node.__asyncValue ;
  227. node.value.async = true ;
  228. }
  229. return base.apply(this,arguments) ;
  230. }
  231. }) ;
  232. var allowedPropSpecifiers = {
  233. get:true,
  234. set:true,
  235. async:true
  236. };
  237. parser.extend("parsePropertyName",function(base){
  238. return function (prop) {
  239. var prevName = prop.key && prop.key.name ;
  240. var key = base.apply(this,arguments) ;
  241. if (key.type === "Identifier" && (key.name === "async") && !hasLineTerminatorBeforeNext(this, key.end)) {
  242. // Look-ahead to see if this is really a property or label called async or await
  243. if (!this.input.slice(key.end).match(atomOrPropertyOrLabel)){
  244. // Cheese - eliminate the cases 'async get(){}' and async set(){}'
  245. if (matchAsyncGet.test(this.input.slice(key.end))) {
  246. key = base.apply(this,arguments) ;
  247. prop.__asyncValue = true ;
  248. } else {
  249. es7check(prop) ;
  250. if (prop.kind === 'set')
  251. this.raise(key.start,"'set <member>(value)' cannot be be async") ;
  252. key = base.apply(this,arguments) ;
  253. if (key.type==='Identifier') {
  254. if (key.name==='set')
  255. this.raise(key.start,"'set <member>(value)' cannot be be async") ;
  256. }
  257. prop.__asyncValue = true ;
  258. }
  259. }
  260. }
  261. return key;
  262. };
  263. }) ;
  264. parser.extend("parseClassMethod",function(base){
  265. return function (classBody, method, isGenerator) {
  266. var wasAsync ;
  267. if (method.__asyncValue) {
  268. if (method.kind==='constructor')
  269. this.raise(method.start,"class constructor() cannot be be async") ;
  270. wasAsync = this.inAsyncFunction ;
  271. this.inAsyncFunction = true ;
  272. }
  273. var r = base.apply(this,arguments) ;
  274. this.inAsyncFunction = wasAsync ;
  275. return r ;
  276. }
  277. }) ;
  278. parser.extend("parseMethod",function(base){
  279. return function (isGenerator) {
  280. var wasAsync ;
  281. if (this.__currentProperty && this.__currentProperty.__asyncValue) {
  282. wasAsync = this.inAsyncFunction ;
  283. this.inAsyncFunction = true ;
  284. }
  285. var r = base.apply(this,arguments) ;
  286. this.inAsyncFunction = wasAsync ;
  287. return r ;
  288. }
  289. }) ;
  290. parser.extend("parsePropertyValue",function(base){
  291. return function (prop, isPattern, isGenerator, startPos, startLoc, refDestructuringErrors) {
  292. var prevProp = this.__currentProperty ;
  293. this.__currentProperty = prop ;
  294. var wasAsync ;
  295. if (prop.__asyncValue) {
  296. wasAsync = this.inAsyncFunction ;
  297. this.inAsyncFunction = true ;
  298. }
  299. var r = base.apply(this,arguments) ;
  300. this.inAsyncFunction = wasAsync ;
  301. this.__currentProperty = prevProp ;
  302. return r ;
  303. }
  304. }) ;
  305. }
  306. module.exports = asyncAwaitPlugin ;