Commit fa293dd7 authored by adamk's avatar adamk Committed by Commit bot

Re-introduce ImportDeclaration to the parser

This also adds a new VariableMode, IMPORT, which will be
used to do appropriate binding for Import-declared Variables.

Only named imports are handled for now. "import *" and default
import syntaxes have had their TODOs adjusted to match the new
code structure.

BUG=v8:1569
LOG=n

Review URL: https://codereview.chromium.org/948303004

Cr-Commit-Position: refs/heads/master@{#26895}
parent d471ceff
...@@ -186,7 +186,6 @@ void AstNumberingVisitor::VisitImportDeclaration(ImportDeclaration* node) { ...@@ -186,7 +186,6 @@ void AstNumberingVisitor::VisitImportDeclaration(ImportDeclaration* node) {
IncrementNodeCount(); IncrementNodeCount();
DisableOptimization(kImportDeclaration); DisableOptimization(kImportDeclaration);
VisitVariableProxy(node->proxy()); VisitVariableProxy(node->proxy());
Visit(node->module());
} }
......
...@@ -609,23 +609,27 @@ class ImportDeclaration FINAL : public Declaration { ...@@ -609,23 +609,27 @@ class ImportDeclaration FINAL : public Declaration {
public: public:
DECLARE_NODE_TYPE(ImportDeclaration) DECLARE_NODE_TYPE(ImportDeclaration)
Module* module() const { return module_; } const AstRawString* import_name() const { return import_name_; }
const AstRawString* module_specifier() const { return module_specifier_; }
void set_module_specifier(const AstRawString* module_specifier) {
DCHECK(module_specifier_ == NULL);
module_specifier_ = module_specifier;
}
InitializationFlag initialization() const OVERRIDE { InitializationFlag initialization() const OVERRIDE {
return kCreatedInitialized; return kNeedsInitialization;
} }
protected: protected:
ImportDeclaration(Zone* zone, ImportDeclaration(Zone* zone, VariableProxy* proxy,
VariableProxy* proxy, const AstRawString* import_name,
Module* module, const AstRawString* module_specifier, Scope* scope, int pos)
Scope* scope, : Declaration(zone, proxy, IMPORT, scope, pos),
int pos) import_name_(import_name),
: Declaration(zone, proxy, LET, scope, pos), module_specifier_(module_specifier) {}
module_(module) {
}
private: private:
Module* module_; const AstRawString* import_name_;
const AstRawString* module_specifier_;
}; };
...@@ -3168,10 +3172,11 @@ class AstNodeFactory FINAL BASE_EMBEDDED { ...@@ -3168,10 +3172,11 @@ class AstNodeFactory FINAL BASE_EMBEDDED {
} }
ImportDeclaration* NewImportDeclaration(VariableProxy* proxy, ImportDeclaration* NewImportDeclaration(VariableProxy* proxy,
Module* module, const AstRawString* import_name,
Scope* scope, const AstRawString* module_specifier,
int pos) { Scope* scope, int pos) {
return new (zone_) ImportDeclaration(zone_, proxy, module, scope, pos); return new (zone_) ImportDeclaration(zone_, proxy, import_name,
module_specifier, scope, pos);
} }
ExportDeclaration* NewExportDeclaration(VariableProxy* proxy, ExportDeclaration* NewExportDeclaration(VariableProxy* proxy,
......
...@@ -173,6 +173,10 @@ static void GetAttributesAndBindingFlags(VariableMode mode, ...@@ -173,6 +173,10 @@ static void GetAttributesAndBindingFlags(VariableMode mode,
? IMMUTABLE_CHECK_INITIALIZED_HARMONY ? IMMUTABLE_CHECK_INITIALIZED_HARMONY
: IMMUTABLE_IS_INITIALIZED_HARMONY; : IMMUTABLE_IS_INITIALIZED_HARMONY;
break; break;
case IMPORT:
// TODO(ES6)
UNREACHABLE();
break;
case DYNAMIC: case DYNAMIC:
case DYNAMIC_GLOBAL: case DYNAMIC_GLOBAL:
case DYNAMIC_LOCAL: case DYNAMIC_LOCAL:
......
...@@ -717,10 +717,12 @@ enum VariableMode { ...@@ -717,10 +717,12 @@ enum VariableMode {
CONST_LEGACY, // declared via legacy 'const' declarations CONST_LEGACY, // declared via legacy 'const' declarations
LET, // declared via 'let' declarations LET, // declared via 'let' declarations (first lexical)
CONST, // declared via 'const' declarations CONST, // declared via 'const' declarations
IMPORT, // declared via 'import' declarations (last lexical)
// Variables introduced by the compiler: // Variables introduced by the compiler:
INTERNAL, // like VAR, but not user-visible (may or may not INTERNAL, // like VAR, but not user-visible (may or may not
// be in a context) // be in a context)
...@@ -748,17 +750,17 @@ inline bool IsDynamicVariableMode(VariableMode mode) { ...@@ -748,17 +750,17 @@ inline bool IsDynamicVariableMode(VariableMode mode) {
inline bool IsDeclaredVariableMode(VariableMode mode) { inline bool IsDeclaredVariableMode(VariableMode mode) {
return mode >= VAR && mode <= CONST; return mode >= VAR && mode <= IMPORT;
} }
inline bool IsLexicalVariableMode(VariableMode mode) { inline bool IsLexicalVariableMode(VariableMode mode) {
return mode == LET || mode == CONST; return mode >= LET && mode <= IMPORT;
} }
inline bool IsImmutableVariableMode(VariableMode mode) { inline bool IsImmutableVariableMode(VariableMode mode) {
return mode == CONST || mode == CONST_LEGACY; return mode == CONST || mode == CONST_LEGACY || mode == IMPORT;
} }
......
...@@ -1284,13 +1284,12 @@ void* Parser::ParseModuleItemList(ZoneList<Statement*>* body, bool* ok) { ...@@ -1284,13 +1284,12 @@ void* Parser::ParseModuleItemList(ZoneList<Statement*>* body, bool* ok) {
} }
Literal* Parser::ParseModuleSpecifier(bool* ok) { const AstRawString* Parser::ParseModuleSpecifier(bool* ok) {
// ModuleSpecifier : // ModuleSpecifier :
// StringLiteral // StringLiteral
int pos = peek_position();
Expect(Token::STRING, CHECK_OK); Expect(Token::STRING, CHECK_OK);
return factory()->NewStringLiteral(GetSymbol(scanner()), pos); return GetSymbol(scanner());
} }
...@@ -1342,9 +1341,7 @@ void* Parser::ParseExportClause(ZoneList<const AstRawString*>* export_names, ...@@ -1342,9 +1341,7 @@ void* Parser::ParseExportClause(ZoneList<const AstRawString*>* export_names,
} }
void* Parser::ParseNamedImports(ZoneList<const AstRawString*>* import_names, ZoneList<ImportDeclaration*>* Parser::ParseNamedImports(int pos, bool* ok) {
ZoneList<const AstRawString*>* local_names,
bool* ok) {
// NamedImports : // NamedImports :
// '{' '}' // '{' '}'
// '{' ImportsList '}' // '{' ImportsList '}'
...@@ -1360,6 +1357,8 @@ void* Parser::ParseNamedImports(ZoneList<const AstRawString*>* import_names, ...@@ -1360,6 +1357,8 @@ void* Parser::ParseNamedImports(ZoneList<const AstRawString*>* import_names,
Expect(Token::LBRACE, CHECK_OK); Expect(Token::LBRACE, CHECK_OK);
ZoneList<ImportDeclaration*>* result =
new (zone()) ZoneList<ImportDeclaration*>(1, zone());
while (peek() != Token::RBRACE) { while (peek() != Token::RBRACE) {
const AstRawString* import_name = ParseIdentifierName(CHECK_OK); const AstRawString* import_name = ParseIdentifierName(CHECK_OK);
const AstRawString* local_name = import_name; const AstRawString* local_name = import_name;
...@@ -1378,15 +1377,18 @@ void* Parser::ParseNamedImports(ZoneList<const AstRawString*>* import_names, ...@@ -1378,15 +1377,18 @@ void* Parser::ParseNamedImports(ZoneList<const AstRawString*>* import_names,
ReportMessage("strict_eval_arguments"); ReportMessage("strict_eval_arguments");
return NULL; return NULL;
} }
import_names->Add(import_name, zone()); VariableProxy* proxy = NewUnresolved(local_name, IMPORT);
local_names->Add(local_name, zone()); ImportDeclaration* declaration =
factory()->NewImportDeclaration(proxy, import_name, NULL, scope_, pos);
Declare(declaration, true, CHECK_OK);
result->Add(declaration, zone());
if (peek() == Token::RBRACE) break; if (peek() == Token::RBRACE) break;
Expect(Token::COMMA, CHECK_OK); Expect(Token::COMMA, CHECK_OK);
} }
Expect(Token::RBRACE, CHECK_OK); Expect(Token::RBRACE, CHECK_OK);
return NULL; return result;
} }
...@@ -1412,7 +1414,7 @@ Statement* Parser::ParseImportDeclaration(bool* ok) { ...@@ -1412,7 +1414,7 @@ Statement* Parser::ParseImportDeclaration(bool* ok) {
// 'import' ModuleSpecifier ';' // 'import' ModuleSpecifier ';'
if (tok == Token::STRING) { if (tok == Token::STRING) {
Literal* module_specifier = ParseModuleSpecifier(CHECK_OK); const AstRawString* module_specifier = ParseModuleSpecifier(CHECK_OK);
ExpectSemicolon(CHECK_OK); ExpectSemicolon(CHECK_OK);
// TODO(ES6): Add module to the requested modules of scope_->module(). // TODO(ES6): Add module to the requested modules of scope_->module().
USE(module_specifier); USE(module_specifier);
...@@ -1424,11 +1426,11 @@ Statement* Parser::ParseImportDeclaration(bool* ok) { ...@@ -1424,11 +1426,11 @@ Statement* Parser::ParseImportDeclaration(bool* ok) {
if (tok != Token::MUL && tok != Token::LBRACE) { if (tok != Token::MUL && tok != Token::LBRACE) {
imported_default_binding = imported_default_binding =
ParseIdentifier(kDontAllowEvalOrArguments, CHECK_OK); ParseIdentifier(kDontAllowEvalOrArguments, CHECK_OK);
// TODO(ES6): Add an appropriate declaration.
} }
const AstRawString* module_instance_binding = NULL; const AstRawString* module_instance_binding = NULL;
ZoneList<const AstRawString*> local_names(1, zone()); ZoneList<ImportDeclaration*>* named_declarations = NULL;
ZoneList<const AstRawString*> import_names(1, zone());
if (imported_default_binding == NULL || Check(Token::COMMA)) { if (imported_default_binding == NULL || Check(Token::COMMA)) {
switch (peek()) { switch (peek()) {
case Token::MUL: { case Token::MUL: {
...@@ -1436,11 +1438,12 @@ Statement* Parser::ParseImportDeclaration(bool* ok) { ...@@ -1436,11 +1438,12 @@ Statement* Parser::ParseImportDeclaration(bool* ok) {
ExpectContextualKeyword(CStrVector("as"), CHECK_OK); ExpectContextualKeyword(CStrVector("as"), CHECK_OK);
module_instance_binding = module_instance_binding =
ParseIdentifier(kDontAllowEvalOrArguments, CHECK_OK); ParseIdentifier(kDontAllowEvalOrArguments, CHECK_OK);
// TODO(ES6): Add an appropriate declaration.
break; break;
} }
case Token::LBRACE: case Token::LBRACE:
ParseNamedImports(&import_names, &local_names, CHECK_OK); named_declarations = ParseNamedImports(pos, CHECK_OK);
break; break;
default: default:
...@@ -1451,23 +1454,21 @@ Statement* Parser::ParseImportDeclaration(bool* ok) { ...@@ -1451,23 +1454,21 @@ Statement* Parser::ParseImportDeclaration(bool* ok) {
} }
ExpectContextualKeyword(CStrVector("from"), CHECK_OK); ExpectContextualKeyword(CStrVector("from"), CHECK_OK);
Literal* module = ParseModuleSpecifier(CHECK_OK); const AstRawString* module_specifier = ParseModuleSpecifier(CHECK_OK);
USE(module);
ExpectSemicolon(CHECK_OK); ExpectSemicolon(CHECK_OK);
if (module_instance_binding != NULL) { if (module_instance_binding != NULL) {
// TODO(ES6): Bind name to the Module Instance Object of module. // TODO(ES6): Set the module specifier for the module namespace binding.
} }
if (imported_default_binding != NULL) { if (imported_default_binding != NULL) {
// TODO(ES6): Add an appropriate declaration. // TODO(ES6): Set the module specifier for the default binding.
} }
const int length = import_names.length(); if (named_declarations != NULL) {
DCHECK_EQ(length, local_names.length()); for (int i = 0; i < named_declarations->length(); ++i) {
for (int i = 0; i < length; ++i) { named_declarations->at(i)->set_module_specifier(module_specifier);
// TODO(ES6): Add an appropriate declaration for each name }
} }
return factory()->NewEmptyStatement(pos); return factory()->NewEmptyStatement(pos);
...@@ -1544,10 +1545,10 @@ Statement* Parser::ParseExportDeclaration(bool* ok) { ...@@ -1544,10 +1545,10 @@ Statement* Parser::ParseExportDeclaration(bool* ok) {
case Token::MUL: { case Token::MUL: {
Consume(Token::MUL); Consume(Token::MUL);
ExpectContextualKeyword(CStrVector("from"), CHECK_OK); ExpectContextualKeyword(CStrVector("from"), CHECK_OK);
Literal* module = ParseModuleSpecifier(CHECK_OK); const AstRawString* module_specifier = ParseModuleSpecifier(CHECK_OK);
ExpectSemicolon(CHECK_OK); ExpectSemicolon(CHECK_OK);
// TODO(ES6): scope_->module()->AddStarExport(...) // TODO(ES6): scope_->module()->AddStarExport(...)
USE(module); USE(module_specifier);
return factory()->NewEmptyStatement(pos); return factory()->NewEmptyStatement(pos);
} }
...@@ -1569,7 +1570,7 @@ Statement* Parser::ParseExportDeclaration(bool* ok) { ...@@ -1569,7 +1570,7 @@ Statement* Parser::ParseExportDeclaration(bool* ok) {
ZoneList<const AstRawString*> local_names(1, zone()); ZoneList<const AstRawString*> local_names(1, zone());
ParseExportClause(&export_names, &export_locations, &local_names, ParseExportClause(&export_names, &export_locations, &local_names,
&reserved_loc, CHECK_OK); &reserved_loc, CHECK_OK);
Literal* indirect_export_module_specifier = NULL; const AstRawString* indirect_export_module_specifier = NULL;
if (CheckContextualKeyword(CStrVector("from"))) { if (CheckContextualKeyword(CStrVector("from"))) {
indirect_export_module_specifier = ParseModuleSpecifier(CHECK_OK); indirect_export_module_specifier = ParseModuleSpecifier(CHECK_OK);
} else if (reserved_loc.IsValid()) { } else if (reserved_loc.IsValid()) {
......
...@@ -707,7 +707,7 @@ class Parser : public ParserBase<ParserTraits> { ...@@ -707,7 +707,7 @@ class Parser : public ParserBase<ParserTraits> {
Statement* ParseStatementListItem(bool* ok); Statement* ParseStatementListItem(bool* ok);
void* ParseModuleItemList(ZoneList<Statement*>* body, bool* ok); void* ParseModuleItemList(ZoneList<Statement*>* body, bool* ok);
Statement* ParseModuleItem(bool* ok); Statement* ParseModuleItem(bool* ok);
Literal* ParseModuleSpecifier(bool* ok); const AstRawString* ParseModuleSpecifier(bool* ok);
Statement* ParseImportDeclaration(bool* ok); Statement* ParseImportDeclaration(bool* ok);
Statement* ParseExportDeclaration(bool* ok); Statement* ParseExportDeclaration(bool* ok);
Statement* ParseExportDefault(bool* ok); Statement* ParseExportDefault(bool* ok);
...@@ -715,8 +715,7 @@ class Parser : public ParserBase<ParserTraits> { ...@@ -715,8 +715,7 @@ class Parser : public ParserBase<ParserTraits> {
ZoneList<Scanner::Location>* export_locations, ZoneList<Scanner::Location>* export_locations,
ZoneList<const AstRawString*>* local_names, ZoneList<const AstRawString*>* local_names,
Scanner::Location* reserved_loc, bool* ok); Scanner::Location* reserved_loc, bool* ok);
void* ParseNamedImports(ZoneList<const AstRawString*>* import_names, ZoneList<ImportDeclaration*>* ParseNamedImports(int pos, bool* ok);
ZoneList<const AstRawString*>* local_names, bool* ok);
Statement* ParseStatement(ZoneList<const AstRawString*>* labels, bool* ok); Statement* ParseStatement(ZoneList<const AstRawString*>* labels, bool* ok);
Statement* ParseSubStatement(ZoneList<const AstRawString*>* labels, bool* ok); Statement* ParseSubStatement(ZoneList<const AstRawString*>* labels, bool* ok);
Statement* ParseFunctionDeclaration(ZoneList<const AstRawString*>* names, Statement* ParseFunctionDeclaration(ZoneList<const AstRawString*>* names,
......
...@@ -106,7 +106,6 @@ void CallPrinter::VisitModuleDeclaration(ModuleDeclaration* node) { ...@@ -106,7 +106,6 @@ void CallPrinter::VisitModuleDeclaration(ModuleDeclaration* node) {
void CallPrinter::VisitImportDeclaration(ImportDeclaration* node) { void CallPrinter::VisitImportDeclaration(ImportDeclaration* node) {
Find(node->module());
} }
...@@ -481,7 +480,7 @@ void PrettyPrinter::VisitImportDeclaration(ImportDeclaration* node) { ...@@ -481,7 +480,7 @@ void PrettyPrinter::VisitImportDeclaration(ImportDeclaration* node) {
Print("import "); Print("import ");
PrintLiteral(node->proxy()->name(), false); PrintLiteral(node->proxy()->name(), false);
Print(" from "); Print(" from ");
Visit(node->module()); PrintLiteral(node->module_specifier()->string(), true);
Print(";"); Print(";");
} }
...@@ -1213,7 +1212,7 @@ void AstPrinter::VisitModuleDeclaration(ModuleDeclaration* node) { ...@@ -1213,7 +1212,7 @@ void AstPrinter::VisitModuleDeclaration(ModuleDeclaration* node) {
void AstPrinter::VisitImportDeclaration(ImportDeclaration* node) { void AstPrinter::VisitImportDeclaration(ImportDeclaration* node) {
IndentedScope indent(this, "IMPORT"); IndentedScope indent(this, "IMPORT");
PrintLiteralIndented("NAME", node->proxy()->name(), true); PrintLiteralIndented("NAME", node->proxy()->name(), true);
Visit(node->module()); PrintLiteralIndented("FROM", node->module_specifier()->string(), true);
} }
......
...@@ -790,7 +790,8 @@ RUNTIME_FUNCTION(Runtime_DeclareModules) { ...@@ -790,7 +790,8 @@ RUNTIME_FUNCTION(Runtime_DeclareModules) {
case VAR: case VAR:
case LET: case LET:
case CONST: case CONST:
case CONST_LEGACY: { case CONST_LEGACY:
case IMPORT: {
PropertyAttributes attr = PropertyAttributes attr =
IsImmutableVariableMode(mode) ? FROZEN : SEALED; IsImmutableVariableMode(mode) ? FROZEN : SEALED;
Handle<AccessorInfo> info = Handle<AccessorInfo> info =
......
...@@ -799,7 +799,6 @@ void AstTyper::VisitModuleDeclaration(ModuleDeclaration* declaration) { ...@@ -799,7 +799,6 @@ void AstTyper::VisitModuleDeclaration(ModuleDeclaration* declaration) {
void AstTyper::VisitImportDeclaration(ImportDeclaration* declaration) { void AstTyper::VisitImportDeclaration(ImportDeclaration* declaration) {
RECURSE(Visit(declaration->module()));
} }
......
...@@ -20,6 +20,7 @@ const char* Variable::Mode2String(VariableMode mode) { ...@@ -20,6 +20,7 @@ const char* Variable::Mode2String(VariableMode mode) {
case CONST_LEGACY: return "CONST_LEGACY"; case CONST_LEGACY: return "CONST_LEGACY";
case LET: return "LET"; case LET: return "LET";
case CONST: return "CONST"; case CONST: return "CONST";
case IMPORT: return "IMPORT";
case DYNAMIC: return "DYNAMIC"; case DYNAMIC: return "DYNAMIC";
case DYNAMIC_GLOBAL: return "DYNAMIC_GLOBAL"; case DYNAMIC_GLOBAL: return "DYNAMIC_GLOBAL";
case DYNAMIC_LOCAL: return "DYNAMIC_LOCAL"; case DYNAMIC_LOCAL: return "DYNAMIC_LOCAL";
......
...@@ -5218,11 +5218,11 @@ TEST(ModuleParsingInternals) { ...@@ -5218,11 +5218,11 @@ TEST(ModuleParsingInternals) {
isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() -
128 * 1024); 128 * 1024);
static const char kSource[] = "let x = 5; export { x as y };"; static const char kSource[] =
"let x = 5; export { x as y }; import { q as z } from 'm.js';";
i::Handle<i::String> source = factory->NewStringFromAsciiChecked(kSource); i::Handle<i::String> source = factory->NewStringFromAsciiChecked(kSource);
i::Handle<i::Script> script = factory->NewScript(source); i::Handle<i::Script> script = factory->NewScript(source);
i::CompilationInfoWithZone info(script); i::CompilationInfoWithZone info(script);
i::AstValueFactory avf(info.zone(), isolate->heap()->HashSeed());
i::Parser parser(&info, isolate->stack_guard()->real_climit(), i::Parser parser(&info, isolate->stack_guard()->real_climit(),
isolate->heap()->HashSeed(), isolate->unicode_cache()); isolate->heap()->HashSeed(), isolate->unicode_cache());
parser.set_allow_harmony_modules(true); parser.set_allow_harmony_modules(true);
...@@ -5233,15 +5233,21 @@ TEST(ModuleParsingInternals) { ...@@ -5233,15 +5233,21 @@ TEST(ModuleParsingInternals) {
CHECK_EQ(i::MODULE_SCOPE, func->scope()->scope_type()); CHECK_EQ(i::MODULE_SCOPE, func->scope()->scope_type());
i::ModuleDescriptor* descriptor = func->scope()->module(); i::ModuleDescriptor* descriptor = func->scope()->module();
CHECK_NOT_NULL(descriptor); CHECK_NOT_NULL(descriptor);
const i::AstRawString* name_x = avf.GetOneByteString("x");
const i::AstRawString* name_y = avf.GetOneByteString("y");
int num_exports = 0; int num_exports = 0;
for (auto it = descriptor->iterator(); !it.done(); it.Advance()) { for (auto it = descriptor->iterator(); !it.done(); it.Advance()) {
++num_exports; ++num_exports;
CHECK(*name_x == *it.local_name()); CHECK(it.local_name()->IsOneByteEqualTo("x"));
CHECK(*name_y == *it.export_name()); CHECK(it.export_name()->IsOneByteEqualTo("y"));
} }
CHECK_EQ(1, num_exports); CHECK_EQ(1, num_exports);
i::ZoneList<i::Declaration*>* declarations = func->scope()->declarations();
CHECK_EQ(2, declarations->length());
CHECK(declarations->at(0)->proxy()->raw_name()->IsOneByteEqualTo("x"));
i::ImportDeclaration* import_decl =
declarations->at(1)->AsImportDeclaration();
CHECK(import_decl->import_name()->IsOneByteEqualTo("q"));
CHECK(import_decl->proxy()->raw_name()->IsOneByteEqualTo("z"));
CHECK(import_decl->module_specifier()->IsOneByteEqualTo("m.js"));
} }
......
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// MODULE
let foo = 42;
import { bar as foo } from "mod";
# Copyright 2015 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
*%(basename)s:8: SyntaxError: Identifier 'foo' has already been declared
import { bar as foo } from "mod";
^^^
SyntaxError: Identifier 'foo' has already been declared
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// MODULE
let foo = 42;
import { foo } from "mod";
# Copyright 2015 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
*%(basename)s:8: SyntaxError: Identifier 'foo' has already been declared
import { foo } from "mod";
^^^
SyntaxError: Identifier 'foo' has already been declared
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment