diff --git a/java/java-frontback-tests/testSrc/com/intellij/java/parser/declarationParsing/AbstractBasicClassParsingTest.java b/java/java-frontback-tests/testSrc/com/intellij/java/parser/declarationParsing/AbstractBasicClassParsingTest.java index 282f3f5a0ef1..1ce7a3e9497a 100644 --- a/java/java-frontback-tests/testSrc/com/intellij/java/parser/declarationParsing/AbstractBasicClassParsingTest.java +++ b/java/java-frontback-tests/testSrc/com/intellij/java/parser/declarationParsing/AbstractBasicClassParsingTest.java @@ -1,4 +1,4 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.java.parser.declarationParsing; import com.intellij.java.parser.AbstractBasicJavaParsingTestCase; @@ -102,4 +102,5 @@ public abstract class AbstractBasicClassParsingTest extends AbstractBasicJavaPar public void testImplicitClassJavaDoc() { doTest(true); } public void testIncompleteClass() { doTest(true); } + public void testIncorrectModifier() { doTest(true); } } diff --git a/java/java-syntax/src/com/intellij/java/syntax/element/SyntaxElementTypes.kt b/java/java-syntax/src/com/intellij/java/syntax/element/SyntaxElementTypes.kt index fcd0e3680434..61e642cbc353 100644 --- a/java/java-syntax/src/com/intellij/java/syntax/element/SyntaxElementTypes.kt +++ b/java/java-syntax/src/com/intellij/java/syntax/element/SyntaxElementTypes.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.java.syntax.element import com.intellij.java.syntax.element.JavaSyntaxTokenType @@ -43,11 +43,6 @@ object SyntaxElementTypes { JToken.VOLATILE_KEYWORD, JToken.DEFAULT_KEYWORD, JToken.SEALED_KEYWORD, JToken.NON_SEALED_KEYWORD, JToken.VALUE_KEYWORD ) - // While only 'final' modifier is acceptable in the (method or lambda) parameter modifier list, we still parse any modifier, - // except the soft ones, as they should be parsed as variable name in lambda parameter list. - val PARAMETER_MODIFIER_BIT_SET: SyntaxElementTypeSet = - MODIFIER_BIT_SET - setOf(JToken.SEALED_KEYWORD, JToken.NON_SEALED_KEYWORD, JToken.VALUE_KEYWORD) - val PRIMITIVE_TYPE_BIT_SET: SyntaxElementTypeSet = syntaxElementTypeSetOf( JToken.BOOLEAN_KEYWORD, JToken.BYTE_KEYWORD, JToken.SHORT_KEYWORD, JToken.INT_KEYWORD, JToken.LONG_KEYWORD, JToken.CHAR_KEYWORD, JToken.FLOAT_KEYWORD, JToken.DOUBLE_KEYWORD, JToken.VOID_KEYWORD diff --git a/java/java-syntax/src/com/intellij/java/syntax/parser/DeclarationParser.kt b/java/java-syntax/src/com/intellij/java/syntax/parser/DeclarationParser.kt index 69f5091463f1..b9f4ff548486 100644 --- a/java/java-syntax/src/com/intellij/java/syntax/parser/DeclarationParser.kt +++ b/java/java-syntax/src/com/intellij/java/syntax/parser/DeclarationParser.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.java.syntax.parser import com.intellij.java.syntax.JavaSyntaxBundle @@ -8,7 +8,6 @@ import com.intellij.java.syntax.element.JavaSyntaxTokenType import com.intellij.java.syntax.element.SyntaxElementTypes.CLASS_KEYWORD_BIT_SET import com.intellij.java.syntax.element.SyntaxElementTypes.KEYWORD_BIT_SET import com.intellij.java.syntax.element.SyntaxElementTypes.MODIFIER_BIT_SET -import com.intellij.java.syntax.element.SyntaxElementTypes.PARAMETER_MODIFIER_BIT_SET import com.intellij.java.syntax.element.SyntaxElementTypes.PRIMITIVE_TYPE_BIT_SET import com.intellij.platform.syntax.SyntaxElementType import com.intellij.platform.syntax.SyntaxElementTypeSet @@ -244,9 +243,7 @@ open class DeclarationParser(private val myParser: JavaParser) { val declaration = builder.mark() val declarationStart = builder.currentOffset - - val modListInfo = parseModifierList(builder) - val modList = modListInfo.first + val modList = parseModifierList(builder) if (builder.expect(JavaSyntaxTokenType.AT)) { if (builder.tokenType === JavaSyntaxTokenType.INTERFACE_KEYWORD) { @@ -356,7 +353,7 @@ open class DeclarationParser(private val myParser: JavaParser) { if (!builder.expect(JavaSyntaxTokenType.IDENTIFIER)) { if (context != Context.CODE_BLOCK || - !modListInfo.second || + modList.getStartOffset() != modList.getEndOffset() || (type.isPrimitive && builder.tokenType !== JavaSyntaxTokenType.DOT) ) { typeParams?.precede()?.errorBefore(message("unexpected.token"), type.marker) @@ -415,12 +412,8 @@ open class DeclarationParser(private val myParser: JavaParser) { } @JvmOverloads - fun parseModifierList( - builder: SyntaxTreeBuilder, - modifiers: SyntaxElementTypeSet = MODIFIER_BIT_SET - ): Pair { + fun parseModifierList(builder: SyntaxTreeBuilder, modifiers: SyntaxElementTypeSet = MODIFIER_BIT_SET): SyntaxTreeBuilder.Marker { val modList = builder.mark() - var isEmpty = true while (true) { var tokenType = builder.tokenType ?: break @@ -436,18 +429,15 @@ open class DeclarationParser(private val myParser: JavaParser) { val nonSealed = builder.mark() builder.advance(3) nonSealed.collapse(JavaSyntaxTokenType.NON_SEALED_KEYWORD) - isEmpty = false } else if (modifiers.contains(tokenType)) { builder.advanceLexer() - isEmpty = false } else if (tokenType === JavaSyntaxTokenType.AT) { if (KEYWORD_BIT_SET.contains(builder.lookAhead(1))) { break } parseAnnotation(builder) - isEmpty = false } else { break @@ -455,7 +445,7 @@ open class DeclarationParser(private val myParser: JavaParser) { } JavaParserUtil.done(modList, JavaSyntaxElementType.MODIFIER_LIST, languageLevel) - return modList to isEmpty + return modList } private fun parseMethodFromLeftParenth( @@ -698,16 +688,13 @@ open class DeclarationParser(private val myParser: JavaParser) { ): SyntaxTreeBuilder.Marker? { val param = builder.mark() - val modListInfo = parseModifierList( - builder, if (type === JavaSyntaxElementType.PARAMETER) PARAMETER_MODIFIER_BIT_SET else MODIFIER_BIT_SET) - - val typeInfo: ReferenceParser.TypeInfo? + val modList: SyntaxTreeBuilder.Marker if (typed) { + modList = parseModifierList(builder) val flags = ReferenceParser.EAT_LAST_DOT or ReferenceParser.WILDCARD or typeFlags - typeInfo = myParser.referenceParser.parseTypeInfo(builder, flags) - if (typeInfo == null) { - if (modListInfo.second) { + if (myParser.referenceParser.parseTypeInfo(builder, flags) == null) { + if (modList.getStartOffset() == modList.getEndOffset()) { param.rollbackTo() return null } @@ -716,13 +703,9 @@ open class DeclarationParser(private val myParser: JavaParser) { JavaParserUtil.emptyElement(builder, JavaSyntaxElementType.TYPE) } } - } - - if (typed) { val tokenType = builder.tokenType if (tokenType === JavaSyntaxTokenType.THIS_KEYWORD || - tokenType === JavaSyntaxTokenType.IDENTIFIER && builder.lookAhead(1) === JavaSyntaxTokenType.DOT - ) { + tokenType === JavaSyntaxTokenType.IDENTIFIER && builder.lookAhead(1) === JavaSyntaxTokenType.DOT) { val mark = builder.mark() val expr = myParser.expressionParser.parse(builder) @@ -735,6 +718,10 @@ open class DeclarationParser(private val myParser: JavaParser) { mark.rollbackTo() } } + else { + modList = builder.mark() // modifier list is empty for lambda expression with untyped parameters + modList.done(JavaSyntaxElementType.MODIFIER_LIST) + } if (builder.expect(JavaSyntaxTokenType.IDENTIFIER)) { if (type === JavaSyntaxElementType.PARAMETER || type === JavaSyntaxElementType.RECORD_COMPONENT) { @@ -746,13 +733,11 @@ open class DeclarationParser(private val myParser: JavaParser) { else { JavaParserUtil.error(builder, message("expected.identifier")) param.drop() - return modListInfo.first + return modList } - if (JavaParserUtil.expectOrError(builder, JavaSyntaxTokenType.EQ, "expected.eq")) { - if (myParser.expressionParser.parse(builder) == null) { - JavaParserUtil.error(builder, message("expected.expression")) - } + if (JavaParserUtil.expectOrError(builder, JavaSyntaxTokenType.EQ, "expected.eq") && myParser.expressionParser.parse(builder) == null) { + JavaParserUtil.error(builder, message("expected.expression")) } JavaParserUtil.done(param, JavaSyntaxElementType.RESOURCE_VARIABLE, languageLevel) @@ -847,10 +832,7 @@ open class DeclarationParser(private val myParser: JavaParser) { return declaration } - private fun eatBrackets( - builder: SyntaxTreeBuilder, - errorKey: @PropertyKey(resourceBundle = JavaSyntaxBundle.BUNDLE) String? - ): Boolean { + private fun eatBrackets(builder: SyntaxTreeBuilder, errorKey: @PropertyKey(resourceBundle = JavaSyntaxBundle.BUNDLE) String?): Boolean { val tokenType = builder.tokenType if (tokenType !== JavaSyntaxTokenType.LBRACKET && tokenType !== JavaSyntaxTokenType.AT) return true diff --git a/java/java-syntax/src/com/intellij/java/syntax/parser/PatternParser.kt b/java/java-syntax/src/com/intellij/java/syntax/parser/PatternParser.kt index 5ab78f128756..7d285de6e430 100644 --- a/java/java-syntax/src/com/intellij/java/syntax/parser/PatternParser.kt +++ b/java/java-syntax/src/com/intellij/java/syntax/parser/PatternParser.kt @@ -1,4 +1,4 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.java.syntax.parser import com.intellij.java.syntax.JavaSyntaxBundle.message @@ -42,7 +42,8 @@ class PatternParser(private val myParser: JavaParser) { fun preParsePattern(builder: SyntaxTreeBuilder): SyntaxTreeBuilder.Marker? { val patternStart = builder.mark() - val hasNoModifier = myParser.declarationParser.parseModifierList(builder, PATTERN_MODIFIERS).second + val modList = myParser.declarationParser.parseModifierList(builder, PATTERN_MODIFIERS) + val hasNoModifier = modList.getStartOffset() == modList.getEndOffset() val type = myParser.referenceParser.parseType(builder, ReferenceParser.EAT_LAST_DOT or ReferenceParser.WILDCARD) val isPattern = type != null && (builder.tokenType === JavaSyntaxTokenType.IDENTIFIER || (builder.tokenType === JavaSyntaxTokenType.LPARENTH && hasNoModifier)) @@ -109,7 +110,8 @@ class PatternParser(private val myParser: JavaParser) { private fun parseTypeOrRecordPattern(builder: SyntaxTreeBuilder, expectVar: Boolean): SyntaxTreeBuilder.Marker { val pattern = builder.mark() val patternVariable = builder.mark() - val hasNoModifiers = myParser.declarationParser.parseModifierList(builder, PATTERN_MODIFIERS).second + val modList = myParser.declarationParser.parseModifierList(builder, PATTERN_MODIFIERS) + val hasNoModifiers = modList.getStartOffset() == modList.getEndOffset() var flags = ReferenceParser.EAT_LAST_DOT or ReferenceParser.WILDCARD if (expectVar) { diff --git a/java/java-tests/testData/psi/parser-full/declarationParsing/class/IncorrectModifier.java b/java/java-tests/testData/psi/parser-full/declarationParsing/class/IncorrectModifier.java new file mode 100644 index 000000000000..e7ddc6e69abe --- /dev/null +++ b/java/java-tests/testData/psi/parser-full/declarationParsing/class/IncorrectModifier.java @@ -0,0 +1,7 @@ +class IncorrectModifier { + X x = (sealed var y) -> System.out.println() + + interface X { + void x(String s); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-full/declarationParsing/class/IncorrectModifier.txt b/java/java-tests/testData/psi/parser-full/declarationParsing/class/IncorrectModifier.txt new file mode 100644 index 000000000000..dc27f114d5f2 --- /dev/null +++ b/java/java-tests/testData/psi/parser-full/declarationParsing/class/IncorrectModifier.txt @@ -0,0 +1,111 @@ +PsiJavaFile:IncorrectModifier.java + PsiImportList + + PsiClass:IncorrectModifier + PsiModifierList: + + PsiKeyword:class('class') + PsiWhiteSpace(' ') + PsiIdentifier:IncorrectModifier('IncorrectModifier') + PsiTypeParameterList + + PsiReferenceList + + PsiReferenceList + + PsiWhiteSpace(' ') + PsiJavaToken:LBRACE('{') + PsiWhiteSpace('\n ') + PsiField:x + PsiModifierList: + + PsiTypeElement:X + PsiJavaCodeReferenceElement:X + PsiIdentifier:X('X') + PsiReferenceParameterList + + PsiWhiteSpace(' ') + PsiIdentifier:x('x') + PsiWhiteSpace(' ') + PsiJavaToken:EQ('=') + PsiWhiteSpace(' ') + PsiLambdaExpression + PsiParameterList:(sealed var y) + PsiJavaToken:LPARENTH('(') + PsiParameter:y + PsiModifierList:sealed + PsiKeyword:sealed('sealed') + PsiWhiteSpace(' ') + PsiTypeElement:var + PsiKeyword:var('var') + PsiWhiteSpace(' ') + PsiIdentifier:y('y') + PsiJavaToken:RPARENTH(')') + PsiWhiteSpace(' ') + PsiJavaToken:ARROW('->') + PsiWhiteSpace(' ') + PsiMethodCallExpression:System.out.println() + PsiReferenceExpression:System.out.println + PsiReferenceExpression:System.out + PsiReferenceExpression:System + PsiReferenceParameterList + + PsiIdentifier:System('System') + PsiJavaToken:DOT('.') + PsiReferenceParameterList + + PsiIdentifier:out('out') + PsiJavaToken:DOT('.') + PsiReferenceParameterList + + PsiIdentifier:println('println') + PsiExpressionList + PsiJavaToken:LPARENTH('(') + PsiJavaToken:RPARENTH(')') + PsiErrorElement:';' expected + + PsiWhiteSpace(' \n \n ') + PsiClass:X + PsiModifierList: + + PsiKeyword:interface('interface') + PsiWhiteSpace(' ') + PsiIdentifier:X('X') + PsiTypeParameterList + + PsiReferenceList + + PsiReferenceList + + PsiWhiteSpace(' ') + PsiJavaToken:LBRACE('{') + PsiWhiteSpace('\n ') + PsiMethod:x + PsiModifierList: + + PsiTypeParameterList + + PsiTypeElement:void + PsiKeyword:void('void') + PsiWhiteSpace(' ') + PsiIdentifier:x('x') + PsiParameterList:(String s) + PsiJavaToken:LPARENTH('(') + PsiParameter:s + PsiModifierList: + + PsiTypeElement:String + PsiJavaCodeReferenceElement:String + PsiIdentifier:String('String') + PsiReferenceParameterList + + PsiWhiteSpace(' ') + PsiIdentifier:s('s') + PsiJavaToken:RPARENTH(')') + PsiReferenceList + + PsiJavaToken:SEMICOLON(';') + PsiWhiteSpace('\n ') + PsiJavaToken:RBRACE('}') + PsiWhiteSpace('\n') + PsiJavaToken:RBRACE('}') \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-full/declarationParsing/class/IncorrectModifier_node.txt b/java/java-tests/testData/psi/parser-full/declarationParsing/class/IncorrectModifier_node.txt new file mode 100644 index 000000000000..ab6db54f3ef4 --- /dev/null +++ b/java/java-tests/testData/psi/parser-full/declarationParsing/class/IncorrectModifier_node.txt @@ -0,0 +1,111 @@ +java.FILE + IMPORT_LIST + + CLASS + MODIFIER_LIST + + CLASS_KEYWORD + WHITE_SPACE + IDENTIFIER + TYPE_PARAMETER_LIST + + EXTENDS_LIST + + IMPLEMENTS_LIST + + WHITE_SPACE + LBRACE + WHITE_SPACE + FIELD + MODIFIER_LIST + + TYPE + JAVA_CODE_REFERENCE + IDENTIFIER + REFERENCE_PARAMETER_LIST + + WHITE_SPACE + IDENTIFIER + WHITE_SPACE + EQ + WHITE_SPACE + LAMBDA_EXPRESSION + PARAMETER_LIST + LPARENTH + PARAMETER + MODIFIER_LIST + SEALED + WHITE_SPACE + TYPE + VAR + WHITE_SPACE + IDENTIFIER + RPARENTH + WHITE_SPACE + ARROW + WHITE_SPACE + METHOD_CALL_EXPRESSION + REFERENCE_EXPRESSION + REFERENCE_EXPRESSION + REFERENCE_EXPRESSION + REFERENCE_PARAMETER_LIST + + IDENTIFIER + DOT + REFERENCE_PARAMETER_LIST + + IDENTIFIER + DOT + REFERENCE_PARAMETER_LIST + + IDENTIFIER + EXPRESSION_LIST + LPARENTH + RPARENTH + ERROR_ELEMENT + + WHITE_SPACE + CLASS + MODIFIER_LIST + + INTERFACE_KEYWORD + WHITE_SPACE + IDENTIFIER + TYPE_PARAMETER_LIST + + EXTENDS_LIST + + IMPLEMENTS_LIST + + WHITE_SPACE + LBRACE + WHITE_SPACE + METHOD + MODIFIER_LIST + + TYPE_PARAMETER_LIST + + TYPE + VOID_KEYWORD + WHITE_SPACE + IDENTIFIER + PARAMETER_LIST + LPARENTH + PARAMETER + MODIFIER_LIST + + TYPE + JAVA_CODE_REFERENCE + IDENTIFIER + REFERENCE_PARAMETER_LIST + + WHITE_SPACE + IDENTIFIER + RPARENTH + THROWS_LIST + + SEMICOLON + WHITE_SPACE + RBRACE + WHITE_SPACE + RBRACE \ No newline at end of file