This commit is contained in:
Jarred Sumner
2021-04-23 17:00:07 -07:00
parent f384465fa7
commit a75f0c1eea
3 changed files with 312 additions and 17 deletions

View File

@@ -2248,13 +2248,19 @@ pub const S = struct {
// Many parts are optional and can be combined in different ways. The only
// restriction is that you cannot have both a clause and a star namespace.
pub const Import = struct {
// If this is a star import: This is a Ref for the namespace symbol. The Loc
// for the symbol is StarLoc.
//
// Otherwise: This is an auto-generated Ref for the namespace representing
// the imported file. In this case StarLoc is nil. The NamespaceRef is used
// when converting this module to a CommonJS module.
namespace_ref: Ref, default_name: *LocRef, items: *[]ClauseItem, star_name_loc: *logger.Loc, import_record_index: u32, is_single_line: bool };
// If this is a star import: This is a Ref for the namespace symbol. The Loc
// for the symbol is StarLoc.
//
// Otherwise: This is an auto-generated Ref for the namespace representing
// the imported file. In this case StarLoc is nil. The NamespaceRef is used
// when converting this module to a CommonJS module.
namespace_ref: Ref,
default_name: ?LocRef = null,
items: []ClauseItem = &([_]ClauseItem{}),
star_name_loc: ?logger.Loc = null,
import_record_index: u32,
is_single_line: bool = false,
};
pub const Return = struct { value: ?ExprNodeIndex = null };
pub const Throw = struct { value: ExprNodeIndex };

View File

