@@ -798,12 +798,7 @@ class Parser extends BaseParser {
798798 expression . addNode ( lookup ) ;
799799 return true ;
800800 }
801- } else if (
802- this . token . isLeftParenthesis ( ) &&
803- ( this . peek ( ) ?. isIdentifierOrKeyword ( ) ||
804- this . peek ( ) ?. isColon ( ) ||
805- this . peek ( ) ?. isRightParenthesis ( ) )
806- ) {
801+ } else if ( this . token . isLeftParenthesis ( ) && this . looksLikeNodePattern ( ) ) {
807802 // Possible graph pattern expression
808803 const pattern = this . parsePatternExpression ( ) ;
809804 if ( pattern !== null ) {
@@ -865,6 +860,42 @@ class Parser extends BaseParser {
865860 return false ;
866861 }
867862
863+ /**
864+ * Peeks ahead from a left parenthesis to determine whether the
865+ * upcoming tokens form a graph-node pattern (e.g. (n:Label), (n),
866+ * (:Label), ()) rather than a parenthesised expression (e.g.
867+ * (variable.property), (a + b)).
868+ *
869+ * The heuristic is:
870+ * • ( followed by `:` or `)` → node pattern
871+ * • ( identifier, then `:` or `{` or `)` → node pattern
872+ * • anything else → parenthesised expression
873+ */
874+ private looksLikeNodePattern ( ) : boolean {
875+ const savedIndex = this . tokenIndex ;
876+ this . setNextToken ( ) ; // skip '('
877+ this . skipWhitespaceAndComments ( ) ;
878+
879+ if ( this . token . isColon ( ) || this . token . isRightParenthesis ( ) ) {
880+ this . tokenIndex = savedIndex ;
881+ return true ;
882+ }
883+
884+ if ( this . token . isIdentifierOrKeyword ( ) ) {
885+ this . setNextToken ( ) ; // skip identifier
886+ this . skipWhitespaceAndComments ( ) ;
887+ const result =
888+ this . token . isColon ( ) ||
889+ this . token . isOpeningBrace ( ) ||
890+ this . token . isRightParenthesis ( ) ;
891+ this . tokenIndex = savedIndex ;
892+ return result ;
893+ }
894+
895+ this . tokenIndex = savedIndex ;
896+ return false ;
897+ }
898+
868899 private parseExpression ( ) : Expression | null {
869900 const expression = new Expression ( ) ;
870901 while ( true ) {
0 commit comments