feat(rslint_parser): Private field presence check
Implements `#a in obj` support.
I decided to split out `JsInExpression` and `JsInstanceofExpression` because private names are only ever valid inside `in` expression but not any other binary expressions (as proposed in
MODIFY
crates/rome_formatter/src/ts/expressions/expression.rs
crates/rome_formatter/src/ts/expressions/expression.rs
@@ -3,10 +3,10 @@ use crate::{
ToFormatElement,
};
use rslint_parser::ast::{
- JsAnyExpression, JsAssignmentExpression, JsAwaitExpression, JsBinaryExpression,
- JsComputedMemberExpression, JsConditionalExpression, JsLogicalExpression, JsNewExpression,
- JsParenthesizedExpression, JsThisExpression, JsUnaryExpression, JsYieldArgument,
- JsYieldExpression, NewTarget,
+ JsAnyExpression, JsAnyInProperty, JsAssignmentExpression, JsAwaitExpression,
+ JsBinaryExpression, JsComputedMemberExpression, JsConditionalExpression, JsInExpression,
+ JsInstanceofExpression, JsLogicalExpression, JsNewExpression, JsParenthesizedExpression,
+ JsThisExpression, JsUnaryExpression, JsYieldArgument, JsYieldExpression, NewTarget,
};
use rslint_parser::{token_set, T};
@@ -87,6 +87,10 @@ impl ToFormatElement for JsAnyExpression {
logical_expression.to_format_element(formatter)
}
JsAnyExpression::JsSuperExpression(expr) => expr.to_format_element(formatter),
+ JsAnyExpression::JsInExpression(expression) => expression.to_format_element(formatter),
+ JsAnyExpression::JsInstanceofExpression(expression) => {
+ expression.to_format_element(formatter)
+ }
}
}
}
@@ -262,3 +266,36 @@ impl ToFormatElement for JsLogicalExpression {
])
}
}
+
+impl ToFormatElement for JsInstanceofExpression {
+ fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
+ Ok(format_elements![
+ formatter.format_node(self.left()?)?,
+ space_token(),
+ formatter.format_token(&self.instanceof_token()?)?,
+ space_token(),
+ formatter.format_node(self.right()?)?,
+ ])
+ }
+}
+
+impl ToFormatElement for JsInExpression {
+ fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
+ Ok(format_elements![
+ formatter.format_node(self.property()?)?,
+ space_token(),
+ formatter.format_token(&self.in_token()?)?,
+ space_token(),
+ formatter.format_node(self.object()?)?,
+ ])
+ }
+}
+
+impl ToFormatElement for JsAnyInProperty {
+ fn to_format_element(&self, formatter: &Formatter) -> FormatResult<FormatElement> {
+ match self {
+ JsAnyInProperty::JsAnyExpression(expression) => expression.to_format_element(formatter),
+ JsAnyInProperty::JsPrivateName(name) => name.to_format_element(formatter),
+ }
+ }
+}

