[Pkg-javascript-commits] [acorn] 02/04: New upstream version 5.0.3

Julien Puydt julien.puydt at laposte.net
Thu Apr 20 15:28:24 UTC 2017


This is an automated email from the git hooks/post-receive script.

jpuydt-guest pushed a commit to branch master
in repository acorn.

commit 547efb4b5530cdcc70bee8e48d5097b32d94b309
Author: Julien Puydt <julien.puydt at laposte.net>
Date:   Thu Apr 20 17:08:52 2017 +0200

    New upstream version 5.0.3
---
 AUTHORS                  |   2 +
 CHANGELOG.md             |  28 +++++++++++
 package.json             |  12 +++--
 src/.eslintrc            |  33 +++++++++++++
 src/bin/.eslintrc        |   6 +++
 src/bin/acorn.js         |  26 +++++-----
 src/expression.js        |  45 ++++++++++-------
 src/identifier.js        |   6 ++-
 src/index.js             |   9 ++--
 src/loose/expression.js  |   4 +-
 src/loose/index.js       |   1 +
 src/loose/parseutil.js   |   2 +-
 src/loose/statement.js   |  14 +++---
 src/loose/tokenize.js    |  15 +++---
 src/lval.js              |  37 ++++++++++----
 src/options.js           |   5 +-
 src/parseutil.js         |   2 +-
 src/scope.js             |  75 +++++++++++++++++++++++++++++
 src/state.js             |   6 ++-
 src/statement.js         |  83 ++++++++++++++++++++------------
 src/tokencontext.js      |  39 +++++++++++++--
 src/tokenize.js          |  97 ++++++++++++++++++-------------------
 src/util.js              |  10 ++--
 src/walk/index.js        |  10 ++--
 test/lint.js             |  10 ++++
 test/tests-asyncawait.js |   2 +-
 test/tests-harmony.js    | 122 +++++++++++++++++++++++++++++++++++++++++++++--
 test/tests.js            |  18 +++++++
 28 files changed, 553 insertions(+), 166 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index ab64891..1377f60 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -23,6 +23,7 @@ Jesse McCarthy
 Jiaxing Wang
 Joel Kemp
 Johannes Herr
+John-David Dalton
 Jordan Klassen
 Jürg Lehni
 Kai Cataldo
@@ -56,6 +57,7 @@ Richard Gibson
 Rich Harris
 Sebastian McKenzie
 Simen Bekkhus
+Teddy Katz
 Timothy Gu
 Toru Nagashima
 Wexpo Lyu
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f6d1fa8..6b5834a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,31 @@
+## 5.0.3 (2017-04-01)
+
+### Bug fixes
+
+Fix spurious duplicate variable definition errors for named functions.
+
+## 5.0.2 (2017-03-30)
+
+### Bug fixes
+
+A binary operator after a parenthesized arrow expression is no longer incorrectly treated as an error.
+
+## 5.0.0 (2017-03-28)
+
+### Bug fixes
+
+Raise an error for duplicated lexical bindings.
+
+Fix spurious error when an assignement expression occurred after a spread expression.
+
+Accept regular expressions after `of` (in `for`/`of`), `yield` (in a generator), and braced arrow functions.
+
+Allow labels in front or `var` declarations, even in strict mode.
+
+### Breaking changes
+
+Parse declarations following `export default` as declaration nodes, not expressions. This means that class and function declarations nodes can now have `null` as their `id`.
+
 ## 4.0.11 (2017-02-07)
 
 ### Bug fixes
diff --git a/package.json b/package.json
index 29a32a7..a18edb2 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
   "homepage": "https://github.com/ternjs/acorn",
   "main": "dist/acorn.js",
   "jsnext:main": "dist/acorn.es.js",
-  "version": "4.0.11",
+  "version": "5.0.3",
   "engines": {
     "node": ">=0.4.0"
   },
