Bug 104442 - Part 1: Report the position and the kind of previous declaration for redeclaration error. r=anba
authorTooru Fujisawa <arai_a@mac.com>
Sat, 25 Feb 2017 12:52:33 +0900
changeset 489642 effa71064b2918dbab3f63716419e84264588f7b
parent 489641 c618b06686e986428a0aa186081e32f74d75ace0
child 489643 175610403a4ebb73d3e99ef36762fc4e8d6f8efd
push id46871
push userbmo:sledru@mozilla.com
push dateSat, 25 Feb 2017 12:16:21 +0000
reviewersanba
bugs104442
milestone54.0a1
Bug 104442 - Part 1: Report the position and the kind of previous declaration for redeclaration error. r=anba
js/src/frontend/NameAnalysisTypes.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/jit-test/tests/parser/redeclaration.js
js/src/js.msg
--- a/js/src/frontend/NameAnalysisTypes.h
+++ b/js/src/frontend/NameAnalysisTypes.h
@@ -119,36 +119,44 @@ static inline bool
 DeclarationKindIsLexical(DeclarationKind kind)
 {
     return BindingKindIsLexical(DeclarationKindToBindingKind(kind));
 }
 
 // Used in Parser to track declared names.
 class DeclaredNameInfo
 {
+    uint32_t pos_;
     DeclarationKind kind_;
 
     // If the declared name is a binding, whether the binding is closed
     // over. Its value is meaningless if the declared name is not a binding
     // (i.e., a 'var' declared name in a non-var scope).
     bool closedOver_;
 
   public:
-    explicit DeclaredNameInfo(DeclarationKind kind)
-      : kind_(kind),
+    explicit DeclaredNameInfo(DeclarationKind kind, uint32_t pos)
+      : pos_(pos),
+        kind_(kind),
         closedOver_(false)
     { }
 
     // Needed for InlineMap.
     DeclaredNameInfo() = default;
 
     DeclarationKind kind() const {
         return kind_;
     }
 
+    static const uint32_t npos = uint32_t(-1);
+
+    uint32_t pos() const {
+        return pos_;
+    }
+
     void alterKind(DeclarationKind kind) {
         kind_ = kind;
     }
 
     void setClosedOver() {
         closedOver_ = true;
     }
 
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -14,16 +14,18 @@
  * syntax trees, see Parser.h).  After tree construction, it rewrites trees to
  * fold constants and evaluate compile-time expressions.
  *
  * This parser attempts no error recovery.
  */
 
 #include "frontend/Parser.h"
 
+#include "mozilla/Sprintf.h"
+
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jscntxt.h"
 #include "jsfun.h"
 #include "jsopcode.h"
 #include "jsscript.h"
 #include "jstypes.h"
 
@@ -193,21 +195,22 @@ DeclarationKindIsCatchParameter(Declarat
 bool
 ParseContext::Scope::addCatchParameters(ParseContext* pc, Scope& catchParamScope)
 {
     if (pc->useAsmOrInsideUseAsm())
         return true;
 
     for (DeclaredNameMap::Range r = catchParamScope.declared_->all(); !r.empty(); r.popFront()) {
         DeclarationKind kind = r.front().value()->kind();
+        uint32_t pos = r.front().value()->pos();
         MOZ_ASSERT(DeclarationKindIsCatchParameter(kind));
         JSAtom* name = r.front().key();
         AddDeclaredNamePtr p = lookupDeclaredNameForAdd(name);
         MOZ_ASSERT(!p);
-        if (!addDeclaredName(pc, p, name, kind))
+        if (!addDeclaredName(pc, p, name, kind, pos))
             return false;
     }
 
     return true;
 }
 
 void
 ParseContext::Scope::removeCatchParameters(ParseContext* pc, Scope& catchParamScope)
@@ -336,17 +339,18 @@ ParseContext::init()
         RootedFunction fun(cx, functionBox()->function());
         if (fun->isNamedLambda()) {
             if (!namedLambdaScope_->init(this))
                 return false;
             AddDeclaredNamePtr p =
                 namedLambdaScope_->lookupDeclaredNameForAdd(fun->explicitName());
             MOZ_ASSERT(!p);
             if (!namedLambdaScope_->addDeclaredName(this, p, fun->explicitName(),
-                                                    DeclarationKind::Const))
+                                                    DeclarationKind::Const,
+                                                    DeclaredNameInfo::npos))
             {
                 return false;
             }
         }
 
         if (!functionScope_->init(this))
             return false;
 