@@ -842,6 +842,11 @@ pub const Lexer = struct {
return buf;
}
// TODO: use wtf-8 encoding.
pub fn utf16ToStringWithValidation(lexer: *Lexer, js: JavascriptString) !string {
return std.unicode.utf16leToUtf8Alloc(lexer.allocator, js);
}
// TODO: use wtf-8 encoding.
pub fn utf16ToString(lexer: *Lexer, js: JavascriptString) string {
return std.unicode.utf16leToUtf8Alloc(lexer.allocator, js) catch unreachable;

View File

@@ -43,6 +43,11 @@ const ExprOrLetStmt = struct {
const Tup = std.meta.Tuple;
// This function exists to tie all of these checks together in one place
fn isEvalOrArguments(name: string) bool {
return strings.eql(name, "eval") or strings.eql(name, "arguments");
}
fn notimpl() noreturn {
std.debug.panic("Not implemented yet!!", .{});
}
@@ -236,6 +241,11 @@ const DeferredErrors = struct {
};
};
const ImportClause = struct {
items: []js_ast.ClauseItem = &([_]js_ast.ClauseItem{}),
is_single_line: bool = false,
};
const ModuleType = enum { esm };
const PropertyOpts = struct {
@@ -1521,7 +1531,7 @@ const P = struct {
p.lexer.unexpected();
}
const export_clause = p.parseExportClause();
const export_clause = try p.parseExportClause();
if (p.lexer.isContextualKeyword("from")) {
p.lexer.expectContextualKeyword("from");
const parsedPath = p.parsePath();
@@ -1972,7 +1982,195 @@ const P = struct {
);
},
.t_import => {
notimpl();
const previous_import_keyword = p.es6_import_keyword;
p.es6_import_keyword = p.lexer.range();
p.lexer.next();
var stmt: S.Import = S.Import{
.namespace_ref = undefined,
.import_record_index = std.math.maxInt(u32),
};
var was_originally_bare_import = false;
// "export import foo = bar"
if ((opts.is_export or (opts.is_namespace_scope and !opts.is_typescript_declare)) and p.lexer.token != .t_identifier) {
p.lexer.expected(.t_identifier);
}
switch (p.lexer.token) {
// "import('path')"
// "import.meta"
.t_open_paren, .t_dot => {
p.es6_import_keyword = previous_import_keyword; // this wasn't an esm import statement after all
const expr = p.parseSuffix(p.parseImportExpr(loc, .lowest), .lowest, null, Expr.EFlags.none);
p.lexer.expectOrInsertSemicolon();
return p.s(S.SExpr{
.value = expr,
}, loc);
},
.t_string_literal, .t_no_substitution_template_literal => {
// "import 'path'"
if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) {
p.lexer.unexpected();
fail();
}
was_originally_bare_import = true;
},
.t_asterisk => {
// "import * as ns from 'path'"
if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) {
p.lexer.unexpected();
fail();
}
p.lexer.next();
p.lexer.expectContextualKeyword("as");
stmt = S.Import{
.namespace_ref = try p.storeNameInRef(p.lexer.identifier),
.star_name_loc = p.lexer.loc(),
.import_record_index = std.math.maxInt(u32),
};
p.lexer.expect(.t_identifier);
p.lexer.expectContextualKeyword("from");
},
.t_open_brace => {
// "import * as ns from 'path'"
if (!opts.is_module_scope and (!opts.is_namespace_scope or !opts.is_typescript_declare)) {
p.lexer.unexpected();
fail();
}
var importClause = try p.parseImportClause();
stmt = S.Import{ .namespace_ref = undefined, .import_record_index = std.math.maxInt(u32), .items = importClause.items, .is_single_line = importClause.is_single_line };
p.lexer.expectContextualKeyword("from");
},
.t_identifier => {
// "import defaultItem from 'path'"
// "import foo = bar"
if (!opts.is_module_scope and (!opts.is_namespace_scope)) {
p.lexer.unexpected();
fail();
}
const default_name = p.lexer.identifier;
stmt = S.Import{ .namespace_ref = undefined, .import_record_index = std.math.maxInt(u32), .default_name = LocRef{
.loc = p.lexer.loc(),
.ref = try p.storeNameInRef(default_name),
} };
p.lexer.next();
if (p.options.ts) {
// Skip over type-only imports
if (strings.eql(default_name, "type")) {
switch (p.lexer.token) {
.t_identifier => {
if (!strings.eql(p.lexer.identifier, "from")) {
// "import type foo from 'bar';"
p.lexer.next();
p.lexer.expectContextualKeyword("from");
_ = p.parsePath();
p.lexer.expectOrInsertSemicolon();
return p.s(S.TypeScript{}, loc);
}
},
.t_asterisk => {
// "import type * as foo from 'bar';"
p.lexer.next();
p.lexer.expectContextualKeyword("as");
p.lexer.expect(.t_identifier);
p.lexer.expectContextualKeyword("from");
_ = p.parsePath();
p.lexer.expectOrInsertSemicolon();
return p.s(S.TypeScript{}, loc);
},
.t_open_brace => {
// "import type {foo} from 'bar';"
_ = try p.parseImportClause();
p.lexer.expectContextualKeyword("from");
_ = p.parsePath();
p.lexer.expectOrInsertSemicolon();
return p.s(S.TypeScript{}, loc);
},
else => {},
}
}
// Parse TypeScript import assignment statements
p.es6_import_keyword = previous_import_keyword; // This wasn't an ESM import statement after all;
return p.parseTypeScriptImportEqualsStmt(loc, opts, logger.Loc.Empty, default_name);
}
if (p.lexer.token == .t_comma) {
p.lexer.next();
switch (p.lexer.token) {
// "import defaultItem, * as ns from 'path'"
.t_asterisk => {
p.lexer.next();
p.lexer.expectContextualKeyword("as");
stmt.namespace_ref = try p.storeNameInRef(p.lexer.identifier);
stmt.star_name_loc = p.lexer.loc();
p.lexer.expect(.t_identifier);
},
// "import defaultItem, {item1, item2} from 'path'"
.t_open_brace => {
const importClause = try p.parseImportClause();
stmt.items = importClause.items;
stmt.is_single_line = importClause.is_single_line;
},
else => {
p.lexer.unexpected();
},
}
p.lexer.expectContextualKeyword("from");
}
},
else => {
p.lexer.unexpected();
fail();
},
}
const path = p.parsePath();
stmt.import_record_index = p.addImportRecord(.stmt, path.loc, path.text);
p.import_records.items[stmt.import_record_index].was_originally_bare_import = was_originally_bare_import;
p.lexer.expectOrInsertSemicolon();
if (stmt.star_name_loc) |star| {
stmt.namespace_ref = try p.declareSymbol(.import, star, p.loadNameFromRef(stmt.namespace_ref));
} else {
var path_name = fs.PathName.init(strings.append(p.allocator, "import_", path.text) catch unreachable);
const name = try path_name.nonUniqueNameString(p.allocator);
stmt.namespace_ref = try p.newSymbol(.other, name);
var scope: *Scope = p.current_scope orelse unreachable;
try scope.generated.append(stmt.namespace_ref);
}
var item_refs = std.StringHashMap(LocRef).init(p.allocator);
// Link the default item to the namespace
if (stmt.default_name) |*name_loc| {
const name = p.loadNameFromRef(name_loc.ref orelse unreachable);
const ref = try p.declareSymbol(.import, name_loc.loc, name);
try p.is_import_item.put(ref, true);
name_loc.ref = ref;
}
if (stmt.items.len > 0) {
try item_refs.ensureCapacity(@intCast(u32, stmt.items.len));
for (stmt.items) |*item| {
const name = p.loadNameFromRef(item.name.ref orelse unreachable);
const ref = try p.declareSymbol(.import, item.name.loc, name);
p.checkForNonBMPCodePoint(item.alias_loc, item.alias);
try p.is_import_item.put(ref, true);
item.name.ref = ref;
item_refs.putAssumeCapacity(item.alias, LocRef{ .loc = item.name.loc, .ref = ref });
}
}
// Track the items for this namespace
try p.import_items_for_namespace.put(stmt.namespace_ref, item_refs);
return p.s(stmt, loc);
},
.t_break => {
notimpl();
@@ -2001,6 +2199,97 @@ const P = struct {
return js_ast.Stmt.empty();
}
pub fn parseTypeScriptImportEqualsStmt(p: *P, loc: logger.Loc, opts: *ParseStatementOptions, default_name_loc: logger.Loc, default_name: string) Stmt {
notimpl();
}
pub fn parseClauseAlias(p: *P, kind: string) !string {
const loc = p.lexer.loc();
// The alias may now be a string (see https://github.com/tc39/ecma262/pull/2154)
if (p.lexer.token == .t_string_literal) {
if (p.lexer.utf16ToStringWithValidation(p.lexer.string_literal)) |alias| {
return alias;
} else |err| {
const r = p.source.rangeOfString(loc);
// TODO: improve error message
try p.log.addRangeErrorFmt(p.source, r, p.allocator, "Invalid {s} alias because it contains an unpaired Unicode surrogate (like emoji)", .{kind});
return p.source.textForRange(r);
}
}
// The alias may be a keyword
if (!p.lexer.isIdentifierOrKeyword()) {
p.lexer.expect(.t_identifier);
}
const alias = p.lexer.identifier;
p.checkForNonBMPCodePoint(loc, alias);
return alias;
}
pub fn parseImportClause(
p: *P,
) !ImportClause {
var items = List(js_ast.ClauseItem).init(p.allocator);
p.lexer.expect(.t_open_brace);
var is_single_line = !p.lexer.has_newline_before;
while (p.lexer.token != .t_close_brace) {
// The alias may be a keyword;
const isIdentifier = p.lexer.token == .t_identifier;
const alias_loc = p.lexer.loc();
const alias = try p.parseClauseAlias("import");
var name = LocRef{ .loc = alias_loc, .ref = try p.storeNameInRef(alias) };
var original_name = alias;
p.lexer.next();
if (p.lexer.isContextualKeyword("as")) {
p.lexer.next();
original_name = p.lexer.identifier;
name = LocRef{ .loc = alias_loc, .ref = try p.storeNameInRef(alias) };
p.lexer.expect(.t_identifier);
} else if (!isIdentifier) {
// An import where the name is a keyword must have an alias
p.lexer.expectedString("\"as\"");
}
// Reject forbidden names
if (isEvalOrArguments(original_name)) {
const r = js_lexer.rangeOfIdentifier(&p.source, name.loc);
try p.log.addRangeErrorFmt(p.source, r, p.allocator, "Cannot use \"{s}\" as an identifier here", .{original_name});
}
try items.append(js_ast.ClauseItem{
.alias = alias,
.alias_loc = alias_loc,
.name = name,
.original_name = original_name,
});
if (p.lexer.token != .t_comma) {
break;
}
if (p.lexer.has_newline_before) {
is_single_line = false;
}
p.lexer.next();
if (p.lexer.has_newline_before) {
is_single_line = false;
}
}
if (p.lexer.has_newline_before) {
is_single_line = false;
}
p.lexer.expect(.t_close_brace);
return ImportClause{ .items = items.toOwnedSlice(), .is_single_line = is_single_line };
}
pub fn forbidInitializers(p: *P, decls: []G.Decl, loop_type: string, is_var: bool) !void {
if (decls.len > 1) {
try p.log.addErrorFmt(p.source, decls[0].binding.loc, p.allocator, "for-{s} loops must have a single declaration", .{loop_type});
@@ -2356,14 +2645,14 @@ const P = struct {
// return Stmt.empty();
}
pub fn parseExportClause(p: *P) ExportClauseResult {
pub fn parseExportClause(p: *P) !ExportClauseResult {
var items = List(js_ast.ClauseItem).initCapacity(p.allocator, 1) catch unreachable;
var first_keyword_item_loc = logger.Loc{};
p.lexer.expect(.t_open_brace);
var is_single_line = !p.lexer.has_newline_before;
while (p.lexer.token != .t_close_brace) {
var alias = p.lexer.identifier;
var alias = try p.parseClauseAlias("export");
var alias_loc = p.lexer.loc();
var name = LocRef{
@@ -2396,14 +2685,9 @@ const P = struct {
if (p.lexer.isContextualKeyword("as")) {
p.lexer.next();
alias = p.lexer.identifier;
alias = try p.parseClauseAlias("export");
alias_loc = p.lexer.loc();
// The alias may be a keyword
if (!p.lexer.isIdentifierOrKeyword()) {
p.lexer.expect(.t_identifier);
}
p.checkForNonBMPCodePoint(alias_loc, alias);
p.lexer.next();
}