@@ -27,18 +27,24 @@
   "license": "MIT",
   "scripts": {
     "prepublish": "npm test",
-    "test": "node test/run.js",
+    "test": "node test/run.js && node test/lint.js",
     "pretest": "npm run build",
     "build": "npm run build:main && npm run build:walk && npm run build:loose && npm run build:bin",
     "build:main": "rollup -c rollup/config.main.js",
     "build:walk": "rollup -c rollup/config.walk.js",
     "build:loose": "rollup -c rollup/config.loose.js",
-    "build:bin": "rollup -c rollup/config.bin.js"
+    "build:bin": "rollup -c rollup/config.bin.js",
+    "lint": "eslint src/"
   },
   "bin": {
     "acorn": "./bin/acorn"
   },
   "devDependencies": {
+    "eslint": "^3.18.0",
+    "eslint-config-standard": "^7.1.0",
+    "eslint-plugin-import": "^2.2.0",
+    "eslint-plugin-promise": "^3.5.0",
+    "eslint-plugin-standard": "^2.1.1",
     "rollup": "^0.34.1",
     "rollup-plugin-buble": "^0.11.0",
     "unicode-9.0.0": "^0.7.0"
diff --git a/src/.eslintrc b/src/.eslintrc
new file mode 100644
index 0000000..5549678
--- /dev/null
+++ b/src/.eslintrc
@@ -0,0 +1,33 @@
+{
+    "extends": [
+        "eslint:recommended",
+        "standard",
+        "plugin:import/errors",
+        "plugin:import/warnings"
+    ],
+    "rules": {
+        "curly": "off",
+        "eqeqeq": "off",
+        "indent": ["error", 2, { "SwitchCase": 0, "VariableDeclarator": 2 }],
+        "new-parens": "off",
+        "no-case-declarations": "off",
+        "no-cond-assign": "off",
+        "no-fallthrough": "off",
+        "no-labels": "off",
+        "no-mixed-operators": "off",
+        "no-return-assign": "off",
+        "no-unused-labels": "error",
+        "no-var": "error",
+        "object-curly-spacing": ["error", "never"],
+        "one-var": "off",
+        "quotes": ["error", "double"],
+        "semi-spacing": "off",
+        "space-before-function-paren": ["error", "never"]
+    },
+    "globals": {
+        "Packages": false
+    },
+    "plugins": [
+        "import"
+    ]
+}
\ No newline at end of file
diff --git a/src/bin/.eslintrc b/src/bin/.eslintrc
new file mode 100644
index 0000000..2598b25
--- /dev/null
+++ b/src/bin/.eslintrc
@@ -0,0 +1,6 @@
+{
+    "extends": "../.eslintrc",
+    "rules": {
+        "no-console": "off"
+    }
+}
\ No newline at end of file
diff --git a/src/bin/acorn.js b/src/bin/acorn.js
index 62e0dad..b9ee5ee 100644
--- a/src/bin/acorn.js
+++ b/src/bin/acorn.js
@@ -22,7 +22,7 @@ for (let i = 2; i < process.argv.length; ++i) {
   else if (arg == "--compact") compact = true
   else if (arg == "--help") help(0)
   else if (arg == "--tokenize") tokenize = true
-  else if (arg == "--module") options.sourceType = 'module'
+  else if (arg == "--module") options.sourceType = "module"
   else {
     let match = arg.match(/^--ecma(\d+)$/)
     if (match)
@@ -34,18 +34,20 @@ for (let i = 2; i < process.argv.length; ++i) {
 
 function run(code) {
   let result
-  if (!tokenize) {
-    try { result = acorn.parse(code, options) }
-    catch(e) { console.error(e.message); process.exit(1) }
-  } else {
-    result = []
-    let tokenizer = acorn.tokenizer(code, options), token
-    while (true) {
-      try { token = tokenizer.getToken() }
-      catch(e) { console.error(e.message); process.exit(1) }
-      result.push(token)
-      if (token.type == acorn.tokTypes.eof) break
+  try {
+    if (!tokenize) {
+      result = acorn.parse(code, options)
+    } else {
+      result = []
+      let tokenizer = acorn.tokenizer(code, options), token
+      do {
+        token = tokenizer.getToken()
+        result.push(token)
+      } while (token.type != acorn.tokTypes.eof)
     }
+  } catch (e) {
+    console.error(e.message)
+    process.exit(1)
   }
   if (!silent) console.log(JSON.stringify(result, null, compact ? null : 2))
 }
diff --git a/src/expression.js b/src/expression.js
index bbcd9da..47790d2 100644
--- a/src/expression.js
+++ b/src/expression.js
@@ -47,8 +47,13 @@ pp.checkPropClash = function(prop, propHash) {
   name = "$" + name
   let other = propHash[name]
   if (other) {
-    let isGetSet = kind !== "init"
-    if ((this.strict || isGetSet) && other[kind] || !(isGetSet ^ other.init))
+    let redefinition
+    if (kind === "init") {
+      redefinition = this.strict && other.init || other.get || other.set
+    } else {
+      redefinition = other.init || other[kind]
+    }
+    if (redefinition)
       this.raiseRecoverable(key.start, "Redefinition of property")
   } else {
     other = propHash[name] = {
@@ -93,10 +98,11 @@ pp.parseExpression = function(noIn, refDestructuringErrors) {
 pp.parseMaybeAssign = function(noIn, refDestructuringErrors, afterLeftParse) {
   if (this.inGenerator && this.isContextual("yield")) return this.parseYield()
 
-  let ownDestructuringErrors = false, oldParenAssign = -1
+  let ownDestructuringErrors = false, oldParenAssign = -1, oldTrailingComma = -1
   if (refDestructuringErrors) {
     oldParenAssign = refDestructuringErrors.parenthesizedAssign
-    refDestructuringErrors.parenthesizedAssign = -1
+    oldTrailingComma = refDestructuringErrors.trailingComma
+    refDestructuringErrors.parenthesizedAssign = refDestructuringErrors.trailingComma = -1
   } else {
     refDestructuringErrors = new DestructuringErrors
     ownDestructuringErrors = true
@@ -122,6 +128,7 @@ pp.parseMaybeAssign = function(noIn, refDestructuringErrors, afterLeftParse) {
     if (ownDestructuringErrors) this.checkExpressionErrors(refDestructuringErrors, true)
   }
   if (oldParenAssign > -1) refDestructuringErrors.parenthesizedAssign = oldParenAssign
+  if (oldTrailingComma > -1) refDestructuringErrors.trailingComma = oldTrailingComma
   return left
 }
 
@@ -148,7 +155,7 @@ pp.parseExprOps = function(noIn, refDestructuringErrors) {
   let startPos = this.start, startLoc = this.startLoc
   let expr = this.parseMaybeUnary(refDestructuringErrors, false)
   if (this.checkExpressionErrors(refDestructuringErrors)) return expr
-  return this.parseExprOp(expr, startPos, startLoc, -1, noIn)
+  return expr.start == startPos && expr.type === "ArrowFunctionExpression" ? expr : this.parseExprOp(expr, startPos, startLoc, -1, noIn)
 }
 
 // Parse binary operators with the operator precedence parsing
@@ -486,7 +493,7 @@ pp.parseNew = function() {
 pp.parseTemplateElement = function() {
   let elem = this.startNode()
   elem.value = {
-    raw: this.input.slice(this.start, this.end).replace(/\r\n?/g, '\n'),
+    raw: this.input.slice(this.start, this.end).replace(/\r\n?/g, "\n"),
     cooked: this.value
   }
   this.next()
@@ -642,6 +649,7 @@ pp.parseMethod = function(isGenerator, isAsync) {
   this.yieldPos = 0
   this.awaitPos = 0
   this.inFunction = true
+  this.enterFunctionScope()
 
   this.expect(tt.parenL)
   node.params = this.parseBindingList(tt.parenR, false, this.options.ecmaVersion >= 8)
@@ -662,6 +670,7 @@ pp.parseArrowExpression = function(node, params, isAsync) {
   let oldInGen = this.inGenerator, oldInAsync = this.inAsync,
       oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, oldInFunc = this.inFunction
 
+  this.enterFunctionScope()
   this.initFunction(node)
   if (this.options.ecmaVersion >= 8)
     node.async = !!isAsync
@@ -692,6 +701,7 @@ pp.parseFunctionBody = function(node, isArrowFunction) {
   if (isExpression) {
     node.body = this.parseMaybeAssign()
     node.expression = true
+    this.checkParams(node, false)
   } else {
     let nonSimple = this.options.ecmaVersion >= 7 && !this.isSimpleParamList(node.params)
     if (!oldStrict || nonSimple) {
@@ -707,20 +717,21 @@ pp.parseFunctionBody = function(node, isArrowFunction) {
     let oldLabels = this.labels
     this.labels = []
     if (useStrict) this.strict = true
-    node.body = this.parseBlock(true)
+
+    // Add the params to varDeclaredNames to ensure that an error is thrown
+    // if a let/const declaration in the function clashes with one of the params.
+    this.checkParams(node, !oldStrict && !useStrict && !isArrowFunction && this.isSimpleParamList(node.params))
+    node.body = this.parseBlock(false)
     node.expression = false
     this.labels = oldLabels
   }
+  this.exitFunctionScope()
 
-  if (oldStrict || useStrict) {
-    this.strict = true
-    if (node.id)
-      this.checkLVal(node.id, true)
-    this.checkParams(node)
-    this.strict = oldStrict
-  } else if (isArrowFunction || !this.isSimpleParamList(node.params)) {
-    this.checkParams(node)
+  if (this.strict && node.id) {
+    // Ensure the function name isn't a forbidden identifier in strict mode, e.g. 'eval'
+    this.checkLVal(node.id, "none")
   }
+  this.strict = oldStrict
 }
 
 pp.isSimpleParamList = function(params) {
@@ -732,9 +743,9 @@ pp.isSimpleParamList = function(params) {
 // Checks function params for various disallowed patterns such as using "eval"
 // or "arguments" and duplicate parameters.
 
-pp.checkParams = function(node) {
+pp.checkParams = function(node, allowDuplicates) {
   let nameHash = {}
-  for (let i = 0; i < node.params.length; i++) this.checkLVal(node.params[i], true, nameHash)
+  for (let i = 0; i < node.params.length; i++) this.checkLVal(node.params[i], "var", allowDuplicates ? null : nameHash)
 }
 
 // Parses a comma-separated list of expressions, and returns them as
diff --git a/src/identifier.js b/src/identifier.js
index c65a24c..fa5ded6 100644
--- a/src/identifier.js
+++ b/src/identifier.js
@@ -10,7 +10,7 @@ export const reservedWords = {
 
 // And the keywords
 
-var ecma5AndLessKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"
+const ecma5AndLessKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"
 
 export const keywords = {
   5: ecma5AndLessKeywords,
@@ -38,7 +38,11 @@ nonASCIIidentifierStartChars = nonASCIIidentifierChars = null
 // offset starts at 0x10000, and each pair of numbers represents an
 // offset to the next range, and then a size of the range. They were
 // generated by bin/generate-identifier-regex.js
+
+// eslint-disable-next-line comma-spacing
 const astralIdentifierStartCodes = [0,11,2,25,2,18,2,1,2,14,3,13,35,122,70,52,268,28,4,48,48,31,17,26,6,37,11,29,3,35,5,7,2,4,43,157,19,35,5,35,5,39,9,51,157,310,10,21,11,7,153,5,3,0,2,43,2,1,4,0,3,22,11,22,10,30,66,18,2,1,11,21,11,25,71,55,7,1,65,0,16,3,2,2,2,26,45,28,4,28,36,7,2,27,28,53,11,21,11,18,14,17,111,72,56,50,14,50,785,52,76,44,33,24,27,35,42,34,4,0,13,47,15,3,22,0,2,0,36,17,2,24,85,6,2,0,2,3,2,14,2,9,8,46,39,7,3,1,3,21,2,6,2,1,2,4,4,0,19,0,13,4,159,52,19,3,54,47,21,1,2,0,185, [...]
+
+// eslint-disable-next-line comma-spacing
 const astralIdentifierCodes = [509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,166,1,1306,2,54,14,32,9,16,3,46,10,54,9,7,2,37,13,2,9,52,0,13,2,49,13,10,2,4,9,83,11,7,0,161,11,6,9,7,3,57,0,2,6,3,1,3,2,10,0,11,1,3,6,4,4,193,17,10,9,87,19,13,9,214,6,3,8,28,1,83,16,16,9,82,12,9,9,84,14,5,9,423,9,838,7,2,7,17,9,57,21,2,13,19882,9,135,4,60,6,26,9,1016,45,17,3,19723,1,5319,4,4,5,9,7,3,6,31,3,149,2,1418,49,513,54,5,49,9,0,15,0,23,4,2,14,1361,6,2,16,3,6,2,1,2,4,2214,6,110,6,6,9,792487,239]
 
 // This has a complexity linear to the value of the code. The
diff --git a/src/index.js b/src/index.js
index 3f93d1e..9c25d92 100644
--- a/src/index.js
+++ b/src/index.js
@@ -25,6 +25,7 @@ import "./statement"
 import "./lval"
 import "./expression"
 import "./location"
+import "./scope"
 
 export {Parser, plugins} from "./state"
 export {defaultOptions} from "./options"
@@ -34,9 +35,9 @@ export {TokenType, types as tokTypes, keywords as keywordTypes} from "./tokentyp
 export {TokContext, types as tokContexts} from "./tokencontext"
 export {isIdentifierChar, isIdentifierStart} from "./identifier"
 export {Token} from "./tokenize"
-export {isNewLine, lineBreak, lineBreakG} from "./whitespace"
+export {isNewLine, lineBreak, lineBreakG, nonASCIIwhitespace} from "./whitespace"
 
-export const version = "4.0.11"
+export const version = "5.0.3"
 
 // The main exported interface (under `self.acorn` when in the
 // browser) is a `parse` function that takes a code string and
@@ -69,9 +70,9 @@ export function tokenizer(input, options) {
 // This is a terrible kludge to support the existing, pre-ES6
 // interface where the loose parser module retroactively adds exports
 // to this module.
-export let parse_dammit, LooseParser, pluginsLoose
+export let parse_dammit, LooseParser, pluginsLoose // eslint-disable-line camelcase
 export function addLooseExports(parse, Parser, plugins) {
-  parse_dammit = parse
+  parse_dammit = parse // eslint-disable-line camelcase
   LooseParser = Parser
   pluginsLoose = plugins
 }
diff --git a/src/loose/expression.js b/src/loose/expression.js
index 321ca66..62bd42d 100644
--- a/src/loose/expression.js
+++ b/src/loose/expression.js
@@ -319,7 +319,7 @@ lp.parseNew = function() {
 lp.parseTemplateElement = function() {
   let elem = this.startNode()
   elem.value = {
-    raw: this.input.slice(this.tok.start, this.tok.end).replace(/\r\n?/g, '\n'),
+    raw: this.input.slice(this.tok.start, this.tok.end).replace(/\r\n?/g, "\n"),
     cooked: this.tok.value
   }
   this.next()
@@ -340,7 +340,7 @@ lp.parseTemplate = function() {
       curElt = this.parseTemplateElement()
     } else {
       curElt = this.startNode()
-      curElt.value = {cooked: '', raw: ''}
+      curElt.value = {cooked: "", raw: ""}
       curElt.tail = true
       this.finishNode(curElt, "TemplateElement")
     }
diff --git a/src/loose/index.js b/src/loose/index.js
index 98bbf4b..daf7bf2 100644
--- a/src/loose/index.js
+++ b/src/loose/index.js
@@ -39,6 +39,7 @@ export {LooseParser, pluginsLoose} from "./state"
 
 defaultOptions.tabSize = 4
 
+// eslint-disable-next-line camelcase
 export function parse_dammit(input, options) {
   let p = new LooseParser(input, options)
   p.next()
diff --git a/src/loose/parseutil.js b/src/loose/parseutil.js
index c5ee096..b620fda 100644
--- a/src/loose/parseutil.js
+++ b/src/loose/parseutil.js
@@ -1 +1 @@
-export function isDummy(node) { return node.name == "✖" }
\ No newline at end of file
+export function isDummy(node) { return node.name == "✖" }
diff --git a/src/loose/statement.js b/src/loose/statement.js
index f4a3b1f..192df43 100644
--- a/src/loose/statement.js
+++ b/src/loose/statement.js
@@ -251,9 +251,8 @@ lp.parseVar = function(noIn, kind) {
 lp.parseClass = function(isStatement) {
   let node = this.startNode()
   this.next()
-  if (isStatement == null) isStatement = this.tok.type === tt.name
   if (this.tok.type === tt.name) node.id = this.parseIdent()
-  else if (isStatement) node.id = this.dummyIdent()
+  else if (isStatement === true) node.id = this.dummyIdent()
   else node.id = null
   node.superClass = this.eat(tt._extends) ? this.parseExpression() : null
   node.body = this.startNode()
@@ -299,7 +298,7 @@ lp.parseClass = function(isStatement) {
           method.key.type === "Literal" && method.key.value === "constructor")) {
         method.kind = "constructor"
       } else {
-        method.kind =  "method"
+        method.kind = "method"
       }
       method.value = this.parseMethod(isGenerator, isAsync)
     }
@@ -326,9 +325,8 @@ lp.parseFunction = function(node, isStatement, isAsync) {
   if (this.options.ecmaVersion >= 8) {
     node.async = !!isAsync
   }
-  if (isStatement == null) isStatement = this.tok.type === tt.name
   if (this.tok.type === tt.name) node.id = this.parseIdent()
-  else if (isStatement) node.id = this.dummyIdent()
+  else if (isStatement === true) node.id = this.dummyIdent()
   this.inAsync = node.async
   node.params = this.parseFunctionParams()
   node.body = this.parseBlock()
@@ -350,9 +348,9 @@ lp.parseExport = function() {
       let fNode = this.startNode()
       this.next()
       if (isAsync) this.next()
-      node.declaration = this.parseFunction(fNode, null, isAsync)
+      node.declaration = this.parseFunction(fNode, "nullableID", isAsync)
     } else if (this.tok.type === tt._class) {
-      node.declaration = this.parseClass(null)
+      node.declaration = this.parseClass("nullableID")
     } else {
       node.declaration = this.parseMaybeAssign()
       this.semicolon()
@@ -378,7 +376,7 @@ lp.parseImport = function() {
   if (this.tok.type === tt.string) {
     node.specifiers = []
     node.source = this.parseExprAtom()
-    node.kind = ''
+    node.kind = ""
   } else {
     let elt
     if (this.tok.type === tt.name && this.tok.value !== "from") {
diff --git a/src/loose/tokenize.js b/src/loose/tokenize.js
index c08be97..2d5130b 100644
--- a/src/loose/tokenize.js
+++ b/src/loose/tokenize.js
@@ -34,7 +34,7 @@ lp.readToken = function() {
         this.toks.type = tt.ellipsis
       }
       return new Token(this.toks)
-    } catch(e) {
+    } catch (e) {
       if (!(e instanceof SyntaxError)) throw e
 
       // Try to skip some text, based on the error message, and then continue
@@ -45,12 +45,15 @@ lp.readToken = function() {
           replace = {start: e.pos, end: pos, type: tt.string, value: this.input.slice(e.pos + 1, pos)}
         } else if (/regular expr/i.test(msg)) {
           let re = this.input.slice(e.pos, pos)
-          try { re = new RegExp(re) } catch(e) {}
+          try { re = new RegExp(re) } catch (e) { /* ignore compilation error due to new syntax */ }
           replace = {start: e.pos, end: pos, type: tt.regexp, value: re}
         } else if (/template/.test(msg)) {
-          replace = {start: e.pos, end: pos,
-                     type: tt.template,
-                     value: this.input.slice(e.pos, pos)}
+          replace = {
+            start: e.pos,
+            end: pos,
+            type: tt.template,
+            value: this.input.slice(e.pos, pos)
+          }
         } else {
           replace = false
         }
@@ -86,7 +89,7 @@ lp.readToken = function() {
 lp.resetTo = function(pos) {
   this.toks.pos = pos
   let ch = this.input.charAt(pos - 1)
-  this.toks.exprAllowed = !ch || /[\[\{\(,;:?\/*=+\-~!|&%^<>]/.test(ch) ||
+  this.toks.exprAllowed = !ch || /[[{(,;:?/*=+\-~!|&%^<>]/.test(ch) ||
     /[enwfd]/.test(ch) &&
     /\b(keywords|case|else|return|throw|new|in|(instance|type)of|delete|void)$/.test(this.input.slice(pos - 10, pos))
 
diff --git a/src/lval.js b/src/lval.js
index c88d719..815d996 100644
--- a/src/lval.js
+++ b/src/lval.js
@@ -10,7 +10,7 @@ const pp = Parser.prototype
 pp.toAssignable = function(node, isBinding) {
   if (this.options.ecmaVersion >= 6 && node) {
     switch (node.type) {
-      case "Identifier":
+    case "Identifier":
       if (this.inAsync && node.name === "await")
         this.raise(node.start, "Can not use 'await' as identifier inside an async function")
       break
@@ -172,48 +172,65 @@ pp.parseMaybeDefault = function(startPos, startLoc, left) {
 
 // Verify that a node is an lval — something that can be assigned
 // to.
+// bindingType can be either:
+// 'var' indicating that the lval creates a 'var' binding
+// 'let' indicating that the lval creates a lexical ('let' or 'const') binding
+// 'none' indicating that the binding should be checked for illegal identifiers, but not for duplicate references
 
-pp.checkLVal = function(expr, isBinding, checkClashes) {
+pp.checkLVal = function(expr, bindingType, checkClashes) {
   switch (expr.type) {
   case "Identifier":
     if (this.strict && this.reservedWordsStrictBind.test(expr.name))
-      this.raiseRecoverable(expr.start, (isBinding ? "Binding " : "Assigning to ") + expr.name + " in strict mode")
+      this.raiseRecoverable(expr.start, (bindingType ? "Binding " : "Assigning to ") + expr.name + " in strict mode")
     if (checkClashes) {
       if (has(checkClashes, expr.name))
         this.raiseRecoverable(expr.start, "Argument name clash")
       checkClashes[expr.name] = true
     }
+    if (bindingType && bindingType !== "none") {
+      if (
+        bindingType === "var" && !this.canDeclareVarName(expr.name) ||
+        bindingType !== "var" && !this.canDeclareLexicalName(expr.name)
+      ) {
+        this.raiseRecoverable(expr.start, `Identifier '${expr.name}' has already been declared`)
+      }
+      if (bindingType === "var") {
+        this.declareVarName(expr.name)
+      } else {
+        this.declareLexicalName(expr.name)
+      }
+    }
     break
 
   case "MemberExpression":
-    if (isBinding) this.raiseRecoverable(expr.start, (isBinding ? "Binding" : "Assigning to") + " member expression")
+    if (bindingType) this.raiseRecoverable(expr.start, (bindingType ? "Binding" : "Assigning to") + " member expression")
     break
 
   case "ObjectPattern":
     for (let i = 0; i < expr.properties.length; i++)
-      this.checkLVal(expr.properties[i].value, isBinding, checkClashes)
+      this.checkLVal(expr.properties[i].value, bindingType, checkClashes)
     break
 
   case "ArrayPattern":
     for (let i = 0; i < expr.elements.length; i++) {
       let elem = expr.elements[i]
-      if (elem) this.checkLVal(elem, isBinding, checkClashes)
+      if (elem) this.checkLVal(elem, bindingType, checkClashes)
     }
     break
 
   case "AssignmentPattern":
-    this.checkLVal(expr.left, isBinding, checkClashes)
+    this.checkLVal(expr.left, bindingType, checkClashes)
     break
 
   case "RestElement":
-    this.checkLVal(expr.argument, isBinding, checkClashes)
+    this.checkLVal(expr.argument, bindingType, checkClashes)
     break
 
   case "ParenthesizedExpression":
-    this.checkLVal(expr.expression, isBinding, checkClashes)
+    this.checkLVal(expr.expression, bindingType, checkClashes)
     break
 
   default:
-    this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " rvalue")
+    this.raise(expr.start, (bindingType ? "Binding" : "Assigning to") + " rvalue")
   }
 }
diff --git a/src/options.js b/src/options.js
index e7b217e..9dda44a 100644
--- a/src/options.js
+++ b/src/options.js
@@ -111,9 +111,9 @@ export function getOptions(opts) {
 }
 
 function pushComment(options, array) {
-  return function (block, text, start, end, startLoc, endLoc) {
+  return function(block, text, start, end, startLoc, endLoc) {
     let comment = {
-      type: block ? 'Block' : 'Line',
+      type: block ? "Block" : "Line",
       value: text,
       start: start,
       end: end
@@ -125,4 +125,3 @@ function pushComment(options, array) {
     array.push(comment)
   }
 }
-
diff --git a/src/parseutil.js b/src/parseutil.js
index 46e4521..ffa0050 100644
--- a/src/parseutil.js
+++ b/src/parseutil.js
@@ -6,7 +6,7 @@ const pp = Parser.prototype
 
 // ## Parser utilities
 
-const literal = /^(?:'((?:[^\']|\.)*)'|"((?:[^\"]|\.)*)"|;)/
+const literal = /^(?:'((?:[^']|\.)*)'|"((?:[^"]|\.)*)"|;)/
 pp.strictDirective = function(start) {
   for (;;) {
     skipWhiteSpace.lastIndex = start
diff --git a/src/scope.js b/src/scope.js
new file mode 100644
index 0000000..2ec0448
--- /dev/null
+++ b/src/scope.js
@@ -0,0 +1,75 @@
+import {Parser} from "./state"
+import {has} from "./util"
+
+const pp = Parser.prototype
+
+// Object.assign polyfill
+const assign = Object.assign || function(target, ...sources) {
+  for (let i = 0; i < sources.length; i++) {
+    const source = sources[i]
+    for (const key in source) {
+      if (has(source, key)) {
+        target[key] = source[key]
+      }
+    }
+  }
+  return target
+}
+
+// The functions in this module keep track of declared variables in the current scope in order to detect duplicate variable names.
+
+pp.enterFunctionScope = function() {
+  // var: a hash of var-declared names in the current lexical scope
+  // lexical: a hash of lexically-declared names in the current lexical scope
+  // childVar: a hash of var-declared names in all child lexical scopes of the current lexical scope (within the current function scope)
+  // parentLexical: a hash of lexically-declared names in all parent lexical scopes of the current lexical scope (within the current function scope)
+  this.scopeStack.push({var: {}, lexical: {}, childVar: {}, parentLexical: {}})
+}
+
+pp.exitFunctionScope = function() {
+  this.scopeStack.pop()
+}
+
+pp.enterLexicalScope = function() {
+  const parentScope = this.scopeStack[this.scopeStack.length - 1]
+  const childScope = {var: {}, lexical: {}, childVar: {}, parentLexical: {}}
+
+  this.scopeStack.push(childScope)
+  assign(childScope.parentLexical, parentScope.lexical, parentScope.parentLexical)
+}
+
+pp.exitLexicalScope = function() {
+  const childScope = this.scopeStack.pop()
+  const parentScope = this.scopeStack[this.scopeStack.length - 1]
+
+  assign(parentScope.childVar, childScope.var, childScope.childVar)
+}
+
+/**
+ * A name can be declared with `var` if there are no variables with the same name declared with `let`/`const`
+ * in the current lexical scope or any of the parent lexical scopes in this function.
+ */
+pp.canDeclareVarName = function(name) {
+  const currentScope = this.scopeStack[this.scopeStack.length - 1]
+
+  return !has(currentScope.lexical, name) && !has(currentScope.parentLexical, name)
+}
+
+/**
+ * A name can be declared with `let`/`const` if there are no variables with the same name declared with `let`/`const`
+ * in the current scope, and there are no variables with the same name declared with `var` in the current scope or in
+ * any child lexical scopes in this function.
+ */
+pp.canDeclareLexicalName = function(name) {
+  const currentScope = this.scopeStack[this.scopeStack.length - 1]
+
+  return !has(currentScope.lexical, name) && !has(currentScope.var, name) && !has(currentScope.childVar, name)
+}
+
+pp.declareVarName = function(name) {
+  this.scopeStack[this.scopeStack.length - 1].var[name] = true
+}
+
+pp.declareLexicalName = function(name) {
+  this.scopeStack[this.scopeStack.length - 1].lexical[name] = true
+}
diff --git a/src/state.js b/src/state.js
index 4c244e4..5d9ae75 100644
--- a/src/state.js
+++ b/src/state.js
@@ -83,8 +83,12 @@ export class Parser {
     this.labels = []
 
     // If enabled, skip leading hashbang line.
-    if (this.pos === 0 && options.allowHashBang && this.input.slice(0, 2) === '#!')
+    if (this.pos === 0 && options.allowHashBang && this.input.slice(0, 2) === "#!")
       this.skipLineComment(2)
+
+    // Scope tracking for duplicate variable names (see scope.js)
+    this.scopeStack = []
+    this.enterFunctionScope()
   }
 
   // DEPRECATED Kept for backwards compatibility until 3.0 in case a plugin uses them
diff --git a/src/statement.js b/src/statement.js
index ee09613..eb2f18e 100644
--- a/src/statement.js
+++ b/src/statement.js
@@ -2,6 +2,7 @@ import {types as tt} from "./tokentype"
 import {Parser} from "./state"
 import {lineBreak, skipWhiteSpace} from "./whitespace"
 import {isIdentifierStart, isIdentifierChar} from "./identifier"
+import {has} from "./util"
 import {DestructuringErrors} from "./parseutil"
 
 const pp = Parser.prototype
@@ -36,7 +37,8 @@ pp.isLet = function() {
   let next = this.pos + skip[0].length, nextCh = this.input.charCodeAt(next)
   if (nextCh === 91 || nextCh == 123) return true // '{' and '['
   if (isIdentifierStart(nextCh, true)) {
-    for (var pos = next + 1; isIdentifierChar(this.input.charCodeAt(pos), true); ++pos) {}
+    let pos = next + 1
+    while (isIdentifierChar(this.input.charCodeAt(pos), true)) ++pos
     let ident = this.input.slice(next, pos)
     if (!this.isKeyword(ident)) return true
   }
@@ -141,7 +143,8 @@ pp.parseBreakContinueStatement = function(node, keyword) {
 
   // Verify that there is an actual destination to break or
   // continue to.
-  for (var i = 0; i < this.labels.length; ++i) {
+  let i = 0
+  for (; i < this.labels.length; ++i) {
     let lab = this.labels[i]
     if (node.label == null || lab.name === node.label.name) {
       if (lab.kind != null && (isBreak || lab.kind === "loop")) break
@@ -183,6 +186,7 @@ pp.parseDoStatement = function(node) {
 pp.parseForStatement = function(node) {
   this.next()
   this.labels.push(loopLabel)
+  this.enterLexicalScope()
   this.expect(tt.parenL)
   if (this.type === tt.semi) return this.parseFor(node, null)
   let isLet = this.isLet()
@@ -247,12 +251,14 @@ pp.parseSwitchStatement = function(node) {
   node.cases = []
   this.expect(tt.braceL)
   this.labels.push(switchLabel)
+  this.enterLexicalScope()
 
   // Statements under must be grouped (by label) in SwitchCase
   // nodes. `cur` is used to keep the node that we are currently
   // adding statements to.
 
-  for (var cur, sawDefault = false; this.type != tt.braceR;) {
+  let cur
+  for (let sawDefault = false; this.type != tt.braceR;) {
     if (this.type === tt._case || this.type === tt._default) {
       let isCase = this.type === tt._case
       if (cur) this.finishNode(cur, "SwitchCase")
@@ -272,6 +278,7 @@ pp.parseSwitchStatement = function(node) {
       cur.consequent.push(this.parseStatement(true))
     }
   }
+  this.exitLexicalScope()
   if (cur) this.finishNode(cur, "SwitchCase")
   this.next() // Closing brace
   this.labels.pop()
@@ -300,9 +307,11 @@ pp.parseTryStatement = function(node) {
     this.next()
     this.expect(tt.parenL)
     clause.param = this.parseBindingAtom()
-    this.checkLVal(clause.param, true)
+    this.enterLexicalScope()
+    this.checkLVal(clause.param, "let")
     this.expect(tt.parenR)
-    clause.body = this.parseBlock()
+    clause.body = this.parseBlock(false)
+    this.exitLexicalScope()
     node.handler = this.finishNode(clause, "CatchClause")
   }
   node.finalizer = this.eat(tt._finally) ? this.parseBlock() : null
@@ -354,7 +363,7 @@ pp.parseLabeledStatement = function(node, maybeName, expr) {
   this.labels.push({name: maybeName, kind: kind, statementStart: this.start})
   node.body = this.parseStatement(true)
   if (node.body.type == "ClassDeclaration" ||
-      node.body.type == "VariableDeclaration" && (this.strict || node.body.kind != "var") ||
+      node.body.type == "VariableDeclaration" && node.body.kind != "var" ||
       node.body.type == "FunctionDeclaration" && (this.strict || node.body.generator))
     this.raiseRecoverable(node.body.start, "Invalid labeled declaration")
   this.labels.pop()
@@ -372,14 +381,20 @@ pp.parseExpressionStatement = function(node, expr) {
 // strict"` declarations when `allowStrict` is true (used for
 // function bodies).
 
-pp.parseBlock = function() {
+pp.parseBlock = function(createNewLexicalScope = true) {
   let node = this.startNode()
   node.body = []
   this.expect(tt.braceL)
+  if (createNewLexicalScope) {
+    this.enterLexicalScope()
+  }
   while (!this.eat(tt.braceR)) {
     let stmt = this.parseStatement(true)
     node.body.push(stmt)
   }
+  if (createNewLexicalScope) {
+    this.exitLexicalScope()
+  }
   return this.finishNode(node, "BlockStatement")
 }
 
@@ -394,6 +409,7 @@ pp.parseFor = function(node, init) {
   this.expect(tt.semi)
   node.update = this.type === tt.parenR ? null : this.parseExpression()
   this.expect(tt.parenR)
+  this.exitLexicalScope()
   node.body = this.parseStatement(false)
   this.labels.pop()
   return this.finishNode(node, "ForStatement")
@@ -408,6 +424,7 @@ pp.parseForIn = function(node, init) {
   node.left = init
   node.right = this.parseExpression()
   this.expect(tt.parenR)
+  this.exitLexicalScope()
   node.body = this.parseStatement(false)
   this.labels.pop()
   return this.finishNode(node, type)
@@ -420,7 +437,7 @@ pp.parseVar = function(node, isFor, kind) {
   node.kind = kind
   for (;;) {
     let decl = this.startNode()
-    this.parseVarId(decl)
+    this.parseVarId(decl, kind)
     if (this.eat(tt.eq)) {
       decl.init = this.parseMaybeAssign(isFor)
     } else if (kind === "const" && !(this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual("of")))) {
@@ -436,9 +453,9 @@ pp.parseVar = function(node, isFor, kind) {
   return node
 }
 
-pp.parseVarId = function(decl) {
-  decl.id = this.parseBindingAtom()
-  this.checkLVal(decl.id, true)
+pp.parseVarId = function(decl, kind) {
+  decl.id = this.parseBindingAtom(kind)
+  this.checkLVal(decl.id, kind, false)
 }
 
 // Parse a function declaration or literal (depending on the
@@ -451,10 +468,12 @@ pp.parseFunction = function(node, isStatement, allowExpressionBody, isAsync) {
   if (this.options.ecmaVersion >= 8)
     node.async = !!isAsync
 
-  if (isStatement == null)
-    isStatement = this.type == tt.name
-  if (isStatement)
-    node.id = this.parseIdent()
+  if (isStatement) {
+    node.id = isStatement === "nullableID" && this.type != tt.name ? null : this.parseIdent()
+    if (node.id) {
+      this.checkLVal(node.id, "var")
+    }
+  }
 
   let oldInGen = this.inGenerator, oldInAsync = this.inAsync,
       oldYieldPos = this.yieldPos, oldAwaitPos = this.awaitPos, oldInFunc = this.inFunction
@@ -463,9 +482,11 @@ pp.parseFunction = function(node, isStatement, allowExpressionBody, isAsync) {
   this.yieldPos = 0
   this.awaitPos = 0
   this.inFunction = true
+  this.enterFunctionScope()
+
+  if (!isStatement)
+    node.id = this.type == tt.name ? this.parseIdent() : null
 
-  if (!isStatement && this.type === tt.name)
-    node.id = this.parseIdent()
   this.parseFunctionParams(node)
   this.parseFunctionBody(node, allowExpressionBody)
 
@@ -488,7 +509,7 @@ pp.parseFunctionParams = function(node) {
 
 pp.parseClass = function(node, isStatement) {
   this.next()
-  if (isStatement == null) isStatement = this.type === tt.name
+
   this.parseClassId(node, isStatement)
   this.parseClassSuper(node)
   let classBody = this.startNode()
@@ -558,7 +579,7 @@ pp.parseClassMethod = function(classBody, method, isGenerator, isAsync) {
 }
 
 pp.parseClassId = function(node, isStatement) {
-  node.id = this.type === tt.name ? this.parseIdent() : isStatement ? this.unexpected() : null
+  node.id = this.type === tt.name ? this.parseIdent() : isStatement === true ? this.unexpected() : null
 }
 
 pp.parseClassSuper = function(node) {
@@ -583,10 +604,10 @@ pp.parseExport = function(node, exports) {
       let fNode = this.startNode()
       this.next()
       if (isAsync) this.next()
-      node.declaration = this.parseFunction(fNode, null, false, isAsync)
+      node.declaration = this.parseFunction(fNode, "nullableID", false, isAsync)
     } else if (this.type === tt._class) {
       let cNode = this.startNode()
-      node.declaration = this.parseClass(cNode, null)
+      node.declaration = this.parseClass(cNode, "nullableID")
     } else {
       node.declaration = this.parseMaybeAssign()
       this.semicolon()
@@ -624,7 +645,7 @@ pp.parseExport = function(node, exports) {
 
 pp.checkExport = function(exports, name, pos) {
   if (!exports) return
-  if (Object.prototype.hasOwnProperty.call(exports, name))
+  if (has(exports, name))
     this.raiseRecoverable(pos, "Duplicate export '" + name + "'")
   exports[name] = true
 }
@@ -654,12 +675,12 @@ pp.checkVariableExport = function(exports, decls) {
 }
 
 pp.shouldParseExportStatement = function() {
-  return this.type.keyword === "var"
-    || this.type.keyword === "const"
-    || this.type.keyword === "class"
-    || this.type.keyword === "function"
-    || this.isLet()
-    || this.isAsyncFunction()
+  return this.type.keyword === "var" ||
+    this.type.keyword === "const" ||
+    this.type.keyword === "class" ||
+    this.type.keyword === "function" ||
+    this.isLet() ||
+    this.isAsyncFunction()
 }
 
 // Parses a comma-separated list of module exports.
@@ -708,7 +729,7 @@ pp.parseImportSpecifiers = function() {
     // import defaultObj, { x, y as z } from '...'
     let node = this.startNode()
     node.local = this.parseIdent()
-    this.checkLVal(node.local, true)
+    this.checkLVal(node.local, "let")
     nodes.push(this.finishNode(node, "ImportDefaultSpecifier"))
     if (!this.eat(tt.comma)) return nodes
   }
@@ -717,7 +738,7 @@ pp.parseImportSpecifiers = function() {
     this.next()
     this.expectContextual("as")
     node.local = this.parseIdent()
-    this.checkLVal(node.local, true)
+    this.checkLVal(node.local, "let")
     nodes.push(this.finishNode(node, "ImportNamespaceSpecifier"))
     return nodes
   }
@@ -737,7 +758,7 @@ pp.parseImportSpecifiers = function() {
       if (this.isKeyword(node.local.name)) this.unexpected(node.local.start)
       if (this.reservedWordsStrict.test(node.local.name)) this.raiseRecoverable(node.local.start, "The keyword '" + node.local.name + "' is reserved")
     }
-    this.checkLVal(node.local, true)
+    this.checkLVal(node.local, "let")
     nodes.push(this.finishNode(node, "ImportSpecifier"))
   }
   return nodes
diff --git a/src/tokencontext.js b/src/tokencontext.js
index 911a515..33e94f4 100644
--- a/src/tokencontext.js
+++ b/src/tokencontext.js
@@ -7,11 +7,12 @@ import {types as tt} from "./tokentype"
 import {lineBreak} from "./whitespace"
 
 export class TokContext {
-  constructor(token, isExpr, preserveSpace, override) {
+  constructor(token, isExpr, preserveSpace, override, generator) {
     this.token = token
     this.isExpr = !!isExpr
     this.preserveSpace = !!preserveSpace
     this.override = override
+    this.generator = !!generator
   }
 }
 
@@ -22,7 +23,9 @@ export const types = {
   p_stat: new TokContext("(", false),
   p_expr: new TokContext("(", true),
   q_tmpl: new TokContext("`", true, true, p => p.readTmplToken()),
-  f_expr: new TokContext("function", true)
+  f_expr: new TokContext("function", true),
+  f_expr_gen: new TokContext("function", true, false, null, true),
+  f_gen: new TokContext("function", false, false, null, true)
 }
 
 const pp = Parser.prototype
@@ -39,13 +42,19 @@ pp.braceIsBlock = function(prevType) {
   }
   if (prevType === tt._return)
     return lineBreak.test(this.input.slice(this.lastTokEnd, this.start))
-  if (prevType === tt._else || prevType === tt.semi || prevType === tt.eof || prevType === tt.parenR)
+  if (prevType === tt._else || prevType === tt.semi || prevType === tt.eof || prevType === tt.parenR || prevType == tt.arrow)
     return true
   if (prevType == tt.braceL)
     return this.curContext() === types.b_stat
   return !this.exprAllowed
 }
 
+pp.inGeneratorContext = function() {
+  for (let i = this.context.length - 1; i >= 0; i--)
+    if (this.context[i].generator) return true
+  return false
+}
+
 pp.updateContext = function(prevType) {
   let update, type = this.type
   if (type.keyword && prevType == tt.dot)
@@ -63,8 +72,8 @@ tt.parenR.updateContext = tt.braceR.updateContext = function() {
     this.exprAllowed = true
     return
   }
-  let out = this.context.pop()
-  if (out === types.b_stat && this.curContext() === types.f_expr) {
+  let out = this.context.pop(), cur
+  if (out === types.b_stat && (cur = this.curContext()) && cur.token === "function") {
     this.context.pop()
     this.exprAllowed = false
   } else if (out === types.b_tmpl) {
@@ -108,3 +117,23 @@ tt.backQuote.updateContext = function() {
     this.context.push(types.q_tmpl)
   this.exprAllowed = false
 }
+
+tt.star.updateContext = function(prevType) {
+  if (prevType == tt._function) {
+    if (this.curContext() === types.f_expr)
+      this.context[this.context.length - 1] = types.f_expr_gen
+    else
+      this.context.push(types.f_gen)
+  }
+  this.exprAllowed = true
+}
+
+tt.name.updateContext = function(prevType) {
+  let allowed = false
+  if (this.options.ecmaVersion >= 6) {
+    if (this.value == "of" && !this.exprAllowed ||
+        this.value == "yield" && this.inGeneratorContext())
+      allowed = true
+  }
+  this.exprAllowed = allowed
+}
diff --git a/src/tokenize.js b/src/tokenize.js
index 9a34ba7..917a013 100644
--- a/src/tokenize.js
+++ b/src/tokenize.js
@@ -48,15 +48,16 @@ pp.getToken = function() {
 
 // If we're in an ES6 environment, make parsers iterable
 if (typeof Symbol !== "undefined")
-  pp[Symbol.iterator] = function () {
-    let self = this
-    return {next: function () {
-      let token = self.getToken()
-      return {
-        done: token.type === tt.eof,
-        value: token
+  pp[Symbol.iterator] = function() {
+    return {
+      next: () => {
+        let token = this.getToken()
+        return {
+          done: token.type === tt.eof,
+          value: token
+        }
       }
-    }}
+    }
   }
 
 // Toggle strict mode. Re-reads the next number or string to please
@@ -118,7 +119,7 @@ pp.skipBlockComment = function() {
 pp.skipLineComment = function(startSkip) {
   let start = this.pos
   let startLoc = this.options.onComment && this.curPosition()
-  let ch = this.input.charCodeAt(this.pos+=startSkip)
+  let ch = this.input.charCodeAt(this.pos += startSkip)
   while (this.pos < this.input.length && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) {
     ++this.pos
     ch = this.input.charCodeAt(this.pos)
@@ -135,38 +136,38 @@ pp.skipSpace = function() {
   loop: while (this.pos < this.input.length) {
     let ch = this.input.charCodeAt(this.pos)
     switch (ch) {
-      case 32: case 160: // ' '
-        ++this.pos
-        break
-      case 13:
-        if (this.input.charCodeAt(this.pos + 1) === 10) {
-          ++this.pos
-        }
-      case 10: case 8232: case 8233:
+    case 32: case 160: // ' '
+      ++this.pos
+      break
+    case 13:
+      if (this.input.charCodeAt(this.pos + 1) === 10) {
         ++this.pos
-        if (this.options.locations) {
-          ++this.curLine
-          this.lineStart = this.pos
-        }
+      }
+    case 10: case 8232: case 8233:
+      ++this.pos
+      if (this.options.locations) {
+        ++this.curLine
+        this.lineStart = this.pos
+      }
+      break
+    case 47: // '/'
+      switch (this.input.charCodeAt(this.pos + 1)) {
+      case 42: // '*'
+        this.skipBlockComment()
         break
-      case 47: // '/'
-        switch (this.input.charCodeAt(this.pos + 1)) {
-          case 42: // '*'
-            this.skipBlockComment()
-            break
-          case 47:
-            this.skipLineComment(2)
-            break
-          default:
-            break loop
-        }
+      case 47:
+        this.skipLineComment(2)
         break
       default:
-        if (ch > 8 && ch < 14 || ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
-          ++this.pos
-        } else {
-          break loop
-        }
+        break loop
+      }
+      break
+    default:
+      if (ch > 8 && ch < 14 || ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
+        ++this.pos
+      } else {
+        break loop
+      }
     }
   }
 }
@@ -210,7 +211,7 @@ pp.readToken_dot = function() {
 
 pp.readToken_slash = function() { // '/'
   let next = this.input.charCodeAt(this.pos + 1)
-  if (this.exprAllowed) {++this.pos; return this.readRegexp()}
+  if (this.exprAllowed) { ++this.pos; return this.readRegexp() }
   if (next === 61) return this.finishOp(tt.assign, 2)
   return this.finishOp(tt.slash, 1)
 }
@@ -383,7 +384,7 @@ function tryCreateRegexp(src, flags, throwErrorAt, parser) {
   }
 }
 
-var regexpUnicodeSupport = !!tryCreateRegexp("\uffff", "u")
+const regexpUnicodeSupport = !!tryCreateRegexp("\uffff", "u")
 
 pp.readRegexp = function() {
   let escaped, inClass, start = this.pos
@@ -510,7 +511,7 @@ pp.readCodePoint = function() {
   if (ch === 123) {
     if (this.options.ecmaVersion < 6) this.unexpected()
     let codePos = ++this.pos
-    code = this.readHexChar(this.input.indexOf('}', this.pos) - this.pos)
+    code = this.readHexChar(this.input.indexOf("}", this.pos) - this.pos)
     ++this.pos
     if (code > 0x10FFFF) this.raise(codePos, "Code point out of bounds")
   } else {
@@ -573,14 +574,14 @@ pp.readTmplToken = function() {
       out += this.input.slice(chunkStart, this.pos)
       ++this.pos
       switch (ch) {
-        case 13:
-          if (this.input.charCodeAt(this.pos) === 10) ++this.pos
-        case 10:
-          out += "\n"
-          break
-        default:
-          out += String.fromCharCode(ch)
-          break
+      case 13:
+        if (this.input.charCodeAt(this.pos) === 10) ++this.pos
+      case 10:
+        out += "\n"
+        break
+      default:
+        out += String.fromCharCode(ch)
+        break
       }
       if (this.options.locations) {
         ++this.curLine
diff --git a/src/util.js b/src/util.js
index 3517f8d..9f548a1 100644
--- a/src/util.js
+++ b/src/util.js
@@ -1,9 +1,11 @@
-export function isArray(obj) {
-  return Object.prototype.toString.call(obj) === "[object Array]"
-}
+const {hasOwnProperty, toString} = Object.prototype
 
 // Checks if an object has a property.
 
 export function has(obj, propName) {
-  return Object.prototype.hasOwnProperty.call(obj, propName)
+  return hasOwnProperty.call(obj, propName)
 }
+
+export const isArray = Array.isArray || ((obj) => (
+  toString.call(obj) === "[object Array]"
+))
diff --git a/src/walk/index.js b/src/walk/index.js
index 0da7e0d..e1c6ad4 100644
--- a/src/walk/index.js
+++ b/src/walk/index.js
@@ -73,7 +73,7 @@ export function findNodeAt(node, start, end, test, base, state) {
   test = makeTest(test)
   if (!base) base = exports.base
   try {
-    ;(function c(node, st, override) {
+    (function c(node, st, override) {
       let type = override || node.type
       if ((start == null || node.start <= start) &&
           (end == null || node.end >= end))
@@ -95,7 +95,7 @@ export function findNodeAround(node, pos, test, base, state) {
   test = makeTest(test)
   if (!base) base = exports.base
   try {
-    ;(function c(node, st, override) {
+    (function c(node, st, override) {
       let type = override || node.type
       if (node.start > pos || node.end < pos) return
       base[type](node, st, c)
@@ -112,7 +112,7 @@ export function findNodeAfter(node, pos, test, base, state) {
   test = makeTest(test)
   if (!base) base = exports.base
   try {
-    ;(function c(node, st, override) {
+    (function c(node, st, override) {
       if (node.end < pos) return
       let type = override || node.type
       if (node.start >= pos && test(type, node)) throw new Found(node, st)
@@ -151,7 +151,7 @@ const create = Object.create || function(proto) {
 export function make(funcs, base) {
   if (!base) base = exports.base
   let visitor = create(base)
-  for (var type in funcs) visitor[type] = funcs[type]
+  for (let type in funcs) visitor[type] = funcs[type]
   return visitor
 }
 
@@ -257,7 +257,7 @@ base.Pattern = (node, st, c) => {
 base.VariablePattern = ignore
 base.MemberPattern = skipThrough
 base.RestElement = (node, st, c) => c(node.argument, st, "Pattern")
-base.ArrayPattern =  (node, st, c) => {
+base.ArrayPattern = (node, st, c) => {
   for (let i = 0; i < node.elements.length; ++i) {
     let elt = node.elements[i]
     if (elt) c(elt, st, "Pattern")
diff --git a/test/lint.js b/test/lint.js
new file mode 100644
index 0000000..d1212e2
--- /dev/null
+++ b/test/lint.js
@@ -0,0 +1,10 @@
+if (parseInt(process.versions.node) > 4) {
+  console.log("Linting...")
+  try {
+    require("child_process").execFileSync("node_modules/.bin/eslint", ["src/"],
+                                          {cwd: __dirname + "/..", encoding: "utf8", stdio: "inherit"})
+    console.log("OK")
+  } catch(_) {
+    process.exit(1)
+  }
+}
diff --git a/test/tests-asyncawait.js b/test/tests-asyncawait.js
index 53c72a1..9250b76 100644
--- a/test/tests-asyncawait.js
+++ b/test/tests-asyncawait.js
@@ -161,7 +161,7 @@ test("export default async function() { }", {
             "start": 0,
             "end": 35,
             "declaration": {
-                "type": "FunctionExpression",
+                "type": "FunctionDeclaration",
                 "start": 15,
                 "end": 35,
                 "id": null,
diff --git a/test/tests-harmony.js b/test/tests-harmony.js
index e11a910..da03849 100644
--- a/test/tests-harmony.js
+++ b/test/tests-harmony.js
@@ -4155,7 +4155,7 @@ test("export default function () {}", {
     type: "ExportDefaultDeclaration",
     range: [0, 29],
     declaration: {
-      type: "FunctionExpression",
+      type: "FunctionDeclaration",
       range: [15, 29],
       id: null,
       generator: false,
@@ -4203,7 +4203,7 @@ test("export default class {}", {
     type: "ExportDefaultDeclaration",
     range: [0, 23],
     declaration: {
-      type: "ClassExpression",
+      type: "ClassDeclaration",
       range: [15, 23],
       id: null,
       superClass: null,
@@ -14464,7 +14464,7 @@ testFail("/[a-z]/s", "Invalid regular expression flag (1:1)", {ecmaVersion: 6});
 
 testFail("[...x in y] = []", "Assigning to rvalue (1:4)", {ecmaVersion: 6});
 
-testFail("export let x = a; export function x() {}", "Duplicate export 'x' (1:34)", {ecmaVersion: 6, sourceType: "module"})
+testFail("export let x = a; export function x() {}", "Identifier 'x' has already been declared (1:34)", {ecmaVersion: 6, sourceType: "module"})
 testFail("export let [{x = 2}] = a; export {x}", "Duplicate export 'x' (1:34)", {ecmaVersion: 6, sourceType: "module"})
 testFail("export default 100; export default 3", "Duplicate export 'default' (1:27)", {ecmaVersion: 6, sourceType: "module"})
 
@@ -15557,3 +15557,119 @@ testFail("({x, y}) = {}", "Parenthesized pattern (1:0)", {ecmaVersion: 6})
 test("[x, (y), {z, u: (v)}] = foo", {}, {ecmaVersion: 6})
 
 test("export default function(x) {};", {body: [{}, {}]}, {ecmaVersion: 6, sourceType: "module"})
+
+testFail("var foo = 1; let foo = 1;", "Identifier 'foo' has already been declared (1:17)", {ecmaVersion: 6})
+
+testFail("{ var foo = 1; let foo = 1; }", "Identifier 'foo' has already been declared (1:19)", {ecmaVersion: 6})
+
+testFail("let foo = 1; var foo = 1;", "Identifier 'foo' has already been declared (1:17)", {ecmaVersion: 6})
+
+testFail("let foo = 1; let foo = 1;", "Identifier 'foo' has already been declared (1:17)", {ecmaVersion: 6})
+
+testFail("var foo = 1; const foo = 1;", "Identifier 'foo' has already been declared (1:19)", {ecmaVersion: 6})
+
+testFail("const foo = 1; var foo = 1;", "Identifier 'foo' has already been declared (1:19)", {ecmaVersion: 6})
+
+testFail("var [foo] = [1]; let foo = 1;", "Identifier 'foo' has already been declared (1:21)", {ecmaVersion: 6})
+
+testFail("var [{ bar: [foo] }] = x; let {foo} = 1;", "Identifier 'foo' has already been declared (1:31)", {ecmaVersion: 6})
+
+testFail("if (x) var foo = 1; let foo = 1;", "Identifier 'foo' has already been declared (1:24)", {ecmaVersion: 6})
+
+testFail("if (x) {} else var foo = 1; let foo = 1;", "Identifier 'foo' has already been declared (1:32)", {ecmaVersion: 6})
+
+testFail("if (x) var foo = 1; else {} let foo = 1;", "Identifier 'foo' has already been declared (1:32)", {ecmaVersion: 6})
+
+testFail("if (x) {} else if (y) {} else var foo = 1; let foo = 1;", "Identifier 'foo' has already been declared (1:47)", {ecmaVersion: 6})
+
+testFail("while (x) var foo = 1; let foo = 1;", "Identifier 'foo' has already been declared (1:27)", {ecmaVersion: 6})
+
+testFail("do var foo = 1; while (x) let foo = 1;", "Identifier 'foo' has already been declared (1:30)", {ecmaVersion: 6})
+
+testFail("for (;;) var foo = 1; let foo = 1;", "Identifier 'foo' has already been declared (1:26)", {ecmaVersion: 6})
+
+testFail("for (const x of y) var foo = 1; let foo = 1;", "Identifier 'foo' has already been declared (1:36)", {ecmaVersion: 6})
+
+testFail("for (const x in y) var foo = 1; let foo = 1;", "Identifier 'foo' has already been declared (1:36)", {ecmaVersion: 6})
+
+testFail("label: var foo = 1; let foo = 1;", "Identifier 'foo' has already been declared (1:24)", {ecmaVersion: 6})
+
+testFail("switch (x) { case 0: var foo = 1 } let foo = 1;", "Identifier 'foo' has already been declared (1:39)", {ecmaVersion: 6})
+
+testFail("try { var foo = 1; } catch (e) {} let foo = 1;", "Identifier 'foo' has already been declared (1:38)", {ecmaVersion: 6})
+
+testFail("function foo() {} let foo = 1;", "Identifier 'foo' has already been declared (1:22)", {ecmaVersion: 6})
+
+testFail("{ var foo = 1; } let foo = 1;", "Identifier 'foo' has already been declared (1:21)", {ecmaVersion: 6})
+
+testFail("let foo = 1; { var foo = 1; }", "Identifier 'foo' has already been declared (1:19)", {ecmaVersion: 6})
+
+testFail("let foo = 1; function x(foo) {} { var foo = 1; }", "Identifier 'foo' has already been declared (1:38)", {ecmaVersion: 6})
+
+testFail("if (x) { if (y) var foo = 1; } let foo = 1;", "Identifier 'foo' has already been declared (1:35)", {ecmaVersion: 6})
+
+testFail("var foo = 1; function x() {} let foo = 1;", "Identifier 'foo' has already been declared (1:33)", {ecmaVersion: 6})
+
+testFail("{ let foo = 1; { let foo = 2; } let foo = 1; }", "Identifier 'foo' has already been declared (1:36)", {ecmaVersion: 6})
+
+testFail("for (var foo of y) {} let foo = 1;", "Identifier 'foo' has already been declared (1:26)", {ecmaVersion: 6})
+
+testFail("function x(foo) { let foo = 1; }", "Identifier 'foo' has already been declared (1:22)", {ecmaVersion: 6})
+
+testFail("var [...foo] = x; let foo = 1;", "Identifier 'foo' has already been declared (1:22)", {ecmaVersion: 6})
+
+testFail("foo => { let foo; }", "Identifier 'foo' has already been declared (1:13)", {ecmaVersion: 6})
+
+testFail("({ x(foo) { let foo; } })", "Identifier 'foo' has already been declared (1:16)", {ecmaVersion: 6})
+
+testFail("try {} catch (foo) { let foo = 1; }", "Identifier 'foo' has already been declared (1:25)", {ecmaVersion: 6})
+
+test("var foo = 1; var foo = 1;", {}, {ecmaVersion: 6})
+
+test("if (x) var foo = 1; var foo = 1;", {}, {ecmaVersion: 6})
+
+test("function x() { var foo = 1; } let foo = 1;", {}, {ecmaVersion: 6})
+
+test("function foo() { let foo = 1; }", {}, {ecmaVersion: 6})
+
+test("var foo = 1; { let foo = 1; }", {}, {ecmaVersion: 6})
+
+test("{ let foo = 1; { let foo = 2; } }", {}, {ecmaVersion: 6})
+
+test("var foo; try {} catch (_) { let foo; }", {}, {ecmaVersion: 6})
+
+test("let x = 1; function foo(x) {}", {}, {ecmaVersion: 6})
+
+test("for (let i = 0;;); for (let i = 0;;);", {}, {ecmaVersion: 6})
+
+test("for (const foo of bar); for (const foo of bar);", {}, {ecmaVersion: 6})
+
+test("for (const foo in bar); for (const foo in bar);", {}, {ecmaVersion: 6})
+
+test("for (let foo in bar) { let foo = 1; }", {}, {ecmaVersion: 6})
+
+test("for (let foo of bar) { let foo = 1; }", {}, {ecmaVersion: 6})
+
+test("class Foo { method(foo) {} method2() { let foo; } }", {}, {ecmaVersion: 6})
+
+test("() => { let foo; }; foo => {}", {}, {ecmaVersion: 6})
+
+test("() => { let foo; }; () => { let foo; }", {}, {ecmaVersion: 6})
+
+test("switch(x) { case 1: let foo = 1; } let foo = 1;", {}, {ecmaVersion: 6})
+
+test("'use strict'; function foo() { let foo = 1; }", {}, {ecmaVersion: 6})
+
+test("let foo = 1; function x() { var foo = 1; }", {}, {ecmaVersion: 6})
+
+test("[...foo, bar = 1]", {}, {ecmaVersion: 6})
+
+test("for (var a of /b/) {}", {}, {ecmaVersion: 6})
+
+test("function* bar() { yield /re/ }", {}, {ecmaVersion: 6})
+
+test("() => {}\n/re/", {}, {ecmaVersion: 6})
+
+test("(() => {}) + 2", {}, {ecmaVersion: 6})
+
+testFail("(x) => {} + 2", "Unexpected token (1:10)", {ecmaVersion: 6})
diff --git a/test/tests.js b/test/tests.js
index a10aeaa..2a06a56 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -29183,3 +29183,21 @@ test("for ((foo = []).bar in {}) {}", {})
 test("((b), a=1)", {})
 
 test("(x) = 1", {})
+
+testFail("try {} catch (foo) { var foo; }", "Identifier 'foo' has already been declared (1:25)")
+testFail("try {} catch (foo) { let foo; }", "Identifier 'foo' has already been declared (1:25)", {ecmaVersion: 6})
+testFail("try {} catch (foo) { try {} catch (_) { var foo; } }", "Identifier 'foo' has already been declared (1:44)")
+testFail("try {} catch ([foo]) { var foo; }", "Identifier 'foo' has already been declared (1:27)", {ecmaVersion: 6})
+testFail("try {} catch ({ foo }) { var foo; }", "Identifier 'foo' has already been declared (1:29)", {ecmaVersion: 6})
+testFail("try {} catch ([foo, foo]) {}", "Identifier 'foo' has already been declared (1:20)", {ecmaVersion: 6})
+testFail("try {} catch ({ a: foo, b: { c: [foo] } }) {}", "Identifier 'foo' has already been declared (1:33)", {ecmaVersion: 6})
+testFail("let foo; try {} catch (foo) {} let foo;", "Identifier 'foo' has already been declared (1:35)", {ecmaVersion: 6})
+testFail("try {} catch (foo) { function foo() {} }", "Identifier 'foo' has already been declared (1:30)")
+
+test("try {} catch (foo) {} var foo;", {})
+test("try {} catch (foo) {} let foo;", {}, {ecmaVersion: 6})
+test("try {} catch (foo) { { let foo; } }", {}, {ecmaVersion: 6})
+test("try {} catch (foo) { function x() { var foo; } }", {}, {ecmaVersion: 6})
+test("try {} catch (foo) { function x(foo) {} }", {}, {ecmaVersion: 6})
+
+test("'use strict'; let foo = function foo() {}", {}, {ecmaVersion: 6})

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/acorn.git



More information about the Pkg-javascript-commits mailing list