@@ -989,37 +993,67 @@ Parser<ParseHandler>::hasValidSimpleStri
         if (!isValidStrictBinding(name->asPropertyName()))
             return false;
     }
     return true;
 }
 
 template <typename ParseHandler>
 void
-Parser<ParseHandler>::reportRedeclaration(HandlePropertyName name, DeclarationKind kind,
-                                          TokenPos pos)
+Parser<ParseHandler>::reportRedeclaration(HandlePropertyName name, DeclarationKind prevKind,
+                                          TokenPos pos, uint32_t prevPos)
 {
     JSAutoByteString bytes;
     if (!AtomToPrintableString(context, name, &bytes))
         return;
-    errorAt(pos.begin, JSMSG_REDECLARED_VAR, DeclarationKindString(kind), bytes.ptr());
+
+    if (prevPos == DeclaredNameInfo::npos) {
+        errorAt(pos.begin, JSMSG_REDECLARED_VAR, DeclarationKindString(prevKind), bytes.ptr());
+        return;
+    }
+
+    auto notes = MakeUnique<JSErrorNotes>();
+    if (!notes)
+        return;
+
+    uint32_t line, column;
+    tokenStream.srcCoords.lineNumAndColumnIndex(prevPos, &line, &column);
+
+    const size_t MaxWidth = sizeof("4294967295");
+    char columnNumber[MaxWidth];
+    SprintfLiteral(columnNumber, "%" PRIu32, column);
+    char lineNumber[MaxWidth];
+    SprintfLiteral(lineNumber, "%" PRIu32, line);
+
+    if (!notes->addNoteLatin1(pc->sc()->context,
+                              getFilename(), line, column,
+                              GetErrorMessage, nullptr,
+                              JSMSG_REDECLARED_PREV,
+                              lineNumber, columnNumber))
+    {
+        return;
+    }
+
+    errorWithNotesAt(Move(notes), pos.begin, JSMSG_REDECLARED_VAR,
+                     DeclarationKindString(prevKind), bytes.ptr());
 }
 
 // notePositionalFormalParameter is called for both the arguments of a regular
 // function definition and the arguments specified by the Function
 // constructor.
 //
 // The 'disallowDuplicateParams' bool indicates whether the use of another
 // feature (destructuring or default arguments) disables duplicate arguments.
 // (ECMA-262 requires us to support duplicate parameter names, but, for newer
 // features, we consider the code to have "opted in" to higher standards and
 // forbid duplicates.)
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::notePositionalFormalParameter(Node fn, HandlePropertyName name,
+                                                    uint32_t beginPos,
                                                     bool disallowDuplicateParams,
                                                     bool* duplicatedParam)
 {
     AutoTraceLog traceLog(TraceLoggerForCurrentThread(context), TraceLogger_FrontendNameAnalysis);
 
     if (AddDeclaredNamePtr p = pc->functionScope().lookupDeclaredNameForAdd(name)) {
         if (disallowDuplicateParams) {
             error(JSMSG_BAD_DUP_ARGS);
@@ -1036,17 +1070,17 @@ Parser<ParseHandler>::notePositionalForm
                 return false;
             if (!strictModeError(JSMSG_DUPLICATE_FORMAL, bytes.ptr()))
                 return false;
         }
 
         *duplicatedParam = true;
     } else {
         DeclarationKind kind = DeclarationKind::PositionalFormalParameter;
-        if (!pc->functionScope().addDeclaredName(pc, p, name, kind))
+        if (!pc->functionScope().addDeclaredName(pc, p, name, kind, beginPos))
             return false;
     }
 
     if (!pc->positionalFormalParameterNames().append(name)) {
         ReportOutOfMemory(context);
         return false;
     }
 
@@ -1140,18 +1174,18 @@ static bool
 DeclarationKindIsParameter(DeclarationKind kind)
 {
     return kind == DeclarationKind::PositionalFormalParameter ||
            kind == DeclarationKind::FormalParameter;
 }
 
 template <typename ParseHandler>
 bool
-Parser<ParseHandler>::tryDeclareVar(HandlePropertyName name, DeclarationKind kind,
-                                    Maybe<DeclarationKind>* redeclaredKind)
+Parser<ParseHandler>::tryDeclareVar(HandlePropertyName name, DeclarationKind kind, uint32_t beginPos,
+                                    Maybe<DeclarationKind>* redeclaredKind, uint32_t* prevPos)
 {
     MOZ_ASSERT(DeclarationKindIsVar(kind));
 
     // It is an early error if a 'var' declaration appears inside a
     // scope contour that has a lexical declaration of the same name. For
     // example, the following are early errors:
     //
     //   { let x; var x; }
@@ -1214,47 +1248,57 @@ Parser<ParseHandler>::tryDeclareVar(Hand
 
                 // Annex B.3.3 allows redeclaring functions in the same block.
                 bool annexB33Allowance = declaredKind == DeclarationKind::LexicalFunction &&
                                          kind == DeclarationKind::VarForAnnexBLexicalFunction &&
                                          scope == pc->innermostScope();
 
                 if (!annexB35Allowance && !annexB33Allowance) {
                     *redeclaredKind = Some(declaredKind);
+                    *prevPos = p->value()->pos();
                     return true;
                 }
             } else if (kind == DeclarationKind::VarForAnnexBLexicalFunction) {
                 MOZ_ASSERT(DeclarationKindIsParameter(declaredKind));
 
                 // Annex B.3.3.1 disallows redeclaring parameter names.
+                // We don't need to set *prevPos here since this case is not
+                // an error.
                 *redeclaredKind = Some(declaredKind);
                 return true;
             }
         } else {
-            if (!scope->addDeclaredName(pc, p, name, kind))
+            if (!scope->addDeclaredName(pc, p, name, kind, beginPos))
                 return false;
         }
     }
 
