aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-02-27 01:49:30 -0800
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-02-27 01:49:30 -0800
commit5e4b50dc6c87c42095efec748bd1055d57b08bf8 (patch)
tree6cddcd3adb3b877aad35e3afb039fcc5cb65f828
parent152f63b019a362ecb115171b043e11e2b9d922cd (diff)
downloadbun-5e4b50dc6c87c42095efec748bd1055d57b08bf8.tar.gz
bun-5e4b50dc6c87c42095efec748bd1055d57b08bf8.tar.zst
bun-5e4b50dc6c87c42095efec748bd1055d57b08bf8.zip
[JS Parser] #privateIdentifiers
-rw-r--r--src/js_parser/js_parser.zig384
1 files changed, 279 insertions, 105 deletions
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index 559c115db..fefbee2f6 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -1587,7 +1587,7 @@ const FunctionKind = enum { stmt, expr };
const EightLetterMatcher = strings.ExactSizeMatcher(8);
-const AsyncPrefixExpression = enum(u4) {
+const AsyncPrefixExpression = enum(u2) {
none,
is_yield,
is_async,
@@ -1885,10 +1885,14 @@ const FnOrArrowDataParse = struct {
allow_await: AwaitOrYield = AwaitOrYield.allow_ident,
allow_yield: AwaitOrYield = AwaitOrYield.allow_ident,
allow_super_call: bool = false,
+ allow_super_property: bool = false,
is_top_level: bool = false,
is_constructor: bool = false,
is_typescript_declare: bool = false,
+ is_return_disallowed: bool = false,
+ is_this_disallowed: bool = false,
+
has_async_range: bool = false,
arrow_arg_errors: DeferredArrowArgErrors = DeferredArrowArgErrors{},
track_arrow_arg_errors: bool = false,
@@ -1948,6 +1952,13 @@ const FnOnlyDataVisit = struct {
// will have to reference a captured variable instead of the real variable.
is_inside_async_arrow_fn: bool = false,
+ // If false, disallow "new.target" expressions. We disallow all "new.target"
+ // expressions at the top-level of the file (i.e. not inside a function or
+ // a class field). Technically since CommonJS files are wrapped in a function
+ // you can use "new.target" in node as an alias for "undefined" but we don't
+ // support that.
+ is_new_target_allowed: bool = false,
+
// If false, the value for "this" is the top-level module scope "this" value.
// That means it's "undefined" for ECMAScript modules and "exports" for
// CommonJS modules. We track this information so that we can substitute the
@@ -1999,6 +2010,7 @@ const ModuleType = enum { esm };
const PropertyOpts = struct {
async_range: logger.Range = logger.Range.None,
+ declare_range: logger.Range = logger.Range.None,
is_async: bool = false,
is_generator: bool = false,
@@ -3625,6 +3637,16 @@ fn NewParser_(
pub fn s(_: *P, t: anytype, loc: logger.Loc) Stmt {
const Type = @TypeOf(t);
+ comptime {
+ if (!is_typescript_enabled and (Type == S.TypeScript or Type == *S.TypeScript)) {
+ @compileError("Attempted to use TypeScript syntax in a non-TypeScript environment");
+ }
+ }
+
+ if (!is_typescript_enabled and (Type == S.TypeScript or Type == *S.TypeScript)) {
+ unreachable;
+ }
+
// Output.print("\nStmt: {s} - {d}\n", .{ @typeName(@TypeOf(t)), loc.start });
if (@typeInfo(Type) == .Pointer) {
// ExportFrom normally becomes import records during the visiting pass
@@ -3653,6 +3675,14 @@ fn NewParser_(
pub fn e(p: *P, t: anytype, loc: logger.Loc) Expr {
const Type = @TypeOf(t);
+ comptime {
+ if (jsx_transform_type == .none) {
+ if (Type == E.JSXElement or Type == *E.JSXElement) {
+ @compileError("JSXElement is not supported in this environment");
+ }
+ }
+ }
+
// Output.print("\nExpr: {s} - {d}\n", .{ @typeName(@TypeOf(t)), loc.start });
if (@typeInfo(Type) == .Pointer) {
if (comptime only_scan_imports_and_do_not_visit) {
@@ -3865,10 +3895,10 @@ fn NewParser_(
fn keyNameForError(p: *P, key: js_ast.Expr) string {
switch (key.data) {
.e_string => {
- return p.lexer.raw();
+ return key.data.e_string.string(p.allocator) catch unreachable;
},
- .e_private_identifier => {
- return p.lexer.raw();
+ .e_private_identifier => |private| {
+ return p.loadNameFromRef(private.ref);
// return p.loadNameFromRef()
},
else => {
@@ -4248,17 +4278,14 @@ fn NewParser_(
logger.rangeData(
p.source,
r,
- std.fmt.allocPrint(allocator, "{s} has already been declared", .{symbol.original_name}) catch unreachable,
+ std.fmt.allocPrint(
+ allocator,
+ "{s} was originally declared here",
+ .{existing_symbol.original_name},
+ ) catch unreachable,
);
- p.log.addRangeErrorFmtWithNotes(
- p.source,
- js_lexer.rangeOfIdentifier(p.source, existing_member_entry.value.loc),
- allocator,
- notes,
- "{s} was originally declared here",
- .{existing_symbol.original_name},
- ) catch unreachable;
+ p.log.addRangeErrorFmtWithNotes(p.source, js_lexer.rangeOfIdentifier(p.source, existing_member_entry.value.loc), allocator, notes, "{s} has already been declared", .{symbol.original_name}) catch unreachable;
}
continue :nextMember;
@@ -4500,6 +4527,19 @@ fn NewParser_(
try p.log.addError(p.source, loc, "Cannot use a declaration in a single-statement context");
}
+ /// If we attempt to parse TypeScript syntax outside of a TypeScript file
+ /// make it a compile error
+ inline fn markTypeScriptOnly(_: *const P) void {
+ if (comptime !is_typescript_enabled) {
+ @compileError("This function can only be used in TypeScript");
+ }
+
+ // explicitly mark it as unreachable in the hopes that the function doesn't exist at all
+ if (!is_typescript_enabled) {
+ unreachable;
+ }
+ }
+
fn logExprErrors(p: *P, errors: *DeferredErrors) void {
if (errors.invalid_expr_default_value) |r| {
p.log.addRangeError(
@@ -4585,20 +4625,22 @@ fn NewParser_(
.allow_missing_body_for_type_script = is_typescript_enabled,
});
- // Don't output anything if it's just a forward declaration of a function
- if (opts.is_typescript_declare or func.flags.is_forward_declaration) {
- p.popAndDiscardScope(scopeIndex);
+ if (comptime is_typescript_enabled) {
+ // Don't output anything if it's just a forward declaration of a function
+ if (opts.is_typescript_declare or func.flags.is_forward_declaration) {
+ p.popAndDiscardScope(scopeIndex);
- // Balance the fake block scope introduced above
- if (hasIfScope) {
- p.popScope();
- }
+ // Balance the fake block scope introduced above
+ if (hasIfScope) {
+ p.popScope();
+ }
- if (opts.is_typescript_declare and opts.is_namespace_scope and opts.is_export) {
- p.has_non_local_export_declare_inside_namespace = true;
- }
+ if (opts.is_typescript_declare and opts.is_namespace_scope and opts.is_export) {
+ p.has_non_local_export_declare_inside_namespace = true;
+ }
- return p.s(S.TypeScript{}, loc);
+ return p.s(S.TypeScript{}, loc);
+ }
}
p.popScope();
@@ -4670,10 +4712,20 @@ fn NewParser_(
// Await and yield are not allowed in function arguments
var old_fn_or_arrow_data = std.mem.toBytes(p.fn_or_arrow_data_parse);
- p.fn_or_arrow_data_parse.allow_await = if (opts.allow_await == .allow_expr) AwaitOrYield.forbid_all else AwaitOrYield.allow_ident;
- p.fn_or_arrow_data_parse.allow_yield = if (opts.allow_yield == .allow_expr) AwaitOrYield.forbid_all else AwaitOrYield.allow_ident;
+ p.fn_or_arrow_data_parse.allow_await = if (opts.allow_await == .allow_expr)
+ AwaitOrYield.forbid_all
+ else
+ AwaitOrYield.allow_ident;
+
+ p.fn_or_arrow_data_parse.allow_yield = if (opts.allow_yield == .allow_expr)
+ AwaitOrYield.forbid_all
+ else
+ AwaitOrYield.allow_ident;
+
// If "super()" is allowed in the body, it's allowed in the arguments
p.fn_or_arrow_data_parse.allow_super_call = opts.allow_super_call;
+ p.fn_or_arrow_data_parse.allow_super_property = opts.allow_super_property;
+
var args = List(G.Arg){};
while (p.lexer.token != T.t_close_paren) {
// Skip over "this" type annotations
@@ -4850,10 +4902,12 @@ fn NewParser_(
}
inline fn skipTypeScriptType(p: *P, level: js_ast.Op.Level) anyerror!void {
+ p.markTypeScriptOnly();
try p.skipTypeScriptTypeWithOpts(level, .{});
}
fn skipTypeScriptBinding(p: *P) anyerror!void {
+ p.markTypeScriptOnly();
switch (p.lexer.token) {
.t_identifier, .t_this => {
try p.lexer.next();
@@ -4929,6 +4983,8 @@ fn NewParser_(
}
fn skipTypescriptFnArgs(p: *P) anyerror!void {
+ p.markTypeScriptOnly();
+
try p.lexer.expect(.t_open_paren);
while (p.lexer.token != .t_close_paren) {
@@ -4977,6 +5033,8 @@ fn NewParser_(
// let x = (y: any): asserts y is (y) => {};
//
fn skipTypeScriptParenOrFnType(p: *P) anyerror!void {
+ p.markTypeScriptOnly();
+
if (p.trySkipTypeScriptArrowArgsWithBacktracking()) {
try p.skipTypescriptReturnType();
} else {
@@ -4987,9 +5045,7 @@ fn NewParser_(
}
fn skipTypeScriptTypeWithOpts(p: *P, level: js_ast.Op.Level, opts: TypeScript.SkipTypeOptions) anyerror!void {
- if (!is_typescript_enabled) {
- unreachable;
- }
+ p.markTypeScriptOnly();
while (true) {
switch (p.lexer.token) {
@@ -5260,6 +5316,8 @@ fn NewParser_(
}
}
fn skipTypeScriptObjectType(p: *P) anyerror!void {
+ p.markTypeScriptOnly();
+
try p.lexer.expect(.t_open_brace);
while (p.lexer.token != .t_close_brace) {
@@ -5541,6 +5599,8 @@ fn NewParser_(
// This is the type parameter declarations that go with other symbol
// declarations (class, function, type, etc.)
fn skipTypeScriptTypeParameters(p: *P) anyerror!void {
+ p.markTypeScriptOnly();
+
if (p.lexer.token == .t_less_than) {
try p.lexer.next();
@@ -5655,13 +5715,15 @@ fn NewParser_(
const scope_index = p.pushScopeForParsePass(.class_name, loc) catch unreachable;
const class = try p.parseClass(class_keyword, name, class_opts);
- if (opts.is_typescript_declare) {
- p.popAndDiscardScope(scope_index);
- if (opts.is_namespace_scope and opts.is_export) {
- p.has_non_local_export_declare_inside_namespace = true;
- }
+ if (comptime is_typescript_enabled) {
+ if (opts.is_typescript_declare) {
+ p.popAndDiscardScope(scope_index);
+ if (opts.is_namespace_scope and opts.is_export) {
+ p.has_non_local_export_declare_inside_namespace = true;
+ }
- return p.s(S.TypeScript{}, loc);
+ return p.s(S.TypeScript{}, loc);
+ }
}
p.popScope();
@@ -5762,7 +5824,11 @@ fn NewParser_(
// "@decorator export default abstract class Foo {}"
// "@decorator export declare class Foo {}"
// "@decorator export declare abstract class Foo {}"
- if (opts.ts_decorators != null and p.lexer.token != js_lexer.T.t_class and p.lexer.token != js_lexer.T.t_default and !p.lexer.isContextualKeyword("abstract") and !p.lexer.isContextualKeyword("declare")) {
+ if (opts.ts_decorators != null and p.lexer.token != js_lexer.T.t_class and
+ p.lexer.token != js_lexer.T.t_default and
+ !p.lexer.isContextualKeyword("abstract") and
+ !p.lexer.isContextualKeyword("declare"))
+ {
try p.lexer.expected(js_lexer.T.t_class);
}
@@ -5799,14 +5865,16 @@ fn NewParser_(
return p.parseStmt(opts);
}
- if (opts.is_typescript_declare and p.lexer.isContextualKeyword("as")) {
- // "export as namespace ns;"
- try p.lexer.next();
- try p.lexer.expectContextualKeyword("namespace");
- try p.lexer.expect(T.t_identifier);
- try p.lexer.expectOrInsertSemicolon();
+ if (comptime is_typescript_enabled) {
+ if (opts.is_typescript_declare and p.lexer.isContextualKeyword("as")) {
+ // "export as namespace ns;"
+ try p.lexer.next();
+ try p.lexer.expectContextualKeyword("namespace");
+ try p.lexer.expect(T.t_identifier);
+ try p.lexer.expectOrInsertSemicolon();
- return p.s(S.TypeScript{}, loc);
+ return p.s(S.TypeScript{}, loc);
+ }
}
if (p.lexer.isContextualKeyword("async")) {
@@ -6726,6 +6794,9 @@ fn NewParser_(
return p.s(S.Continue{ .label = name }, loc);
},
.t_return => {
+ if (p.fn_or_arrow_data_parse.is_return_disallowed) {
+ try p.log.addRangeError(p.source, p.lexer.range(), "A return statement cannot be used here");
+ }
try p.lexer.next();
var value: ?Expr = null;
if ((p.lexer.token != .t_semicolon and
@@ -8004,6 +8075,7 @@ fn NewParser_(
if (first_non_identifier_loc.start != 0 and !p.lexer.isContextualKeyword("from")) {
const r = js_lexer.rangeOfIdentifier(p.source, first_non_identifier_loc);
try p.lexer.addRangeError(r, "Expected identifier but found \"{s}\"", .{p.source.textForRange(r)}, true);
+ return error.SyntaxError;
}
return ExportClauseResult{
@@ -8157,7 +8229,7 @@ fn NewParser_(
.delete_bare_name => "\"delete\" of a bare identifier",
.for_in_var_init => "Variable initializers within for-in loops",
.eval_or_arguments => try std.fmt.allocPrint(p.allocator, "Declarations with the name {s}", .{detail}),
- .reserved_word => try std.fmt.allocPrint(p.allocator, "{s} is a reserved word and", .{detail}),
+ .reserved_word => try std.fmt.allocPrint(p.allocator, "\"{s}\" is a reserved word and", .{detail}),
.legacy_octal_literal => "Legacy octal literals",
.legacy_octal_escape => "Legacy octal escape sequences",
.if_else_function_stmt => "Function declarations inside if statements",
@@ -8284,10 +8356,27 @@ fn NewParser_(
if (comptime !is_generated) {
switch (scope.canMergeSymbols(symbol.kind, kind, is_typescript_enabled)) {
.forbidden => {
- const r = js_lexer.rangeOfIdentifier(p.source, loc);
var notes = try p.allocator.alloc(logger.Data, 1);
- notes[0] = logger.rangeData(p.source, r, try std.fmt.allocPrint(p.allocator, "{s} has already been declared", .{name}));
- try p.log.addRangeErrorWithNotes(p.source, r, try std.fmt.allocPrint(p.allocator, "{s} was originally declared here", .{name}), notes);
+ notes[0] =
+ logger.rangeData(
+ p.source,
+ js_lexer.rangeOfIdentifier(p.source, existing.loc),
+ std.fmt.allocPrint(
+ p.allocator,
+ "{s} was originally declared here",
+ .{symbol.original_name},
+ ) catch unreachable,
+ );
+
+ p.log.addRangeErrorFmtWithNotes(
+ p.source,
+ js_lexer.rangeOfIdentifier(p.source, loc),
+ p.allocator,
+ notes,
+ "\"{s}\" has already been declared",
+ .{symbol.original_name},
+ ) catch unreachable;
+
return existing.ref;
},
.keep_existing => {
@@ -8432,6 +8521,8 @@ fn NewParser_(
// The ability to call "super()" is inherited by arrow functions
data.allow_super_call = p.fn_or_arrow_data_parse.allow_super_call;
+ data.allow_super_property = p.fn_or_arrow_data_parse.allow_super_property;
+ data.is_this_disallowed = p.fn_or_arrow_data_parse.is_this_disallowed;
if (p.lexer.token == .t_open_brace) {
var body = try p.parseFnBody(data);
@@ -8560,6 +8651,7 @@ fn NewParser_(
.t_identifier => {
if (level.lte(.assign)) {
// p.markLoweredSyntaxFeature();
+
const ref = try p.storeNameInRef(p.lexer.identifier);
var args = try p.allocator.alloc(G.Arg, 1);
args[0] = G.Arg{ .binding = p.b(
@@ -8612,6 +8704,7 @@ fn NewParser_(
pub const Backtracking = struct {
pub inline fn lexerBacktracker(p: *P, func: anytype) bool {
+ p.markTypeScriptOnly();
var old_lexer = std.mem.toBytes(p.lexer);
const old_log_disabled = p.lexer.is_log_disabled;
p.lexer.is_log_disabled = true;
@@ -8876,21 +8969,24 @@ fn NewParser_(
const wasIdentifier = p.lexer.token == .t_identifier;
const expr = try p.parseExpr(.comma);
- // Handle index signatures
- if (is_typescript_enabled and p.lexer.token == .t_colon and wasIdentifier and opts.is_class) {
- switch (expr.data) {
- .e_identifier => {
- try p.lexer.next();
- try p.skipTypeScriptType(.lowest);
- try p.lexer.expect(.t_close_bracket);
- try p.lexer.expect(.t_colon);
- try p.skipTypeScriptType(.lowest);
- try p.lexer.expectOrInsertSemicolon();
+ if (comptime is_typescript_enabled) {
- // Skip this property entirely
- return null;
- },
- else => {},
+ // Handle index signatures
+ if (p.lexer.token == .t_colon and wasIdentifier and opts.is_class) {
+ switch (expr.data) {
+ .e_identifier => {
+ try p.lexer.next();
+ try p.skipTypeScriptType(.lowest);
+ try p.lexer.expect(.t_close_bracket);
+ try p.lexer.expect(.t_colon);
+ try p.skipTypeScriptType(.lowest);
+ try p.lexer.expectOrInsertSemicolon();
+
+ // Skip this property entirely
+ return null;
+ },
+ else => {},
+ }
}
}
@@ -9022,8 +9118,11 @@ fn NewParser_(
(p.fn_or_arrow_data_parse.allow_yield != .allow_ident and
strings.eqlComptime(name, "yield")))
{
- // TODO: add fmt to addRangeError
- p.log.addRangeError(p.source, name_range, "Cannot use \"yield\" or \"await\" here.") catch unreachable;
+ if (strings.eqlComptime(name, "await")) {
+ p.log.addRangeError(p.source, name_range, "Cannot use \"await\" here") catch unreachable;
+ } else {
+ p.log.addRangeError(p.source, name_range, "Cannot use \"yield\" here") catch unreachable;
+ }
}
const ref = p.storeNameInRef(name) catch unreachable;
@@ -9050,7 +9149,7 @@ fn NewParser_(
},
}
- if (is_typescript_enabled) {
+ if (comptime is_typescript_enabled) {
// "class X { foo?: number }"
// "class X { foo!: number }"
if (opts.is_class and (p.lexer.token == .t_question or p.lexer.token == .t_exclamation)) {
@@ -9079,20 +9178,28 @@ fn NewParser_(
}
}
- // Skip over types
- if (is_typescript_enabled and p.lexer.token == .t_colon) {
- try p.lexer.next();
- try p.skipTypeScriptType(.lowest);
+ if (comptime is_typescript_enabled) {
+ // Skip over types
+ if (p.lexer.token == .t_colon) {
+ try p.lexer.next();
+ try p.skipTypeScriptType(.lowest);
+ }
}
if (p.lexer.token == .t_equals) {
+ if (comptime is_typescript_enabled) {
+ if (!opts.declare_range.isEmpty()) {
+ try p.log.addRangeError(p.source, p.lexer.range(), "Class fields that use \"declare\" cannot be initialized");
+ }
+ }
+
try p.lexer.next();
initializer = try p.parseExpr(.comma);
}
// Special-case private identifiers
switch (key.data) {
- .e_private_identifier => |private| {
+ .e_private_identifier => |*private| {
const name = p.loadNameFromRef(private.ref);
if (strings.eqlComptime(name, "#constructor")) {
p.log.addRangeError(p.source, key_range, "Invalid field name \"#constructor\"") catch unreachable;
@@ -9203,7 +9310,7 @@ fn NewParser_(
// Special-case private identifiers
switch (key.data) {
- .e_private_identifier => |private| {
+ .e_private_identifier => |*private| {
var declare: Symbol.Kind = undefined;
var suffix: string = "";
switch (kind) {
@@ -9285,20 +9392,22 @@ fn NewParser_(
// This seems kind of wasteful to me but it's what the official compiler
// does and it probably doesn't have that high of a performance overhead
// because "extends" clauses aren't that frequent, so it should be ok.
- if (is_typescript_enabled) {
+ if (comptime is_typescript_enabled) {
_ = try p.skipTypeScriptTypeArguments(false); // isInsideJSXElement
}
}
- if (is_typescript_enabled and p.lexer.isContextualKeyword("implements")) {
- try p.lexer.next();
+ if (comptime is_typescript_enabled) {
+ if (p.lexer.isContextualKeyword("implements")) {
+ try p.lexer.next();
- while (true) {
- try p.skipTypeScriptType(.lowest);
- if (p.lexer.token != .t_comma) {
- break;
+ while (true) {
+ try p.skipTypeScriptType(.lowest);
+ if (p.lexer.token != .t_comma) {
+ break;
+ }
+ try p.lexer.next();
}
- try p.lexer.next();
}
}
@@ -9372,6 +9481,7 @@ fn NewParser_(
}
pub fn skipTypeScriptTypeArguments(p: *P, comptime isInsideJSXElement: bool) anyerror!bool {
+ p.markTypeScriptOnly();
switch (p.lexer.token) {
.t_less_than, .t_less_than_equals, .t_less_than_less_than, .t_less_than_less_than_equals => {},
else => {
@@ -9594,8 +9704,9 @@ fn NewParser_(
},
.t_less_than => {
// "a?.<T>()"
- if (!is_typescript_enabled) {
+ if (comptime !is_typescript_enabled) {
try p.lexer.expected(.t_identifier);
+ return error.SyntaxError;
}
_ = try p.skipTypeScriptTypeArguments(false);
@@ -10300,7 +10411,9 @@ fn NewParser_(
}
},
.t_dot, .t_open_bracket => {
- return p.e(E.Super{}, loc);
+ if (p.fn_or_arrow_data_parse.allow_super_property) {
+ return p.e(E.Super{}, loc);
+ }
},
else => {},
}
@@ -10340,11 +10453,14 @@ fn NewParser_(
return p.e(E.Null{}, loc);
},
.t_this => {
+ if (p.fn_or_arrow_data_parse.is_this_disallowed) {
+ p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"this\" here") catch unreachable;
+ }
try p.lexer.next();
return Expr{ .data = Prefill.Data.This, .loc = loc };
},
.t_private_identifier => {
- if (!p.allow_private_identifiers or !p.allow_in) {
+ if (!p.allow_private_identifiers or !p.allow_in or level.gte(.compare)) {
try p.lexer.unexpected();
return error.SyntaxError;
}
@@ -10377,11 +10493,11 @@ fn NewParser_(
.is_await => {
switch (p.fn_or_arrow_data_parse.allow_await) {
.forbid_all => {
- p.log.addRangeError(p.source, name_range, "The keyword \"await\" cannot be used here.") catch unreachable;
+ p.log.addRangeError(p.source, name_range, "The keyword \"await\" cannot be used here") catch unreachable;
},
.allow_expr => {
if (AsyncPrefixExpression.find(raw) != .is_await) {
- p.log.addRangeError(p.source, name_range, "The keyword \"await\" cannot be escaped.") catch unreachable;
+ p.log.addRangeError(p.source, name_range, "The keyword \"await\" cannot be escaped") catch unreachable;
} else {
if (p.fn_or_arrow_data_parse.is_top_level) {
p.top_level_await_keyword = name_range;
@@ -10530,12 +10646,14 @@ fn NewParser_(
try p.lexer.unexpected();
return error.SyntaxError;
}
- // TODO: add error deleting private identifier
- // const private = value.data.e_private_identifier;
- // if (private) |private| {
- // const name = p.loadNameFromRef(private.ref);
- // p.log.addRangeError(index.loc, )
- // }
+ if (value.data == .e_index) {
+ if (value.data.e_index.index.data == .e_private_identifier) {
+ const private = value.data.e_index.index.data.e_private_identifier;
+ const name = p.loadNameFromRef(private.ref);
+ const range = logger.Range{ .loc = value.loc, .len = @intCast(i32, name.len) };
+ p.log.addRangeErrorFmt(p.source, range, p.allocator, "Deleting the private name \"{s}\" is forbidden", .{name}) catch unreachable;
+ }
+ }
return p.e(E.Unary{ .op = .un_delete, .value = value }, loc);
},
@@ -10599,9 +10717,22 @@ fn NewParser_(
_ = p.pushScopeForParsePass(.class_name, loc) catch unreachable;
// Parse an optional class name
- if (p.lexer.token == .t_identifier and !js_lexer.StrictModeReservedWords.has(p.lexer.identifier)) {
- name = js_ast.LocRef{ .loc = p.lexer.loc(), .ref = p.newSymbol(.other, p.lexer.identifier) catch unreachable };
- try p.lexer.next();
+ if (p.lexer.token == .t_identifier) {
+ const name_text = p.lexer.identifier;
+ if (!is_typescript_enabled or !strings.eqlComptime(name_text, "implements")) {
+ if (p.fn_or_arrow_data_parse.allow_await != .allow_ident and strings.eqlComptime(name_text, "await")) {
+ p.log.addRangeError(p.source, p.lexer.range(), "Cannot use \"await\" as an identifier here") catch unreachable;
+ }
+
+ name = js_ast.LocRef{
+ .loc = p.lexer.loc(),
+ .ref = p.newSymbol(
+ .other,
+ name_text,
+ ) catch unreachable,
+ };
+ try p.lexer.next();
+ }
}
// Even anonymous classes can have TypeScript type parameters
@@ -10625,9 +10756,10 @@ fn NewParser_(
try p.lexer.unexpected();
return error.SyntaxError;
}
+ const range = logger.Range{ .loc = loc, .len = p.lexer.range().end().start - loc.start };
try p.lexer.next();
- return p.e(E.NewTarget{}, loc);
+ return p.e(E.NewTarget{ .range = range }, loc);
}
const target = try p.parseExprWithFlags(.member, flags);
@@ -11192,7 +11324,7 @@ fn NewParser_(
const r = p.lexer.range();
// Not dealing with this right now.
try p.log.addRangeError(p.source, r, "Invalid JSX escape - use XML entity codes quotes or pass a JavaScript string instead");
- p.panic("", .{});
+ return error.SyntaxError;
}
// A slash here is a self-closing element
@@ -11626,7 +11758,14 @@ fn NewParser_(
// Output.print("\nVisit: {s} - {d}\n", .{ @tagName(expr.data), expr.loc.start });
switch (expr.data) {
- .e_null, .e_super, .e_boolean, .e_big_int, .e_reg_exp, .e_new_target, .e_undefined => {},
+ .e_null, .e_super, .e_boolean, .e_big_int, .e_reg_exp, .e_undefined => {},
+
+ .e_new_target => |target| {
+ if (!p.fn_only_data_visit.is_new_target_allowed) {
+ p.log.addRangeError(p.source, target.range, "Cannot use \"new.target\" here") catch unreachable;
+ }
+ },
+
.e_string => {
// If you're using this, you're probably not using 0-prefixed legacy octal notation
@@ -11725,9 +11864,7 @@ fn NewParser_(
.was_originally_identifier = true,
});
},
- .e_private_identifier => {
- p.panic("Unexpected private identifier. This is an internal error - not your fault.", .{});
- },
+
.e_jsx_element => |e_| {
switch (comptime jsx_transform_type) {
.macro => {
@@ -12015,20 +12152,23 @@ fn NewParser_(
.e_binary => |e_| {
switch (e_.left.data) {
// Special-case private identifiers
- .e_private_identifier => |private| {
+ .e_private_identifier => |_private| {
if (e_.op == .bin_in) {
+ var private = _private;
const name = p.loadNameFromRef(private.ref);
const result = p.findSymbol(e_.left.loc, name) catch unreachable;
private.ref = result.ref;
// Unlike regular identifiers, there are no unbound private identifiers
- const symbol: Symbol = p.symbols.items[result.ref.innerIndex()];
- if (!Symbol.isKindPrivate(symbol.kind)) {
+ const kind: Symbol.Kind = p.symbols.items[result.ref.innerIndex()].kind;
+ if (!Symbol.isKindPrivate(kind)) {
const r = logger.Range{ .loc = e_.left.loc, .len = @intCast(i32, name.len) };
p.log.addRangeErrorFmt(p.source, r, p.allocator, "Private name \"{s}\" must be declared in an enclosing class", .{name}) catch unreachable;
}
e_.right = p.visitExpr(e_.right);
+ e_.left = .{ .data = .{ .e_private_identifier = private }, .loc = e_.left.loc };
+
// privateSymbolNeedsToBeLowered
return expr;
}
@@ -12448,8 +12588,40 @@ fn NewParser_(
.has_chain_parent = (e_.optional_chain orelse js_ast.OptionalChain.start) == js_ast.OptionalChain.ccontinue,
});
e_.target = target;
- const index = p.visitExpr(e_.index);
- e_.index = index;
+
+ switch (e_.index.data) {
+ .e_private_identifier => |_private| {
+ var private = _private;
+ const name = p.loadNameFromRef(private.ref);
+ const result = p.findSymbol(e_.index.loc, name) catch unreachable;
+ private.ref = result.ref;
+
+ // Unlike regular identifiers, there are no unbound private identifiers
+ const kind: Symbol.Kind = p.symbols.items[result.ref.innerIndex()].kind;
+ var r: logger.Range = undefined;
+ if (!Symbol.isKindPrivate(kind)) {
+ r = logger.Range{ .loc = e_.index.loc, .len = @intCast(i32, name.len) };
+ p.log.addRangeErrorFmt(p.source, r, p.allocator, "Private name \"{s}\" must be declared in an enclosing class", .{name}) catch unreachable;
+ } else {
+ if (in.assign_target != .none and (kind == .private_method or kind == .private_static_method)) {
+ r = logger.Range{ .loc = e_.index.loc, .len = @intCast(i32, name.len) };
+ p.log.addRangeWarningFmt(p.source, r, p.allocator, "Writing to read-only method \"{s}\" will throw", .{name}) catch unreachable;
+ } else if (in.assign_target != .none and (kind == .private_get or kind == .private_static_get)) {
+ r = logger.Range{ .loc = e_.index.loc, .len = @intCast(i32, name.len) };
+ p.log.addRangeWarningFmt(p.source, r, p.allocator, "Writing to getter-only property \"{s}\" will throw", .{name}) catch unreachable;
+ } else if (in.assign_target != .replace and (kind == .private_set or kind == .private_static_set)) {
+ r = logger.Range{ .loc = e_.index.loc, .len = @intCast(i32, name.len) };
+ p.log.addRangeWarningFmt(p.source, r, p.allocator, "Reading from setter-only property \"{s}\" will throw", .{name}) catch unreachable;
+ }
+ }
+
+ e_.index = .{ .data = .{ .e_private_identifier = private }, .loc = e_.index.loc };
+ },
+ else => {
+ const index = p.visitExpr(e_.index);
+ e_.index = index;
+ },
+ }
if (e_.optional_chain == null and e_.index.data == .e_string and e_.index.data.e_string.isUTF8()) {
const literal = e_.index.data.e_string.utf8;
@@ -14951,7 +15123,7 @@ fn NewParser_(
}
const r = js_lexer.rangeOfIdentifier(p.source, loc);
- p.log.addRangeErrorFmt(p.source, r, p.allocator, "There is no containing label named {s}", .{name}) catch unreachable;
+ p.log.addRangeErrorFmt(p.source, r, p.allocator, "There is no containing label named \"{s}\"", .{name}) catch unreachable;
// Allocate an "unbound" symbol
var ref = p.newSymbol(.unbound, name) catch unreachable;
@@ -15051,6 +15223,7 @@ fn NewParser_(
const old_is_this_captured = p.fn_only_data_visit.is_this_nested;
const old_this = p.fn_only_data_visit.this_class_static_ref;
p.fn_only_data_visit.is_this_nested = true;
+ p.fn_only_data_visit.is_new_target_allowed = true;
p.fn_only_data_visit.this_class_static_ref = null;
defer p.fn_only_data_visit.is_this_nested = old_is_this_captured;
defer p.fn_only_data_visit.this_class_static_ref = old_this;
@@ -15388,7 +15561,8 @@ fn NewParser_(
// attempt to convert the expressions to bindings first before deciding
// whether this is an arrow function, and only pick an arrow function if
// there were no conversion errors.
- if (p.lexer.token == .t_equals_greater_than or (invalidLog.items.len == 0 and
+ if (p.lexer.token == .t_equals_greater_than or ((comptime is_typescript_enabled) and
+ invalidLog.items.len == 0 and
p.trySkipTypeScriptArrowReturnTypeWithBacktracking()) or
opts.force_arrow_fn)
{