fix(rslint_parser): Fix private name with whitespace
Trivia isn't allowed between a `#` token and the private name identifier. This PR changes the
*private member name* and *private name* implementation to emit an error if there's whitespace between the `#` and identifier name.
MODIFY
crates/rslint_parser/src/syntax/class.rs
crates/rslint_parser/src/syntax/class.rs
@@ -14,7 +14,7 @@ use crate::syntax::js_parse_error::expected_binding;
use crate::syntax::object::{
is_at_literal_member_name, parse_computed_member_name, parse_literal_member_name,
};
-use crate::syntax::stmt::{is_semi, optional_semi, parse_statements, StatementContext};
+use crate::syntax::stmt::{optional_semi, parse_statements, StatementContext};
use crate::syntax::typescript::{
maybe_ts_type_annotation, ts_heritage_clause, ts_modifier, ts_type_params,
DISALLOWED_TYPE_NAMES,
@@ -22,8 +22,7 @@ use crate::syntax::typescript::{
use crate::JsSyntaxFeature::TypeScript;
use crate::ParsedSyntax::{Absent, Present};
use crate::{
- CompletedMarker, Event, Marker, ParseNodeList, ParseRecovery, Parser, StrictMode,
- SyntaxFeature, TokenSet,
+ CompletedMarker, Event, Marker, ParseNodeList, ParseRecovery, Parser, StrictMode, SyntaxFeature,
};
use rome_rowan::SyntaxKind;
use rslint_syntax::JsSyntaxKind::*;
@@ -377,8 +376,8 @@ fn parse_class_member_impl(
member_marker: Marker,
modifiers: ClassMemberModifiers,
) -> ParsedSyntax {
+ let start_token_pos = p.token_pos();
let generator_range = p.cur_tok().range();
- let checkpoint = p.checkpoint();
// Seems like we're at a generator method
if p.at(T![*]) {
@@ -536,44 +535,8 @@ fn parse_class_member_impl(
};
}
- if let Some(member_name) = member_name {
- // test property_class_member
- // class foo {
- // property
- // declare;
- // initializedProperty = "a"
- // "a";
- // 5
- // ["a" + "b"]
- // static staticProperty
- // static staticInitializedProperty = 1
- // #private
- // #privateInitialized = "a"
- // static #staticPrivate
- // static #staticPrivateInitializedProperty = 1
- // }
- if is_at_property_class_member(p, 0) {
- // test_err class_declare_member
- // class B { declare foo }
- let property = if modifiers.get_range(ModifierKind::Declare).is_some() {
- property_declaration_class_member_body(p, member_marker, member_name.kind())
- } else {
- parse_property_class_member_body(p, member_marker)
- };
-
- return property.map(|mut property| {
- if !property.kind().is_unknown() && is_constructor {
- let err = p
- .err_builder("class properties may not be called `constructor`")
- .primary(property.range(p), "");
-
- p.error(err);
- property.change_to_unknown(p);
- }
- property
- });
- }
-
+ match member_name {
+ Some(member_name) => {
if member_name.kind() == JS_LITERAL_MEMBER_NAME {
let is_at_line_break_or_generator = p.has_linebreak_before_n(0) && p.at(T![*]);
let member_name_text = member_name.text(p);
@@ -683,19 +646,57 @@ fn parse_class_member_impl(
return Present(completed);
}
- }
- }
+ };
- // if we're arrived here it means that the parser has advanced but we have no clue what kind of member
- // this is supposed to be. Let's rewind the parser so that the error recovery in the members loop
- // kicks in and any potential `missing` slots get removed.
+ // test property_class_member
+ // class foo {
+ // property
+ // declare;
+ // initializedProperty = "a"
+ // "a";
+ // 5
+ // ["a" + "b"]
+ // static staticProperty
+ // static staticInitializedProperty = 1
+ // #private
+ // #privateInitialized = "a"
+ // static #staticPrivate
+ // static #staticPrivateInitializedProperty = 1
+ // }
+ //
+ // test_err class_declare_member
+ // class B { declare foo }
+ let property = if modifiers.get_range(ModifierKind::Declare).is_some() {
+ property_declaration_class_member_body(p, member_marker, member_name.kind())
+ } else {
+ parse_property_class_member_body(p, member_marker)
+ };
+ property.map(|mut property| {
+ if !property.kind().is_unknown() && is_constructor {
+ let err = p
+ .err_builder("class properties may not be called `constructor`")
+ .primary(property.range(p), "");
+
+ p.error(err);
+ property.change_to_unknown(p);
+ }
+ property
+ })
+ }
+ None => {
// test_err block_stmt_in_class
// class S{{}}
- p.rewind(checkpoint);
+ debug_assert_eq!(
+ p.token_pos(),
+ start_token_pos,
+ "Parser shouldn't be progressing when returning Absent"
+ );
member_marker.abandon(p);
Absent
}
+ }
+}
fn is_at_static_initialization_block_class_member(p: &Parser) -> bool {
p.at(T![ident]) && p.cur_src() == "static" && p.nth_at(1, T!['{'])
@@ -1006,10 +1007,28 @@ pub(crate) fn parse_private_class_member_name(p: &mut Parser) -> ParsedSyntax {
return Absent;
}
let m = p.start();
+ let hash_end = p.cur_tok().range().end;
+
p.expect(T![#]);
+
+ if p.at(T![ident]) && hash_end != p.cur_tok().start() {
+ // test_err private_member_name_with_space
+ // class A {
+ // # test;
+ // }
+ p.error(
+ p.err_builder("Unexpected space or comment between `#` and identifier")
+ .primary(
+ hash_end..p.cur_tok().start(),
+ "remove the space or comment here",
+ ),
+ );
+ Present(m.complete(p, JS_UNKNOWN))
+ } else {
p.expect(T![ident]);
Present(m.complete(p, JS_PRIVATE_CLASS_MEMBER_NAME))
}
+}
fn parse_access_modifier(p: &mut Parser) -> Option<Range<usize>> {
let kind = match p.cur_src() {
@@ -1024,18 +1043,6 @@ fn parse_access_modifier(p: &mut Parser) -> Option<Range<usize>> {
Some(range)
}
-const PROPERTY_START_SET: TokenSet = token_set![T![!], T![:], T![=], T!['}'], T![;]];
-
-/// Tests if the parser is currently (considering the offset) at the body of a property member.
-/// The method assumes that the identifier has already been consumed.
-fn is_at_property_class_member(p: &Parser, mut offset: usize) -> bool {
- if p.nth_at(offset, T![?]) {
- offset += 1;
- }
-
- PROPERTY_START_SET.contains(p.nth(offset)) || is_semi(p, offset)
-}
-
fn is_at_method_class_member(p: &Parser, mut offset: usize) -> bool {
if p.nth_at(offset, T![?]) {
offset += 1;

MODIFY
crates/rslint_parser/src/syntax/expr.rs
crates/rslint_parser/src/syntax/expr.rs
@@ -656,10 +656,24 @@ fn parse_private_name(p: &mut Parser) -> ParsedSyntax {
}
let m = p.start();
+ let hash_end = p.cur_tok().range().end;
p.expect(T![#]);
+
+ if p.at(T![ident]) && hash_end != p.cur_tok().start() {
+ // test_err private_name_with_space
+ // class A {
+ // # test;
+ // }
+ p.error(
+ p.err_builder("Unexpected space or comment between `#` and identifier")
+ .primary(hash_end..p.cur_tok().start(), "remove the space here"),
+ );
+ Present(m.complete(p, JS_UNKNOWN))
+ } else {
p.expect(T![ident]);
Present(m.complete(p, JS_PRIVATE_NAME))
}
+}
pub(super) fn parse_any_name(p: &mut Parser) -> ParsedSyntax {
match p.cur() {

ADD
crates/rslint_parser/test_data/inline/err/private_member_name_with_space.js
+class A {
+ # test;
+}

ADD
crates/rslint_parser/test_data/inline/err/private_name_with_space.js
+class A {
+ # test;
+}

fix(rslint_parser): Fix private name with whitespace (#1952)

Trivia isn't allowed between a `#` token and the private name identifier. This PR changes the
*private member name* and *private name* implementation to emit an error if there's whitespace between the `#` and identifier name.