-    if (!pc->sc()->strict() && pc->sc()->isEvalContext())
+    if (!pc->sc()->strict() && pc->sc()->isEvalContext()) {
         *redeclaredKind = isVarRedeclaredInEval(name, kind);
+        // We don't have position information at runtime.
+        *prevPos = DeclaredNameInfo::npos;
+    }
 
     return true;
 }
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::tryDeclareVarForAnnexBLexicalFunction(HandlePropertyName name,
-                                                            bool* tryAnnexB)
+                                                            uint32_t beginPos, bool* tryAnnexB)
 {
     AutoTraceLog traceLog(TraceLoggerForCurrentThread(context), TraceLogger_FrontendNameAnalysis);
 
     Maybe<DeclarationKind> redeclaredKind;
-    if (!tryDeclareVar(name, DeclarationKind::VarForAnnexBLexicalFunction, &redeclaredKind))
+    uint32_t unused;
+    if (!tryDeclareVar(name, DeclarationKind::VarForAnnexBLexicalFunction, beginPos,
+                       &redeclaredKind, &unused))
+    {
         return false;
+    }
 
     if (!redeclaredKind && pc->isFunctionBox()) {
         ParseContext::Scope& funScope = pc->functionScope();
         ParseContext::Scope& varScope = pc->varScope();
         if (&funScope != &varScope) {
             // Annex B.3.3.1 disallows redeclaring parameter names. In the
             // presence of parameter expressions, parameter names are on the
             // function scope, which encloses the var scope. This means
@@ -1318,38 +1362,39 @@ Parser<ParseHandler>::noteDeclaredName(H
     if (pc->useAsmOrInsideUseAsm())
         return true;
 
     switch (kind) {
       case DeclarationKind::Var:
       case DeclarationKind::BodyLevelFunction:
       case DeclarationKind::ForOfVar: {
         Maybe<DeclarationKind> redeclaredKind;
-        if (!tryDeclareVar(name, kind, &redeclaredKind))
+        uint32_t prevPos;
+        if (!tryDeclareVar(name, kind, pos.begin, &redeclaredKind, &prevPos))
             return false;
 
         if (redeclaredKind) {
-            reportRedeclaration(name, *redeclaredKind, pos);
+            reportRedeclaration(name, *redeclaredKind, pos, prevPos);
             return false;
         }
 
         break;
       }
 
       case DeclarationKind::FormalParameter: {
         // It is an early error if any non-positional formal parameter name
         // (e.g., destructuring formal parameter) is duplicated.
 
         AddDeclaredNamePtr p = pc->functionScope().lookupDeclaredNameForAdd(name);
         if (p) {
             error(JSMSG_BAD_DUP_ARGS);
             return false;
         }
 
-        if (!pc->functionScope().addDeclaredName(pc, p, name, kind))
+        if (!pc->functionScope().addDeclaredName(pc, p, name, kind, pos.begin))
             return false;
 
         break;
       }
 
       case DeclarationKind::LexicalFunction: {
         // Functions in block have complex allowances in sloppy mode for being
         // labelled that other lexical declarations do not have. Those checks
@@ -1362,25 +1407,25 @@ Parser<ParseHandler>::noteDeclaredName(H
             // with the same name in the same scope.
             //
             // In sloppy mode, lexical functions may redeclare other lexical
             // functions for web compatibility reasons.
             if (pc->sc()->strict() ||
                 (p->value()->kind() != DeclarationKind::LexicalFunction &&
                  p->value()->kind() != DeclarationKind::VarForAnnexBLexicalFunction))
             {
-                reportRedeclaration(name, p->value()->kind(), pos);
+                reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
                 return false;
             }
 
             // Update the DeclarationKind to make a LexicalFunction
             // declaration that shadows the VarForAnnexBLexicalFunction.
             p->value()->alterKind(kind);
         } else {
-            if (!scope->addDeclaredName(pc, p, name, kind))
+            if (!scope->addDeclaredName(pc, p, name, kind, pos.begin))
                 return false;
         }
 
         break;
       }
 
       case DeclarationKind::Let:
       case DeclarationKind::Const:
@@ -1410,39 +1455,39 @@ Parser<ParseHandler>::noteDeclaredName(H
 
         // For body-level lexically declared names in a function, it is an
         // early error if there is a formal parameter of the same name. This
         // needs a special check if there is an extra var scope due to
         // parameter expressions.
         if (pc->isFunctionExtraBodyVarScopeInnermost()) {
             DeclaredNamePtr p = pc->functionScope().lookupDeclaredName(name);
             if (p && DeclarationKindIsParameter(p->value()->kind())) {
-                reportRedeclaration(name, p->value()->kind(), pos);
+                reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
                 return false;
             }
         }
 
         // It is an early error if there is another declaration with the same
         // name in the same scope.
         AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name);
         if (p) {
             // If the early error would have occurred due to Annex B.3.3
             // semantics, remove the synthesized Annex B var declaration, do
             // not report the redeclaration, and declare the lexical name.
             if (p->value()->kind() == DeclarationKind::VarForAnnexBLexicalFunction) {
                 ParseContext::Scope::removeVarForAnnexBLexicalFunction(pc, name);
                 p = scope->lookupDeclaredNameForAdd(name);
                 MOZ_ASSERT(!p);
             } else {
-                reportRedeclaration(name, p->value()->kind(), pos);
+                reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
                 return false;
             }
         }
 
-        if (!p && !scope->addDeclaredName(pc, p, name, kind))
+        if (!p && !scope->addDeclaredName(pc, p, name, kind, pos.begin))
             return false;
 
         break;
       }
 
       case DeclarationKind::CoverArrowParameter:
         // CoverArrowParameter is only used as a placeholder declaration kind.
         break;
@@ -2182,18 +2227,21 @@ Parser<ParseHandler>::declareFunctionThi
         declareThis = funbox->function()->lazyScript()->hasThisBinding();
     else
         declareThis = hasUsedFunctionSpecialName(dotThis) || funbox->isDerivedClassConstructor();
 
     if (declareThis) {
         ParseContext::Scope& funScope = pc->functionScope();
         AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotThis);
         MOZ_ASSERT(!p);
-        if (!funScope.addDeclaredName(pc, p, dotThis, DeclarationKind::Var))
+        if (!funScope.addDeclaredName(pc, p, dotThis, DeclarationKind::Var,
+                                      DeclaredNameInfo::npos))
+        {
             return false;
+        }
         funbox->setHasThisBinding();
     }
 
     return true;
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
@@ -2225,18 +2273,21 @@ template <typename ParseHandler>
 bool
 Parser<ParseHandler>::declareDotGeneratorName()
 {
     // The special '.generator' binding must be on the function scope, as
     // generators expect to find it on the CallObject.
     ParseContext::Scope& funScope = pc->functionScope();
     HandlePropertyName dotGenerator = context->names().dotGenerator;
     AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotGenerator);
-    if (!p && !funScope.addDeclaredName(pc, p, dotGenerator, DeclarationKind::Var))
+    if (!p && !funScope.addDeclaredName(pc, p, dotGenerator, DeclarationKind::Var,
+                                        DeclaredNameInfo::npos))
+    {
         return false;
+    }
     return true;
 }
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::finishFunctionScopes(bool isStandaloneFunction)
 {
     FunctionBox* funbox = pc->functionBox();
@@ -2449,18 +2500,21 @@ Parser<ParseHandler>::declareFunctionArg
             tryDeclareArguments = true;
         else
             funbox->usesArguments = true;
     }
 
     if (tryDeclareArguments) {
         AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(argumentsName);
         if (!p) {
-            if (!funScope.addDeclaredName(pc, p, argumentsName, DeclarationKind::Var))
+            if (!funScope.addDeclaredName(pc, p, argumentsName, DeclarationKind::Var,
+                                          DeclaredNameInfo::npos))
+            {
                 return false;
+            }
             funbox->declaredArguments = true;
             funbox->usesArguments = true;
         } else if (hasExtraBodyVarScope) {
             // Formal parameters shadow the arguments object.
             return true;
         }
     }
 
@@ -2932,18 +2986,18 @@ Parser<ParseHandler>::functionArguments(
 
                 if (parenFreeArrow)
                     funbox->setStart(tokenStream);
 
                 RootedPropertyName name(context, bindingIdentifier(yieldHandling));
                 if (!name)
                     return false;
 
-                if (!notePositionalFormalParameter(funcpn, name, disallowDuplicateParams,
-                                                   &duplicatedParam))
+                if (!notePositionalFormalParameter(funcpn, name, pos().begin,
+                                                   disallowDuplicateParams, &duplicatedParam))
                 {
                     return false;
                 }
                 if (duplicatedParam)
                     funbox->hasDuplicateParameters = true;
 
                 break;
               }
@@ -3652,17 +3706,17 @@ Parser<ParseHandler>::functionStmt(Yield
         MOZ_ASSERT(StatementKindIsBraced(declaredInStmt->kind()));
 
         if (!pc->sc()->strict() && generatorKind == NotGenerator) {
             // Under sloppy mode, try Annex B.3.3 semantics. If making an
             // additional 'var' binding of the same name does not throw an
             // early error, do so. This 'var' binding would be assigned
             // the function object when its declaration is reached, not at
             // the start of the block.
-            if (!tryDeclareVarForAnnexBLexicalFunction(name, &tryAnnexB))
+            if (!tryDeclareVarForAnnexBLexicalFunction(name, pos().begin, &tryAnnexB))
                 return null();
         }
 
         if (!noteDeclaredName(name, DeclarationKind::LexicalFunction, pos()))
             return null();
     } else {
         if (!noteDeclaredName(name, DeclarationKind::BodyLevelFunction, pos()))
             return null();
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -142,19 +142,19 @@ class ParseContext : public Nestable<Par
             return declared_->lookup(name);
         }
 
         AddDeclaredNamePtr lookupDeclaredNameForAdd(JSAtom* name) {
             return declared_->lookupForAdd(name);
         }
 
         MOZ_MUST_USE bool addDeclaredName(ParseContext* pc, AddDeclaredNamePtr& p, JSAtom* name,
-                                          DeclarationKind kind)
+                                          DeclarationKind kind, uint32_t pos)
         {
-            return maybeReportOOM(pc, declared_->add(p, name, DeclaredNameInfo(kind)));
+            return maybeReportOOM(pc, declared_->add(p, name, DeclaredNameInfo(kind, pos)));
         }
 
         // Remove all VarForAnnexBLexicalFunction declarations of a certain
         // name from all scopes in pc's scope stack.
         static void removeVarForAnnexBLexicalFunction(ParseContext* pc, JSAtom* name);
 
         // Add and remove catch parameter names. Used to implement the odd
         // semantics of catch bodies.
@@ -1437,25 +1437,27 @@ class Parser final : public ParserBase, 
                                        FunctionCallBehavior behavior = ForbidAssignmentToFunctionCalls);
 
   private:
     bool checkIncDecOperand(Node operand, uint32_t operandOffset);
     bool checkStrictAssignment(Node lhs);
 
     bool hasValidSimpleStrictParameterNames();
 