MODIFY
crates/rslint_parser/src/ast/generated/nodes.rs
crates/rslint_parser/src/ast/generated/nodes.rs
@@ -1205,6 +1205,21 @@ impl JsImportNamespaceClause {
pub fn assertion(&self) -> Option<JsImportAssertion> { support::node(&self.syntax, 5usize) }
}
#[derive(Clone, PartialEq, Eq, Hash)]
+pub struct JsInExpression {
+ pub(crate) syntax: SyntaxNode,
+}
+impl JsInExpression {
+ pub fn property(&self) -> SyntaxResult<JsAnyInProperty> {
+ support::required_node(&self.syntax, 0usize)
+ }
+ pub fn in_token(&self) -> SyntaxResult<SyntaxToken> {
+ support::required_token(&self.syntax, 1usize)
+ }
+ pub fn object(&self) -> SyntaxResult<JsAnyExpression> {
+ support::required_node(&self.syntax, 2usize)
+ }
+}
+#[derive(Clone, PartialEq, Eq, Hash)]
pub struct JsInitializerClause {
pub(crate) syntax: SyntaxNode,
}
@@ -1217,6 +1232,21 @@ impl JsInitializerClause {
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
+pub struct JsInstanceofExpression {
+ pub(crate) syntax: SyntaxNode,
+}
+impl JsInstanceofExpression {
+ pub fn left(&self) -> SyntaxResult<JsAnyExpression> {
+ support::required_node(&self.syntax, 0usize)
+ }
+ pub fn instanceof_token(&self) -> SyntaxResult<SyntaxToken> {
+ support::required_token(&self.syntax, 1usize)
+ }
+ pub fn right(&self) -> SyntaxResult<JsAnyExpression> {
+ support::required_node(&self.syntax, 2usize)
+ }
+}
+#[derive(Clone, PartialEq, Eq, Hash)]
pub struct JsLabeledStatement {
pub(crate) syntax: SyntaxNode,
}
@@ -2970,6 +3000,8 @@ pub enum JsAnyExpression {
JsFunctionExpression(JsFunctionExpression),
JsIdentifierExpression(JsIdentifierExpression),
JsImportCallExpression(JsImportCallExpression),
+ JsInExpression(JsInExpression),
+ JsInstanceofExpression(JsInstanceofExpression),
JsLogicalExpression(JsLogicalExpression),
JsNewExpression(JsNewExpression),
JsObjectExpression(JsObjectExpression),
@@ -3018,6 +3050,11 @@ pub enum JsAnyImportAssertionEntry {
JsUnknownImportAssertionEntry(JsUnknownImportAssertionEntry),
}
#[derive(Clone, PartialEq, Eq, Hash)]
+pub enum JsAnyInProperty {
+ JsAnyExpression(JsAnyExpression),
+ JsPrivateName(JsPrivateName),
+}
+#[derive(Clone, PartialEq, Eq, Hash)]
pub enum JsAnyLiteralExpression {
JsBigIntLiteralExpression(JsBigIntLiteralExpression),
JsBooleanLiteralExpression(JsBooleanLiteralExpression),
@@ -5809,6 +5846,32 @@ impl From<JsImportNamespaceClause> for SyntaxNode {
impl From<JsImportNamespaceClause> for SyntaxElement {
fn from(n: JsImportNamespaceClause) -> SyntaxElement { n.syntax.into() }
}
+impl AstNode for JsInExpression {
+ fn can_cast(kind: JsSyntaxKind) -> bool { kind == JS_IN_EXPRESSION }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ if Self::can_cast(syntax.kind()) {
+ Some(Self { syntax })
+ } else {
+ None
+ }
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl std::fmt::Debug for JsInExpression {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("JsInExpression")
+ .field("property", &support::DebugSyntaxResult(self.property()))
+ .field("in_token", &support::DebugSyntaxResult(self.in_token()))
+ .field("object", &support::DebugSyntaxResult(self.object()))
+ .finish()
+ }
+}
+impl From<JsInExpression> for SyntaxNode {
+ fn from(n: JsInExpression) -> SyntaxNode { n.syntax }
+}
+impl From<JsInExpression> for SyntaxElement {
+ fn from(n: JsInExpression) -> SyntaxElement { n.syntax.into() }
+}
impl AstNode for JsInitializerClause {
fn can_cast(kind: JsSyntaxKind) -> bool { kind == JS_INITIALIZER_CLAUSE }
fn cast(syntax: SyntaxNode) -> Option<Self> {
@@ -5834,6 +5897,35 @@ impl From<JsInitializerClause> for SyntaxNode {
impl From<JsInitializerClause> for SyntaxElement {
fn from(n: JsInitializerClause) -> SyntaxElement { n.syntax.into() }
}
+impl AstNode for JsInstanceofExpression {
+ fn can_cast(kind: JsSyntaxKind) -> bool { kind == JS_INSTANCEOF_EXPRESSION }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ if Self::can_cast(syntax.kind()) {
+ Some(Self { syntax })
+ } else {
+ None
+ }
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl std::fmt::Debug for JsInstanceofExpression {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("JsInstanceofExpression")
+ .field("left", &support::DebugSyntaxResult(self.left()))
+ .field(
+ "instanceof_token",
+ &support::DebugSyntaxResult(self.instanceof_token()),
+ )
+ .field("right", &support::DebugSyntaxResult(self.right()))
+ .finish()
+ }
+}
+impl From<JsInstanceofExpression> for SyntaxNode {
+ fn from(n: JsInstanceofExpression) -> SyntaxNode { n.syntax }
+}
+impl From<JsInstanceofExpression> for SyntaxElement {
+ fn from(n: JsInstanceofExpression) -> SyntaxElement { n.syntax.into() }
+}
impl AstNode for JsLabeledStatement {
fn can_cast(kind: JsSyntaxKind) -> bool { kind == JS_LABELED_STATEMENT }
fn cast(syntax: SyntaxNode) -> Option<Self> {
@@ -11018,6 +11110,14 @@ impl From<JsImportCallExpression> for JsAnyExpression {
JsAnyExpression::JsImportCallExpression(node)
}
}
+impl From<JsInExpression> for JsAnyExpression {
+ fn from(node: JsInExpression) -> JsAnyExpression { JsAnyExpression::JsInExpression(node) }
+}
+impl From<JsInstanceofExpression> for JsAnyExpression {
+ fn from(node: JsInstanceofExpression) -> JsAnyExpression {
+ JsAnyExpression::JsInstanceofExpression(node)
+ }
+}
impl From<JsLogicalExpression> for JsAnyExpression {
fn from(node: JsLogicalExpression) -> JsAnyExpression {
JsAnyExpression::JsLogicalExpression(node)
@@ -11104,6 +11204,8 @@ impl AstNode for JsAnyExpression {
| JS_FUNCTION_EXPRESSION
| JS_IDENTIFIER_EXPRESSION
| JS_IMPORT_CALL_EXPRESSION
+ | JS_IN_EXPRESSION
+ | JS_INSTANCEOF_EXPRESSION
| JS_LOGICAL_EXPRESSION
| JS_NEW_EXPRESSION
| JS_OBJECT_EXPRESSION
@@ -11157,6 +11259,10 @@ impl AstNode for JsAnyExpression {
JS_IMPORT_CALL_EXPRESSION => {
JsAnyExpression::JsImportCallExpression(JsImportCallExpression { syntax })
}
+ JS_IN_EXPRESSION => JsAnyExpression::JsInExpression(JsInExpression { syntax }),
+ JS_INSTANCEOF_EXPRESSION => {
+ JsAnyExpression::JsInstanceofExpression(JsInstanceofExpression { syntax })
+ }
JS_LOGICAL_EXPRESSION => {
JsAnyExpression::JsLogicalExpression(JsLogicalExpression { syntax })
}
@@ -11217,6 +11323,8 @@ impl AstNode for JsAnyExpression {
JsAnyExpression::JsFunctionExpression(it) => &it.syntax,
JsAnyExpression::JsIdentifierExpression(it) => &it.syntax,
JsAnyExpression::JsImportCallExpression(it) => &it.syntax,
+ JsAnyExpression::JsInExpression(it) => &it.syntax,
+ JsAnyExpression::JsInstanceofExpression(it) => &it.syntax,
JsAnyExpression::JsLogicalExpression(it) => &it.syntax,
JsAnyExpression::JsNewExpression(it) => &it.syntax,
JsAnyExpression::JsObjectExpression(it) => &it.syntax,
@@ -11256,6 +11364,8 @@ impl std::fmt::Debug for JsAnyExpression {
JsAnyExpression::JsFunctionExpression(it) => std::fmt::Debug::fmt(it, f),
JsAnyExpression::JsIdentifierExpression(it) => std::fmt::Debug::fmt(it, f),
JsAnyExpression::JsImportCallExpression(it) => std::fmt::Debug::fmt(it, f),
+ JsAnyExpression::JsInExpression(it) => std::fmt::Debug::fmt(it, f),
+ JsAnyExpression::JsInstanceofExpression(it) => std::fmt::Debug::fmt(it, f),
JsAnyExpression::JsLogicalExpression(it) => std::fmt::Debug::fmt(it, f),
JsAnyExpression::JsNewExpression(it) => std::fmt::Debug::fmt(it, f),
JsAnyExpression::JsObjectExpression(it) => std::fmt::Debug::fmt(it, f),
@@ -11294,6 +11404,8 @@ impl From<JsAnyExpression> for SyntaxNode {
JsAnyExpression::JsFunctionExpression(it) => it.into(),
JsAnyExpression::JsIdentifierExpression(it) => it.into(),
JsAnyExpression::JsImportCallExpression(it) => it.into(),
+ JsAnyExpression::JsInExpression(it) => it.into(),
+ JsAnyExpression::JsInstanceofExpression(it) => it.into(),
JsAnyExpression::JsLogicalExpression(it) => it.into(),
JsAnyExpression::JsNewExpression(it) => it.into(),
JsAnyExpression::JsObjectExpression(it) => it.into(),
@@ -11646,6 +11758,58 @@ impl From<JsAnyImportAssertionEntry> for SyntaxElement {
node.into()
}
}
+impl From<JsPrivateName> for JsAnyInProperty {
+ fn from(node: JsPrivateName) -> JsAnyInProperty { JsAnyInProperty::JsPrivateName(node) }
+}
+impl AstNode for JsAnyInProperty {
+ fn can_cast(kind: JsSyntaxKind) -> bool {
+ match kind {
+ JS_PRIVATE_NAME => true,
+ k if JsAnyExpression::can_cast(k) => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ let res = match syntax.kind() {
+ JS_PRIVATE_NAME => JsAnyInProperty::JsPrivateName(JsPrivateName { syntax }),
+ _ => {
+ if let Some(js_any_expression) = JsAnyExpression::cast(syntax) {
+ return Some(JsAnyInProperty::JsAnyExpression(js_any_expression));
+ }
+ return None;
+ }
+ };
+ Some(res)
+ }
+ fn syntax(&self) -> &SyntaxNode {
+ match self {
+ JsAnyInProperty::JsPrivateName(it) => &it.syntax,
+ JsAnyInProperty::JsAnyExpression(it) => it.syntax(),
+ }
+ }
+}
+impl std::fmt::Debug for JsAnyInProperty {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ JsAnyInProperty::JsAnyExpression(it) => std::fmt::Debug::fmt(it, f),
+ JsAnyInProperty::JsPrivateName(it) => std::fmt::Debug::fmt(it, f),
+ }
+ }
+}
+impl From<JsAnyInProperty> for SyntaxNode {
+ fn from(n: JsAnyInProperty) -> SyntaxNode {
+ match n {
+ JsAnyInProperty::JsAnyExpression(it) => it.into(),
+ JsAnyInProperty::JsPrivateName(it) => it.into(),
+ }
+ }
+}
+impl From<JsAnyInProperty> for SyntaxElement {
+ fn from(n: JsAnyInProperty) -> SyntaxElement {
+ let node: SyntaxNode = n.into();
+ node.into()
+ }
+}
impl From<JsBigIntLiteralExpression> for JsAnyLiteralExpression {
fn from(node: JsBigIntLiteralExpression) -> JsAnyLiteralExpression {
JsAnyLiteralExpression::JsBigIntLiteralExpression(node)
@@ -13423,6 +13587,11 @@ impl std::fmt::Display for JsAnyImportAssertionEntry {
std::fmt::Display::fmt(self.syntax(), f)
}
}
+impl std::fmt::Display for JsAnyInProperty {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ std::fmt::Display::fmt(self.syntax(), f)
+ }
+}
impl std::fmt::Display for JsAnyLiteralExpression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
@@ -13908,11 +14077,21 @@ impl std::fmt::Display for JsImportNamespaceClause {
std::fmt::Display::fmt(self.syntax(), f)
}
}
+impl std::fmt::Display for JsInExpression {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ std::fmt::Display::fmt(self.syntax(), f)
+ }
+}
impl std::fmt::Display for JsInitializerClause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
}
}
+impl std::fmt::Display for JsInstanceofExpression {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ std::fmt::Display::fmt(self.syntax(), f)
+ }
+}
impl std::fmt::Display for JsLabeledStatement {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
@@ -16001,9 +16180,15 @@ impl Debug for DebugSyntaxElement {
JS_IMPORT_NAMESPACE_CLAUSE => {
std::fmt::Debug::fmt(&JsImportNamespaceClause::cast(node.clone()).unwrap(), f)
}
+ JS_IN_EXPRESSION => {
+ std::fmt::Debug::fmt(&JsInExpression::cast(node.clone()).unwrap(), f)
+ }
JS_INITIALIZER_CLAUSE => {
std::fmt::Debug::fmt(&JsInitializerClause::cast(node.clone()).unwrap(), f)
}
+ JS_INSTANCEOF_EXPRESSION => {
+ std::fmt::Debug::fmt(&JsInstanceofExpression::cast(node.clone()).unwrap(), f)
+ }
JS_LABELED_STATEMENT => {
std::fmt::Debug::fmt(&JsLabeledStatement::cast(node.clone()).unwrap(), f)
}

MODIFY
crates/rslint_parser/src/ast/generated/syntax_factory.rs
crates/rslint_parser/src/ast/generated/syntax_factory.rs
@@ -450,8 +450,6 @@ impl SyntaxFactory for JsSyntaxFactory {
| T ! [&]
| T ! [|]
| T ! [^]
- | T![in]
- | T![instanceof]
) {
slots.mark_present();
current_element = elements.next();
@@ -3094,6 +3092,39 @@ impl SyntaxFactory for JsSyntaxFactory {
}
slots.into_node(JS_IMPORT_NAMESPACE_CLAUSE, children)
}
+ JS_IN_EXPRESSION => {
+ let mut elements = (&children).into_iter();
+ let mut slots: RawNodeSlots<3usize> = RawNodeSlots::default();
+ let mut current_element = elements.next();
+ if let Some(element) = &current_element {
+ if JsAnyInProperty::can_cast(element.kind()) {
+ slots.mark_present();
+ current_element = elements.next();
+ }
+ }
+ slots.next_slot();
+ if let Some(element) = &current_element {
+ if element.kind() == T![in] {
+ slots.mark_present();
+ current_element = elements.next();
+ }
+ }
+ slots.next_slot();
+ if let Some(element) = &current_element {
+ if JsAnyExpression::can_cast(element.kind()) {
+ slots.mark_present();
+ current_element = elements.next();
+ }
+ }
+ slots.next_slot();
+ if current_element.is_some() {
+ return RawSyntaxNode::new(
+ JS_IN_EXPRESSION.to_unknown(),
+ children.into_iter().map(Some),
+ );
+ }
+ slots.into_node(JS_IN_EXPRESSION, children)
+ }
JS_INITIALIZER_CLAUSE => {
let mut elements = (&children).into_iter();
let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default();
@@ -3120,6 +3151,39 @@ impl SyntaxFactory for JsSyntaxFactory {
}
slots.into_node(JS_INITIALIZER_CLAUSE, children)
}
+ JS_INSTANCEOF_EXPRESSION => {
+ let mut elements = (&children).into_iter();
+ let mut slots: RawNodeSlots<3usize> = RawNodeSlots::default();
+ let mut current_element = elements.next();
+ if let Some(element) = &current_element {
+ if JsAnyExpression::can_cast(element.kind()) {
+ slots.mark_present();
+ current_element = elements.next();
+ }
+ }
+ slots.next_slot();
+ if let Some(element) = &current_element {
+ if element.kind() == T![instanceof] {
+ slots.mark_present();
+ current_element = elements.next();
+ }
+ }
+ slots.next_slot();
+ if let Some(element) = &current_element {
+ if JsAnyExpression::can_cast(element.kind()) {
+ slots.mark_present();
+ current_element = elements.next();
+ }
+ }
+ slots.next_slot();
+ if current_element.is_some() {
+ return RawSyntaxNode::new(
+ JS_INSTANCEOF_EXPRESSION.to_unknown(),
+ children.into_iter().map(Some),
+ );
+ }
+ slots.into_node(JS_INSTANCEOF_EXPRESSION, children)
+ }
JS_LABELED_STATEMENT => {
let mut elements = (&children).into_iter();
let mut slots: RawNodeSlots<3usize> = RawNodeSlots::default();

MODIFY
crates/rslint_parser/src/syntax/expr.rs
crates/rslint_parser/src/syntax/expr.rs
@@ -75,6 +75,7 @@ const STARTS_EXPR: TokenSet = token_set![
T![class],
T![import],
T![super],
+ T![#],
BACKTICK,
TRUE_KW,
FALSE_KW,
@@ -313,7 +314,14 @@ pub(super) fn parse_conditional_expr(p: &mut Parser) -> ParsedSyntax {
/// A binary expression such as `2 + 2` or `foo * bar + 2` or a logical expression 'a || b'
fn parse_binary_or_logical_expression(p: &mut Parser) -> ParsedSyntax {
- let left = parse_unary_expr(p);
+ // test private_name_presence_check
+ // class A {
+ // #prop;
+ // test() {
+ // #prop in this
+ // }
+ // }
+ let left = parse_unary_expr(p).or_else(|| parse_private_name(p));
parse_binary_or_logical_expression_recursive(p, left, 0)
}
@@ -399,6 +407,8 @@ fn parse_binary_or_logical_expression_recursive(
// foo(foo ||)
let expression_kind = match op {
T![??] | T![||] | T![&&] => JS_LOGICAL_EXPRESSION,
+ T![instanceof] => JS_INSTANCEOF_EXPRESSION,
+ T![in] => JS_IN_EXPRESSION,
_ => JS_BINARY_EXPRESSION,
};
@@ -411,6 +421,21 @@ fn parse_binary_or_logical_expression_recursive(
p.error(err);
parse_binary_or_logical_expression_recursive(p, Absent, 0)
+ } else if p.at(T![#]) {
+ // test_err private_name_presence_check_recursive
+ // class A {
+ // #prop;
+ // test() {
+ // #prop in #prop in this
+ // }
+ // }
+ let mut private_name = parse_private_name(p).unwrap();
+ private_name.change_kind(p, JS_UNKNOWN_EXPRESSION);
+ p.error(
+ p.err_builder("Private names are only allowed on the left side of a binary expression")
+ .primary(private_name.range(p), ""),
+ );
+ Present(private_name)
} else {
parse_unary_expr(p)
};

ADD
crates/rslint_parser/test_data/inline/err/private_name_presence_check_recursive.js
+class A {
+ #prop;
+ test() {
+ #prop in #prop in this
+ }
+}

MODIFY
crates/rslint_parser/test_data/inline/ok/binary_expressions.rast
crates/rslint_parser/test_data/inline/ok/binary_expressions.rast
@@ -88,12 +88,12 @@ JsModule {
semicolon_token: missing (optional),
},
JsExpressionStatement {
- expression: JsBinaryExpression {
- left: JsNumberLiteralExpression {
+ expression: JsInExpression {
+ property: JsNumberLiteralExpression {
value_token: JS_NUMBER_LITERAL@45..49 "74" [Newline("\n")] [Whitespace(" ")],
},
- operator: IN_KW@49..52 "in" [] [Whitespace(" ")],
- right: JsIdentifierExpression {
+ in_token: IN_KW@49..52 "in" [] [Whitespace(" ")],
+ object: JsIdentifierExpression {
name: JsReferenceIdentifier {
value_token: IDENT@52..55 "foo" [] [],
},
@@ -102,13 +102,13 @@ JsModule {
semicolon_token: missing (optional),
},
JsExpressionStatement {
- expression: JsBinaryExpression {
+ expression: JsInstanceofExpression {
left: JsIdentifierExpression {
name: JsReferenceIdentifier {
value_token: IDENT@55..60 "foo" [Newline("\n")] [Whitespace(" ")],
},
},
- operator: INSTANCEOF_KW@60..71 "instanceof" [] [Whitespace(" ")],
+ instanceof_token: INSTANCEOF_KW@60..71 "instanceof" [] [Whitespace(" ")],
right: JsIdentifierExpression {
name: JsReferenceIdentifier {
value_token: IDENT@71..76 "Array" [] [],
@@ -291,7 +291,7 @@ JsModule {
0: JS_NUMBER_LITERAL@44..45 "2" [] []
1: (empty)
4: JS_EXPRESSION_STATEMENT@45..55
- 0: JS_BINARY_EXPRESSION@45..55
+ 0: JS_IN_EXPRESSION@45..55
0: JS_NUMBER_LITERAL_EXPRESSION@45..49
0: JS_NUMBER_LITERAL@45..49 "74" [Newline("\n")] [Whitespace(" ")]
1: IN_KW@49..52 "in" [] [Whitespace(" ")]
@@ -300,7 +300,7 @@ JsModule {
0: IDENT@52..55 "foo" [] []
1: (empty)
5: JS_EXPRESSION_STATEMENT@55..76
- 0: JS_BINARY_EXPRESSION@55..76
+ 0: JS_INSTANCEOF_EXPRESSION@55..76
0: JS_IDENTIFIER_EXPRESSION@55..60
0: JS_REFERENCE_IDENTIFIER@55..60
0: IDENT@55..60 "foo" [Newline("\n")] [Whitespace(" ")]

ADD
crates/rslint_parser/test_data/inline/ok/private_name_presence_check.js
+class A {
+ #prop;
+ test() {
+ #prop in this
+ }
+}

ADD
crates/rslint_parser/test_data/inline/ok/private_name_presence_check.rast
+JsModule {
+ interpreter_token: missing (optional),
+ directives: JsDirectiveList [],
+ items: JsModuleItemList [
+ JsClassStatement {
+ class_token: CLASS_KW@0..6 "class" [] [Whitespace(" ")],
+ id: JsIdentifierBinding {
+ name_token: IDENT@6..8 "A" [] [Whitespace(" ")],
+ },
+ extends_clause: missing (optional),
+ implements_clause: missing (optional),
+ l_curly_token: L_CURLY@8..9 "{" [] [],
+ members: JsClassMemberList [
+ JsPropertyClassMember {
+ declare_token: missing (optional),
+ access_modifier: missing (optional),
+ static_token: missing (optional),
+ readonly_token: missing (optional),
+ abstract_token: missing (optional),
+ name: JsPrivateClassMemberName {
+ hash_token: HASH@9..12 "#" [Newline("\n"), Whitespace("\t")] [],
+ id_token: IDENT@12..16 "prop" [] [],
+ },
+ question_mark_token: missing (optional),
+ excl_token: missing (optional),
+ ty: missing (optional),
+ value: missing (optional),
+ semicolon_token: SEMICOLON@16..17 ";" [] [],
+ },
+ JsMethodClassMember {
+ access_modifier: missing (optional),
+ static_token: missing (optional),
+ abstract_token: missing (optional),
+ async_token: missing (optional),
+ star_token: missing (optional),
+ name: JsLiteralMemberName {
+ value: IDENT@17..23 "test" [Newline("\n"), Whitespace("\t")] [],
+ },
+ type_parameters: missing (optional),
+ parameters: JsParameters {
+ l_paren_token: L_PAREN@23..24 "(" [] [],
+ items: JsParameterList [],
+ r_paren_token: R_PAREN@24..26 ")" [] [Whitespace(" ")],
+ },
+ return_type: missing (optional),
+ body: JsFunctionBody {
+ l_curly_token: L_CURLY@26..27 "{" [] [],
+ directives: JsDirectiveList [],
+ statements: JsStatementList [
+ JsExpressionStatement {
+ expression: JsInExpression {
+ property: JsPrivateName {
+ hash_token: HASH@27..32 "#" [Newline("\n"), Whitespace(" ")] [],
+ value_token: IDENT@32..37 "prop" [] [Whitespace(" ")],
+ },
+ in_token: IN_KW@37..40 "in" [] [Whitespace(" ")],
+ object: JsThisExpression {
+ this_token: THIS_KW@40..44 "this" [] [],
+ },
+ },
+ semicolon_token: missing (optional),
+ },
+ ],
+ r_curly_token: R_CURLY@44..47 "}" [Newline("\n"), Whitespace(" ")] [],
+ },
+ },
+ ],
+ r_curly_token: R_CURLY@47..49 "}" [Newline("\n")] [],
+ },
+ ],
+ eof_token: EOF@49..50 "" [Newline("\n")] [],
+}
+
+0: JS_MODULE@0..50
+ 0: (empty)
+ 1: JS_DIRECTIVE_LIST@0..0
+ 2: JS_MODULE_ITEM_LIST@0..49
+ 0: JS_CLASS_STATEMENT@0..49
+ 0: CLASS_KW@0..6 "class" [] [Whitespace(" ")]
+ 1: JS_IDENTIFIER_BINDING@6..8
+ 0: IDENT@6..8 "A" [] [Whitespace(" ")]
+ 2: (empty)
+ 3: (empty)
+ 4: L_CURLY@8..9 "{" [] []
+ 5: JS_CLASS_MEMBER_LIST@9..47
+ 0: JS_PROPERTY_CLASS_MEMBER@9..17
+ 0: (empty)
+ 1: (empty)
+ 2: (empty)
+ 3: (empty)
+ 4: (empty)
+ 5: JS_PRIVATE_CLASS_MEMBER_NAME@9..16
+ 0: HASH@9..12 "#" [Newline("\n"), Whitespace("\t")] []
+ 1: IDENT@12..16 "prop" [] []
+ 6: (empty)
+ 7: (empty)
+ 8: (empty)
+ 9: (empty)
+ 10: SEMICOLON@16..17 ";" [] []
+ 1: JS_METHOD_CLASS_MEMBER@17..47
+ 0: (empty)
+ 1: (empty)
+ 2: (empty)
+ 3: (empty)
+ 4: (empty)
+ 5: JS_LITERAL_MEMBER_NAME@17..23
+ 0: IDENT@17..23 "test" [Newline("\n"), Whitespace("\t")] []
+ 6: (empty)
+ 7: JS_PARAMETERS@23..26
+ 0: L_PAREN@23..24 "(" [] []
+ 1: JS_PARAMETER_LIST@24..24
+ 2: R_PAREN@24..26 ")" [] [Whitespace(" ")]
+ 8: (empty)
+ 9: JS_FUNCTION_BODY@26..47
+ 0: L_CURLY@26..27 "{" [] []
+ 1: JS_DIRECTIVE_LIST@27..27
+ 2: JS_STATEMENT_LIST@27..44
+ 0: JS_EXPRESSION_STATEMENT@27..44
+ 0: JS_IN_EXPRESSION@27..44
+ 0: JS_PRIVATE_NAME@27..37
+ 0: HASH@27..32 "#" [Newline("\n"), Whitespace(" ")] []
+ 1: IDENT@32..37 "prop" [] [Whitespace(" ")]
+ 1: IN_KW@37..40 "in" [] [Whitespace(" ")]
+ 2: JS_THIS_EXPRESSION@40..44
+ 0: THIS_KW@40..44 "this" [] []
+ 1: (empty)
+ 3: R_CURLY@44..47 "}" [Newline("\n"), Whitespace(" ")] []
+ 6: R_CURLY@47..49 "}" [Newline("\n")] []
+ 3: EOF@49..50 "" [Newline("\n")] []

MODIFY
crates/rslint_syntax/src/generated.rs
crates/rslint_syntax/src/generated.rs
@@ -229,6 +229,8 @@ pub enum JsSyntaxKind {
JS_PRE_UPDATE_EXPRESSION,
JS_POST_UPDATE_EXPRESSION,
JS_BINARY_EXPRESSION,
+ JS_INSTANCEOF_EXPRESSION,
+ JS_IN_EXPRESSION,
JS_LOGICAL_EXPRESSION,
JS_CONDITIONAL_EXPRESSION,
JS_ASSIGNMENT_EXPRESSION,

MODIFY
xtask/js.ungram
xtask/js.ungram
@@ -281,6 +281,8 @@ JsAnyExpression =
| JsFunctionExpression
| JsIdentifierExpression
| JsImportCallExpression
+ | JsInExpression
+ | JsInstanceofExpression
| JsLogicalExpression
| JsNewExpression
| JsObjectExpression
@@ -449,10 +451,24 @@ JsBinaryExpression =
operator: (
'<' | '>' | '<=' | '>=' | '==' | '===' | '!=' | '!=='
| '+' | '-' | '*' | '/' | '%' | '**' | '<<' | '>>' | '>>>'
- | '&' | '|' | '^' | 'in' | 'instanceof'
+ | '&' | '|' | '^'
)
right: JsAnyExpression
+JsInstanceofExpression =
+ left: JsAnyExpression
+ 'instanceof'
+ right: JsAnyExpression
+
+JsInExpression =
+ property: JsAnyInProperty
+ 'in'
+ object: JsAnyExpression
+
+JsAnyInProperty =
+ JsPrivateName
+ | JsAnyExpression
+
JsLogicalExpression =
left: JsAnyExpression
operator: ('??' | '||' | '&&')

MODIFY
xtask/src/codegen/kinds_src.rs
xtask/src/codegen/kinds_src.rs
@@ -246,6 +246,8 @@ pub const KINDS_SRC: KindsSrc = KindsSrc {
"JS_PRE_UPDATE_EXPRESSION",
"JS_POST_UPDATE_EXPRESSION",
"JS_BINARY_EXPRESSION",
+ "JS_INSTANCEOF_EXPRESSION",
+ "JS_IN_EXPRESSION",
"JS_LOGICAL_EXPRESSION",
"JS_CONDITIONAL_EXPRESSION",
"JS_ASSIGNMENT_EXPRESSION",

feat(rslint_parser): Private field presence check (#1964)

Implements `#a in obj` support.

I decided to split out `JsInExpression` and `JsInstanceofExpression` because private names are only ever valid inside `in` expression but not any other binary expressions (as proposed in #1868).