-    void reportRedeclaration(HandlePropertyName name, DeclarationKind kind, TokenPos pos);
-    bool notePositionalFormalParameter(Node fn, HandlePropertyName name,
+    void reportRedeclaration(HandlePropertyName name, DeclarationKind prevKind, TokenPos pos,
+                             uint32_t prevPos);
+    bool notePositionalFormalParameter(Node fn, HandlePropertyName name, uint32_t beginPos,
                                        bool disallowDuplicateParams, bool* duplicatedParam);
     bool noteDestructuredPositionalFormalParameter(Node fn, Node destruct);
     mozilla::Maybe<DeclarationKind> isVarRedeclaredInEval(HandlePropertyName name,
                                                           DeclarationKind kind);
-    bool tryDeclareVar(HandlePropertyName name, DeclarationKind kind,
-                       mozilla::Maybe<DeclarationKind>* redeclaredKind);
-    bool tryDeclareVarForAnnexBLexicalFunction(HandlePropertyName name, bool* tryAnnexB);
+    bool tryDeclareVar(HandlePropertyName name, DeclarationKind kind, uint32_t beginPos,
+                       mozilla::Maybe<DeclarationKind>* redeclaredKind, uint32_t* prevPos);
+    bool tryDeclareVarForAnnexBLexicalFunction(HandlePropertyName name, uint32_t beginPos,
+                                               bool* tryAnnexB);
     bool checkLexicalDeclarationDirectlyWithinBlock(ParseContext::Statement& stmt,
                                                     DeclarationKind kind, TokenPos pos);
     bool noteDeclaredName(HandlePropertyName name, DeclarationKind kind, TokenPos pos);
     bool noteUsedName(HandlePropertyName name);
     bool hasUsedName(HandlePropertyName name);
 
     // Required on Scope exit.
     bool propagateFreeNamesAndMarkClosedOverBindings(ParseContext::Scope& scope);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parser/redeclaration.js
@@ -0,0 +1,230 @@
+// Error message for redeclaration should show the position where the variable
+// was declared.
+
+const npos = -1;
+
+function test_one(fun, filename, name,
+                  [prevLineNumber, prevColumnNumber],
+                  [lineNumber, columnNumber]) {
+    let caught = false;
+    try {
+        fun();
+    } catch (e) {
+        assertEq(e.message.includes("redeclaration"), true);
+        assertEq(e.lineNumber, lineNumber);
+        assertEq(e.columnNumber, columnNumber);
+        let notes = getErrorNotes(e);
+        if (prevLineNumber == npos) {
+            assertEq(notes.length, 0);
+        } else {
+            assertEq(notes.length, 1);
+            let note = notes[0];
+            assertEq(note.message,
+                     `Previously declared at line ${prevLineNumber}, column ${prevColumnNumber}`);
+            assertEq(note.lineNumber, prevLineNumber);
+            assertEq(note.columnNumber, prevColumnNumber);
+            if (filename)
+                assertEq(note.fileName, filename);
+        }
+        caught = true;
+    }
+    assertEq(caught, true);
+}
+
+function test_parse(source, ...args) {
+    test_one(() => {
+        Reflect.parse(source, { source: "foo.js" });
+    }, "foo.js", ...args);
+}
+
+function test_eval(source, ...args) {
+    test_one(() => {
+        eval(source);
+    }, undefined, ...args);
+}
+
+function test(...args) {
+    test_parse(...args);
+    test_eval(...args);
+}
+
+// let
+
+test(`
+let a, a;
+`, "a", [2, 4], [2, 7]);
+
+test(`
+let a;
+let a;
+`, "a", [2, 4], [3, 4]);
+
+test(`
+let a;
+const a = 1;
+`, "a", [2, 4], [3, 6]);
+
+test(`
+let a;
+var a;
+`, "a", [2, 4], [3, 4]);
+
+test(`
+let a;
+function a() {
+}
+`, "a", [2, 4], [3, 9]);
+
+test(`
+{
+  let a;
+  function a() {
+  }
+}
+`, "a", [3, 6], [4, 11]);
+
+// const
+
+test(`
+const a = 1, a = 2;
+`, "a", [2, 6], [2, 13]);
+
+test(`
+const a = 1;
+const a = 2;
+`, "a", [2, 6], [3, 6]);
+
+test(`
+const a = 1;
+let a;
+`, "a", [2, 6], [3, 4]);
+
+test(`
+const a = 1;
+var a;
+`, "a", [2, 6], [3, 4]);
+
+test(`
+const a = 1;
+function a() {
+}
+`, "a", [2, 6], [3, 9]);
+
+test(`
+{
+  const a = 1;
+  function a() {
+  }
+}
+`, "a", [3, 8], [4, 11]);
+
+// var
+
+test(`
+var a;
+let a;
+`, "a", [2, 4], [3, 4]);
+
+test(`
+var a;
+const a = 1;
+`, "a", [2, 4], [3, 6]);
+
+// function
+
+test(`
+function a() {};
+let a;
+`, "a", [2, 9], [3, 4]);
+
+test(`
+function a() {};
+const a = 1;
+`, "a", [2, 9], [3, 6]);
+
+// Annex B lexical function
+
+test(`
+{
+  function a() {};
+  let a;
+}
+`, "a", [3, 11], [4, 6]);
+
+test(`
+{
+  function a() {};
+  const a = 1;
+}
+`, "a", [3, 11], [4, 8]);
+
+// catch parameter
+
+test(`
+try {
+} catch (a) {
+  let a;
+}
+`, "a", [3, 9], [4, 6]);
+
+test(`
+try {
+} catch (a) {
+  const a = 1;
+}
+`, "a", [3, 9], [4, 8]);
+
+test(`
+try {
+} catch (a) {
+  function a() {
+  }
+}
+`, "a", [3, 9], [4, 11]);
+
+// parameter
+
+test(`
+function f(a) {
+  let a;
+}
+`, "a", [2, 11], [3, 6]);
+
+test(`
+function f(a) {
+  const a = 1;
+}
+`, "a", [2, 11], [3, 8]);
+
+test(`
+function f([a]) {
+  let a;
+}
+`, "a", [2, 12], [3, 6]);
+
+test(`
+function f({a}) {
+  let a;
+}
+`, "a", [2, 12], [3, 6]);
+
+test(`
+function f(...a) {
+  let a;
+}
+`, "a", [2, 14], [3, 6]);
+
+test(`
+function f(a=1) {
+  let a;
+}
+`, "a", [2, 11], [3, 6]);
+
+// eval
+// We don't have position information at runtime.
+// No note should be shown.
+
+test_eval(`
+let a;
+eval("var a");
+`, "a", [npos, npos], [1, 4]);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -56,16 +56,17 @@ MSG_DEF(JSMSG_TOPRIMITIVE_NOT_CALLABLE, 
 MSG_DEF(JSMSG_TOPRIMITIVE_RETURNED_OBJECT, 2, JSEXN_TYPEERR, "can't convert {0} to {1}: its [Symbol.toPrimitive] method returned an object")
 MSG_DEF(JSMSG_NO_PROPERTIES,           1, JSEXN_TYPEERR, "{0} has no properties")
 MSG_DEF(JSMSG_BAD_REGEXP_FLAG,         1, JSEXN_SYNTAXERR, "invalid regular expression flag {0}")
 MSG_DEF(JSMSG_ARG_INDEX_OUT_OF_RANGE,  1, JSEXN_RANGEERR, "argument {0} accesses an index that is out of range")
 MSG_DEF(JSMSG_SPREAD_TOO_LARGE,        0, JSEXN_RANGEERR, "array too large due to spread operand(s)")
 MSG_DEF(JSMSG_BAD_WEAKMAP_KEY,         0, JSEXN_TYPEERR, "cannot use the given object as a weak map key")
 MSG_DEF(JSMSG_BAD_GETTER_OR_SETTER,    1, JSEXN_TYPEERR, "invalid {0} usage")
 MSG_DEF(JSMSG_BAD_ARRAY_LENGTH,        0, JSEXN_RANGEERR, "invalid array length")
+MSG_DEF(JSMSG_REDECLARED_PREV,         2, JSEXN_NOTE, "Previously declared at line {0}, column {1}")
 MSG_DEF(JSMSG_REDECLARED_VAR,          2, JSEXN_SYNTAXERR, "redeclaration of {0} {1}")
 MSG_DEF(JSMSG_UNDECLARED_VAR,          1, JSEXN_REFERENCEERR, "assignment to undeclared variable {0}")
 MSG_DEF(JSMSG_GETTER_ONLY,             0, JSEXN_TYPEERR, "setting a property that has only a getter")
 MSG_DEF(JSMSG_OVERWRITING_ACCESSOR,    1, JSEXN_TYPEERR, "can't overwrite accessor property {0}")
 MSG_DEF(JSMSG_UNDEFINED_PROP,          1, JSEXN_REFERENCEERR, "reference to undefined property {0}")
 MSG_DEF(JSMSG_INVALID_MAP_ITERABLE,    1, JSEXN_TYPEERR, "iterable for {0} should have array-like objects")
 MSG_DEF(JSMSG_NESTING_GENERATOR,       0, JSEXN_TYPEERR, "already executing generator")
 MSG_DEF(JSMSG_INCOMPATIBLE_METHOD,     3, JSEXN_TYPEERR, "{0} {1} called on incompatible {2}")