diff --git a/java/java-frontback-psi-impl/src/com/intellij/psi/impl/source/WhiteSpaceAndCommentSetHolder.java b/java/java-frontback-psi-impl/src/com/intellij/psi/impl/source/WhiteSpaceAndCommentSetHolder.java index 38f4f29f523a..d6c51d647e63 100644 --- a/java/java-frontback-psi-impl/src/com/intellij/psi/impl/source/WhiteSpaceAndCommentSetHolder.java +++ b/java/java-frontback-psi-impl/src/com/intellij/psi/impl/source/WhiteSpaceAndCommentSetHolder.java @@ -8,6 +8,7 @@ import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.ParentAwareTokenSet; import com.intellij.psi.tree.TokenSet; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -17,6 +18,14 @@ import static com.intellij.psi.impl.source.BasicJavaDocElementType.BASIC_DOC_COM import static com.intellij.psi.tree.ParentAwareTokenSet.create; import static com.intellij.psi.tree.ParentAwareTokenSet.orSet; + +/** + * @deprecated Use the new Java syntax library instead. + * See {@link com.intellij.java.syntax.parser.JavaParser} + * You can temporarily use @{@link com.intellij.psi.impl.source.OldParserWhiteSpaceAndCommentSetHolder} + * + */ +@ApiStatus.ScheduledForRemoval @Deprecated public class WhiteSpaceAndCommentSetHolder { public static final WhiteSpaceAndCommentSetHolder INSTANCE = new WhiteSpaceAndCommentSetHolder(); diff --git a/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestCase.java b/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestCase.java index cbcbbf59a2be..f8cf6dc2ee7a 100644 --- a/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestCase.java +++ b/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestCase.java @@ -7,7 +7,7 @@ import com.intellij.lang.Language; import com.intellij.lang.LanguageExtension; import com.intellij.lang.java.JavaLanguage; import com.intellij.lang.java.JavaParserDefinition; -import com.intellij.lang.java.parser.BasicJavaParserUtil; +import com.intellij.lang.java.parser.JavaParserUtil; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.util.io.FileUtil; import com.intellij.platform.backend.workspace.WorkspaceModelTopics; @@ -73,7 +73,7 @@ public abstract class AbstractBasicJavaParsingTestCase extends ParsingTestCase { return file; } - protected void doParserTest(BasicJavaParserUtil.@NotNull ParserWrapper parser) { + protected void doParserTest(JavaParserUtil.@NotNull ParserWrapper parser) { doParserTest((Object)parser); } @@ -138,7 +138,7 @@ public abstract class AbstractBasicJavaParsingTestCase extends ParsingTestCase { } protected void doParserTest(@NotNull String text, - BasicJavaParserUtil.@NotNull ParserWrapper parser) { + JavaParserUtil.@NotNull ParserWrapper parser) { doParserTest(text, (Object)parser); } @@ -146,7 +146,7 @@ public abstract class AbstractBasicJavaParsingTestCase extends ParsingTestCase { @NotNull Object parser) { String name = getTestName(false); myFile = myConfigurator.createPsiFile(this, name, text, parser); - if (parser instanceof BasicJavaParserUtil.@NotNull ParserWrapper wrapper) { + if (parser instanceof JavaParserUtil.@NotNull ParserWrapper wrapper) { myKmpTree = myConfigurator.createFileSyntaxNode(text, wrapper); } try { diff --git a/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestConfigurator.java b/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestConfigurator.java index aa3b3cc76f91..4117e7014452 100644 --- a/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestConfigurator.java +++ b/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestConfigurator.java @@ -1,7 +1,7 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.java.parser; -import com.intellij.lang.java.parser.BasicJavaParserUtil; +import com.intellij.lang.java.parser.JavaParserUtil; import com.intellij.platform.syntax.tree.SyntaxNode; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.PsiFile; @@ -25,7 +25,7 @@ public interface AbstractBasicJavaParsingTestConfigurator { * @return SyntaxNode node representing the parsed content */ @Nullable - SyntaxNode createFileSyntaxNode(@NotNull String text, @Nullable BasicJavaParserUtil.ParserWrapper parserWrapper); + SyntaxNode createFileSyntaxNode(@NotNull String text, @Nullable JavaParserUtil.ParserWrapper parserWrapper); boolean checkPsi(); diff --git a/java/java-frontback-tests/testSrc/com/intellij/java/parser/JavaParsingTestConfiguratorBase.kt b/java/java-frontback-tests/testSrc/com/intellij/java/parser/JavaParsingTestConfiguratorBase.kt index b263378b66e1..c0911176f49e 100644 --- a/java/java-frontback-tests/testSrc/com/intellij/java/parser/JavaParsingTestConfiguratorBase.kt +++ b/java/java-frontback-tests/testSrc/com/intellij/java/parser/JavaParsingTestConfiguratorBase.kt @@ -7,7 +7,6 @@ import com.intellij.java.syntax.element.JavaLanguageLevelProvider import com.intellij.lang.ASTNode import com.intellij.lang.LanguageASTFactory import com.intellij.lang.java.JavaLanguage -import com.intellij.lang.java.parser.BasicJavaParserUtil import com.intellij.lang.java.parser.JavaParserUtil import com.intellij.lang.java.syntax.JavaElementTypeConverterExtension import com.intellij.openapi.extensions.ExtensionPointName @@ -60,7 +59,7 @@ abstract class JavaParsingTestConfiguratorBase( ourLanguageLevel = languageLevel } - override fun createFileSyntaxNode(text: String, parserWrapper: BasicJavaParserUtil.ParserWrapper?): SyntaxNode { + override fun createFileSyntaxNode(text: String, parserWrapper: JavaParserUtil.ParserWrapper?): SyntaxNode { ourLanguageLevel = languageLevel return parseForSyntaxTree(text) { builder -> if (parserWrapper != null) { @@ -78,7 +77,7 @@ abstract class JavaParsingTestConfiguratorBase( text: String, parser: Any, ): PsiFile { - ourTestParser = parser as BasicJavaParserUtil.ParserWrapper + ourTestParser = parser as JavaParserUtil.ParserWrapper val virtualFile = LightVirtualFile("$name.java", JavaFileType.INSTANCE, text, -1) val psiManager = PsiManager.getInstance(thinJavaParsingTestCase.getProject()) @@ -107,7 +106,7 @@ private fun createBuilder(chameleon: ASTNode?): PsiSyntaxBuilder { return builder } -private fun parseWithWrapper(builder: SyntaxTreeBuilder, parser: BasicJavaParserUtil.ParserWrapper) { +private fun parseWithWrapper(builder: SyntaxTreeBuilder, parser: JavaParserUtil.ParserWrapper) { val root = builder.mark() parser.parse(builder, ourLanguageLevel) if (!builder.eof()) { @@ -121,7 +120,7 @@ private fun parseWithWrapper(builder: SyntaxTreeBuilder, parser: BasicJavaParser private lateinit var ourLanguageLevel: LanguageLevel private val ourSyntaxElementType = SyntaxElementType("test.java.file") private val ourTestFileElementType: IFileElementType = MyIFileElementType() -private var ourTestParser: BasicJavaParserUtil.ParserWrapper? = null +private var ourTestParser: JavaParserUtil.ParserWrapper? = null private val converter = elementTypeConverterOf(ourSyntaxElementType to ourTestFileElementType) diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/DeclarationParser.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/DeclarationParser.java index 4da81a7482b7..e59d348d7720 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/DeclarationParser.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/parser/DeclarationParser.java @@ -1,65 +1,1021 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.lang.java.parser; +import com.intellij.core.JavaPsiBundle; +import com.intellij.java.syntax.parser.JavaKeywords; import com.intellij.lang.PsiBuilder; +import com.intellij.lang.PsiBuilderUtil; +import com.intellij.openapi.util.Pair; +import com.intellij.pom.java.JavaFeature; +import com.intellij.psi.impl.source.AbstractBasicJavaElementTypeFactory; +import com.intellij.psi.impl.source.OldParserWhiteSpaceAndCommentSetHolder; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.ILazyParseableElementType; +import com.intellij.psi.tree.TokenSet; +import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.PropertyKey; + +import static com.intellij.lang.PsiBuilderUtil.expect; +import static com.intellij.lang.java.parser.JavaParserUtil.*; +import static com.intellij.psi.impl.source.tree.ElementType.*; /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated -public class DeclarationParser extends BasicDeclarationParser { +public class DeclarationParser { + private static final TokenSet BEFORE_LBRACE_ELEMENTS_SET = TokenSet.create( + IDENTIFIER, COMMA, EXTENDS_KEYWORD, IMPLEMENTS_KEYWORD, LPARENTH); + private static final TokenSet APPEND_TO_METHOD_SET = TokenSet.create( + IDENTIFIER, COMMA, THROWS_KEYWORD); + private static final TokenSet PARAM_LIST_STOPPERS = TokenSet.create( + RPARENTH, LBRACE, ARROW); + private static final TokenSet METHOD_PARAM_LIST_STOPPERS = TokenSet.create( + RPARENTH, LBRACE, ARROW, SEMICOLON); + private static final TokenSet TYPE_START = TokenSet.orSet( + PRIMITIVE_TYPE_BIT_SET, TokenSet.create(IDENTIFIER, AT, VAR_KEYWORD)); + private static final String WHITESPACES = "\n\r \t"; + private static final String LINE_ENDS = "\n\r"; + private final TokenSet RESOURCE_EXPRESSIONS; + private final JavaParser myParser; + private final AbstractBasicJavaElementTypeFactory.JavaElementTypeContainer myJavaElementTypeContainer; + + public void parseClassBodyWithBraces(PsiBuilder builder, boolean isAnnotation, boolean isEnum) { + assert builder.getTokenType() == LBRACE : builder.getTokenType(); + builder.advanceLexer(); + + final PsiBuilder builderWrapper = braceMatchingBuilder(builder); + if (isEnum) { + parseEnumConstants(builderWrapper); + } + parseClassBodyDeclarations(builderWrapper, isAnnotation); + + expectOrError(builder, RBRACE, "expected.rbrace"); + } + + private @Nullable PsiBuilder.Marker parseClassFromKeyword(PsiBuilder builder, + PsiBuilder.Marker declaration, + boolean isAnnotation, + Context context) { + IElementType keywordTokenType = builder.getTokenType(); + final boolean isRecord = isRecordToken(builder, keywordTokenType); + if (isRecord) { + builder.remapCurrentToken(RECORD_KEYWORD); + if (builder.lookAhead(1) != IDENTIFIER) { + builder.advanceLexer(); + error(builder, JavaPsiBundle.message("expected.identifier")); + declaration.drop(); + return null; + } + final IElementType afterIdent = builder.lookAhead(2); + // No parser recovery for local records without < or ( to support light stubs + // (look at com.intellij.psi.impl.source.JavaLightStubBuilder.CodeBlockVisitor.visit) + if (context == Context.CODE_BLOCK && afterIdent != LPARENTH && afterIdent != LT) { + // skipping record kw and identifier + PsiBuilderUtil.advance(builder, 2); + error(builder, JavaPsiBundle.message("expected.lt.or.lparen")); + declaration.drop(); + return null; + } + keywordTokenType = RECORD_KEYWORD; + } + assert CLASS_KEYWORD_BIT_SET.contains(keywordTokenType) : keywordTokenType; + builder.advanceLexer(); + final boolean isEnum = (keywordTokenType == ENUM_KEYWORD); + + if (!expect(builder, IDENTIFIER)) { + error(builder, JavaPsiBundle.message("expected.identifier")); + declaration.drop(); + return null; + } + + final ReferenceParser refParser = myParser.getReferenceParser(); + refParser.parseTypeParameters(builder); + + if (builder.getTokenType() == LPARENTH) { + parseElementList(builder, ListType.RECORD_COMPONENTS); + } + + refParser.parseReferenceList(builder, EXTENDS_KEYWORD, myJavaElementTypeContainer.EXTENDS_LIST, COMMA); + refParser.parseReferenceList(builder, IMPLEMENTS_KEYWORD, myJavaElementTypeContainer.IMPLEMENTS_LIST, + COMMA); + if (builder.getTokenType() == IDENTIFIER && + JavaKeywords.PERMITS.equals(builder.getTokenText())) { + builder.remapCurrentToken(PERMITS_KEYWORD); + } + if (builder.getTokenType() == PERMITS_KEYWORD) { + refParser.parseReferenceList(builder, PERMITS_KEYWORD, myJavaElementTypeContainer.PERMITS_LIST, COMMA); + } + + if (builder.getTokenType() != LBRACE) { + final PsiBuilder.Marker error = builder.mark(); + while (BEFORE_LBRACE_ELEMENTS_SET.contains(builder.getTokenType())) { + builder.advanceLexer(); + } + error.error(JavaPsiBundle.message("expected.lbrace")); + } + + if (builder.getTokenType() == LBRACE) { + parseClassBodyWithBraces(builder, isAnnotation, isEnum); + } + + done(declaration, myJavaElementTypeContainer.CLASS, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + return declaration; + } + + private void parseEnumConstants(PsiBuilder builder) { + boolean first = true; + while (builder.getTokenType() != null) { + if (expect(builder, SEMICOLON)) { + return; + } + + if (builder.getTokenType() == PRIVATE_KEYWORD || builder.getTokenType() == PROTECTED_KEYWORD) { + error(builder, JavaPsiBundle.message("expected.semicolon")); + return; + } + + PsiBuilder.Marker enumConstant = parseEnumConstant(builder); + if (enumConstant == null && builder.getTokenType() == COMMA && first) { + IElementType next = builder.lookAhead(1); + if (next != SEMICOLON && next != RBRACE) { + error(builder, JavaPsiBundle.message("expected.identifier")); + } + } + + first = false; + + int commaCount = 0; + while (builder.getTokenType() == COMMA) { + if (commaCount > 0) { + error(builder, JavaPsiBundle.message("expected.identifier")); + } + builder.advanceLexer(); + commaCount++; + } + if (commaCount == 0 && + builder.getTokenType() != null && + builder.getTokenType() != SEMICOLON) { + error(builder, JavaPsiBundle.message("expected.comma.or.semicolon")); + return; + } + } + } + + public @Nullable PsiBuilder.Marker parseEnumConstant(PsiBuilder builder) { + final PsiBuilder.Marker constant = builder.mark(); + + parseModifierList(builder); + + if (expect(builder, IDENTIFIER)) { + if (builder.getTokenType() == LPARENTH) { + myParser.getExpressionParser().parseArgumentList(builder); + } + else { + emptyElement(builder, myJavaElementTypeContainer.EXPRESSION_LIST); + } + + if (builder.getTokenType() == LBRACE) { + final PsiBuilder.Marker constantInit = builder.mark(); + parseClassBodyWithBraces(builder, false, false); + done(constantInit, myJavaElementTypeContainer.ENUM_CONSTANT_INITIALIZER, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + } + + done(constant, myJavaElementTypeContainer.ENUM_CONSTANT, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + return constant; + } + else { + constant.rollbackTo(); + return null; + } + } + + public void parseClassBodyDeclarations(PsiBuilder builder, boolean isAnnotation) { + final Context context = isAnnotation ? Context.ANNOTATION_INTERFACE : Context.CLASS; + + PsiBuilder.Marker invalidElements = null; + while (true) { + final IElementType tokenType = builder.getTokenType(); + if (tokenType == null || tokenType == RBRACE) break; + + if (tokenType == SEMICOLON) { + if (invalidElements != null) { + invalidElements.error(JavaPsiBundle.message("unexpected.token")); + invalidElements = null; + } + builder.advanceLexer(); + continue; + } + + final PsiBuilder.Marker declaration = parse(builder, context); + if (declaration != null) { + if (invalidElements != null) { + invalidElements.errorBefore(JavaPsiBundle.message("unexpected.token"), declaration); + invalidElements = null; + } + continue; + } + + if (invalidElements == null) { + invalidElements = builder.mark(); + } + + // adding a reference, not simple tokens allows "Browse ..." to work well + final PsiBuilder.Marker ref = myParser.getReferenceParser().parseJavaCodeReference(builder, true, true, false, false); + if (ref == null) { + builder.advanceLexer(); + } + } + + if (invalidElements != null) { + invalidElements.error(JavaPsiBundle.message("unexpected.token")); + } + } + + public @Nullable PsiBuilder.Marker parse(@NotNull PsiBuilder builder, Context context) { + IElementType tokenType = builder.getTokenType(); + if (tokenType == null) return null; + + if (tokenType == LBRACE) { + if (context == Context.FILE || context == Context.CODE_BLOCK) return null; + } + else if (!isRecordToken(builder, tokenType) && !isSealedToken(builder, tokenType) && !isNonSealedToken(builder, tokenType)) { + if (!TYPE_START.contains(tokenType) || tokenType == AT) { + if (tokenType instanceof ILazyParseableElementType) { + builder.advanceLexer(); + return null; + } + else if (!MODIFIER_BIT_SET.contains(tokenType) && + !CLASS_KEYWORD_BIT_SET.contains(tokenType) && + tokenType != AT && + (context == Context.CODE_BLOCK || tokenType != LT)) { + return null; + } + } + } + + final PsiBuilder.Marker declaration = builder.mark(); + final int declarationStart = builder.getCurrentOffset(); + + final Pair modListInfo = parseModifierList(builder); + final PsiBuilder.Marker modList = modListInfo.first; + + if (expect(builder, AT)) { + if (builder.getTokenType() == INTERFACE_KEYWORD) { + final PsiBuilder.Marker result = parseClassFromKeyword(builder, declaration, true, context); + return result != null ? result : modList; + } + else { + declaration.rollbackTo(); + return null; + } + } + if (CLASS_KEYWORD_BIT_SET.contains(builder.getTokenType()) || isRecordToken(builder, builder.getTokenType())) { + final PsiBuilder.Marker result = parseClassFromKeyword(builder, declaration, false, context); + return result != null ? result : modList; + } + + PsiBuilder.Marker typeParams = null; + if (builder.getTokenType() == LT && context != Context.CODE_BLOCK) { + typeParams = myParser.getReferenceParser().parseTypeParameters(builder); + } + + if (builder.getTokenType() == LBRACE) { + if (context == Context.CODE_BLOCK) { + error(builder, JavaPsiBundle.message("expected.identifier.or.type"), null); + declaration.drop(); + return modList; + } + + PsiBuilder.Marker codeBlock = myParser.getStatementParser().parseCodeBlock(builder); + assert codeBlock != null : builder.getOriginalText(); + + if (typeParams != null) { + PsiBuilder.Marker error = typeParams.precede(); + error.errorBefore(JavaPsiBundle.message("unexpected.token"), codeBlock); + } + + done(declaration, myJavaElementTypeContainer.CLASS_INITIALIZER, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + return declaration; + } + + ReferenceParser.TypeInfo type = null; + + if (TYPE_START.contains(builder.getTokenType())) { + PsiBuilder.Marker pos = builder.mark(); + + int flags = ReferenceParser.EAT_LAST_DOT | ReferenceParser.WILDCARD; + if (context == Context.CODE_BLOCK || context == Context.JSHELL) flags |= ReferenceParser.VAR_TYPE; + type = myParser.getReferenceParser().parseTypeInfo(builder, flags); + + if (type == null) { + pos.rollbackTo(); + } + else if (builder.getTokenType() == LPARENTH || + builder.getTokenType() == LBRACE || + builder.getTokenType() == THROWS_KEYWORD) { // constructor + if (context == Context.CODE_BLOCK) { + declaration.rollbackTo(); + return null; + } + + pos.rollbackTo(); + if (typeParams == null) { + emptyElement(builder, myJavaElementTypeContainer.TYPE_PARAMETER_LIST); + } + parseAnnotations(builder); + + if (!expect(builder, IDENTIFIER)) { + PsiBuilder.Marker primitive = builder.mark(); + builder.advanceLexer(); + primitive.error(JavaPsiBundle.message("expected.identifier")); + } + + if (builder.getTokenType() == LPARENTH) { + return parseMethodFromLeftParenth(builder, declaration, false, true); + } + else if (builder.getTokenType() == LBRACE) { // compact constructor + emptyElement(builder, myJavaElementTypeContainer.THROWS_LIST); + return parseMethodBody(builder, declaration, false); + } + else if (builder.getTokenType() == THROWS_KEYWORD) { + myParser.getReferenceParser() + .parseReferenceList(builder, THROWS_KEYWORD, myJavaElementTypeContainer.THROWS_LIST, COMMA); + return parseMethodBody(builder, declaration, false); + } + else { + declaration.rollbackTo(); + return null; + } + } + else { + pos.drop(); + } + } + + if (type == null) { + PsiBuilder.Marker error = typeParams != null ? typeParams.precede() : builder.mark(); + error.error(JavaPsiBundle.message("expected.identifier.or.type")); + declaration.drop(); + return modList; + } + + if (!expect(builder, IDENTIFIER)) { + if (context != Context.CODE_BLOCK || + Boolean.FALSE.equals(modListInfo.second) || + (type.isPrimitive && builder.getTokenType() != DOT)) { + if (typeParams != null) { + typeParams.precede().errorBefore(JavaPsiBundle.message("unexpected.token"), type.marker); + } + builder.error(JavaPsiBundle.message("expected.identifier")); + declaration.drop(); + return modList; + } + else { + declaration.rollbackTo(); + return null; + } + } + + if (builder.getTokenType() == LPARENTH) { + if (context == Context.CLASS || context == Context.ANNOTATION_INTERFACE || context == Context.FILE + || context == Context.JSHELL) { // method + if (typeParams == null) { + emptyElement(type.marker, myJavaElementTypeContainer.TYPE_PARAMETER_LIST); + } + return parseMethodFromLeftParenth(builder, declaration, (context == Context.ANNOTATION_INTERFACE), false); + } + } + + if (typeParams != null) { + typeParams.precede().errorBefore(JavaPsiBundle.message("unexpected.token"), type.marker); + } + return parseFieldOrLocalVariable(builder, declaration, declarationStart, context); + } + + public @NotNull Pair parseModifierList(PsiBuilder builder) { + return parseModifierList(builder, MODIFIER_BIT_SET); + } + + public @NotNull Pair parseModifierList(PsiBuilder builder, TokenSet modifiers) { + final PsiBuilder.Marker modList = builder.mark(); + boolean isEmpty = true; + + while (true) { + IElementType tokenType = builder.getTokenType(); + if (tokenType == null) break; + if (isValueToken(builder, tokenType)) { + builder.remapCurrentToken(VALUE_KEYWORD); + tokenType = VALUE_KEYWORD; + } + else if (isSealedToken(builder, tokenType)) { + builder.remapCurrentToken(SEALED_KEYWORD); + tokenType = SEALED_KEYWORD; + } + if (isNonSealedToken(builder, tokenType)) { + PsiBuilder.Marker nonSealed = builder.mark(); + PsiBuilderUtil.advance(builder, 3); + nonSealed.collapse(NON_SEALED_KEYWORD); + isEmpty = false; + } + else if (modifiers.contains(tokenType)) { + builder.advanceLexer(); + isEmpty = false; + } + else if (tokenType == AT) { + if (KEYWORD_BIT_SET.contains(builder.lookAhead(1))) { + break; + } + parseAnnotation(builder); + isEmpty = false; + } + else { + break; + } + } + + done(modList, myJavaElementTypeContainer.MODIFIER_LIST, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + return Pair.create(modList, isEmpty); + } + + private PsiBuilder.Marker parseMethodFromLeftParenth(PsiBuilder builder, + PsiBuilder.Marker declaration, + boolean anno, + boolean constructor) { + parseParameterList(builder); + + eatBrackets(builder, constructor ? "expected.lbrace" : null); + + myParser.getReferenceParser() + .parseReferenceList(builder, THROWS_KEYWORD, myJavaElementTypeContainer.THROWS_LIST, COMMA); + + if (anno && expect(builder, DEFAULT_KEYWORD) && parseAnnotationValue(builder) == null) { + error(builder, JavaPsiBundle.message("expected.value")); + } + + return parseMethodBody(builder, declaration, anno); + } + + private @NotNull PsiBuilder.Marker parseMethodBody(PsiBuilder builder, PsiBuilder.Marker declaration, boolean anno) { + IElementType tokenType = builder.getTokenType(); + if (tokenType != SEMICOLON && tokenType != LBRACE) { + PsiBuilder.Marker error = builder.mark(); + // heuristic: going to next line obviously means method signature is over, starting new method (actually, another one completion hack) + CharSequence text = builder.getOriginalText(); + Loop: + while (true) { + for (int i = builder.getCurrentOffset() - 1; i >= 0; i--) { + char ch = text.charAt(i); + if (ch == '\n') { + break Loop; + } + else if (ch != ' ' && ch != '\t') break; + } + if (!expect(builder, APPEND_TO_METHOD_SET)) break; + } + error.error(JavaPsiBundle.message("expected.lbrace.or.semicolon")); + } + + if (!expect(builder, SEMICOLON)) { + if (builder.getTokenType() == LBRACE) { + myParser.getStatementParser().parseCodeBlock(builder); + } + } + + done(declaration, anno ? myJavaElementTypeContainer.ANNOTATION_METHOD : myJavaElementTypeContainer.METHOD, + builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + return declaration; + } + + public void parseParameterList(PsiBuilder builder) { + parseElementList(builder, ListType.METHOD); + } + + public void parseResourceList(PsiBuilder builder) { + parseElementList(builder, ListType.RESOURCE); + } + + public void parseLambdaParameterList(PsiBuilder builder, boolean typed) { + parseElementList(builder, typed ? ListType.LAMBDA_TYPED : ListType.LAMBDA_UNTYPED); + } + + private void parseElementList(PsiBuilder builder, ListType type) { + final boolean lambda = (type == ListType.LAMBDA_TYPED || type == ListType.LAMBDA_UNTYPED); + final boolean resources = (type == ListType.RESOURCE); + final PsiBuilder.Marker elementList = builder.mark(); + final boolean leftParenth = expect(builder, LPARENTH); + assert lambda || leftParenth : builder.getTokenType(); + + final IElementType delimiter = resources ? SEMICOLON : COMMA; + final String noDelimiterMsg = resources ? "expected.semicolon" : "expected.comma"; + final String noElementMsg = resources ? "expected.resource" : "expected.parameter"; + + PsiBuilder.Marker invalidElements = null; + String errorMessage = null; + boolean delimiterExpected = false; + boolean noElements = true; + while (true) { + final IElementType tokenType = builder.getTokenType(); + if (tokenType == null || type.getStopperTypes().contains(tokenType)) { + final boolean noLastElement = !delimiterExpected && (!noElements && !resources || noElements && resources); + if (noLastElement) { + final String key = lambda ? "expected.parameter" : "expected.identifier.or.type"; + error(builder, JavaPsiBundle.message(key)); + } + if (tokenType == RPARENTH) { + if (invalidElements != null) { + invalidElements.error(errorMessage); + invalidElements = null; + } + builder.advanceLexer(); + } + else { + if (!noLastElement || resources) { + if (invalidElements != null) { + invalidElements.error(errorMessage); + } + invalidElements = null; + if (leftParenth) { + error(builder, JavaPsiBundle.message("expected.rparen")); + } + } + } + break; + } + + if (delimiterExpected) { + if (builder.getTokenType() == delimiter) { + delimiterExpected = false; + if (invalidElements != null) { + invalidElements.error(errorMessage); + invalidElements = null; + } + builder.advanceLexer(); + continue; + } + } + else { + final PsiBuilder.Marker listElement = + type == ListType.RECORD_COMPONENTS ? parseParameterOrRecordComponent(builder, true, false, false, false) : + resources ? parseResource(builder) : + lambda ? parseLambdaParameter(builder, type == ListType.LAMBDA_TYPED) : + parseParameter(builder, true, false, false); + if (listElement != null) { + delimiterExpected = true; + if (invalidElements != null) { + invalidElements.errorBefore(errorMessage, listElement); + invalidElements = null; + } + noElements = false; + continue; + } + } + + if (invalidElements == null) { + if (builder.getTokenType() == delimiter) { + error(builder, JavaPsiBundle.message(noElementMsg)); + builder.advanceLexer(); + if (noElements && resources) { + noElements = false; + } + continue; + } + else { + invalidElements = builder.mark(); + errorMessage = JavaPsiBundle.message(delimiterExpected ? noDelimiterMsg : noElementMsg); + } + } + + // adding a reference, not simple tokens allows "Browse .." to work well + final PsiBuilder.Marker ref = myParser.getReferenceParser().parseJavaCodeReference(builder, true, true, false, false); + if (ref == null && builder.getTokenType() != null) { + builder.advanceLexer(); + } + } + + if (invalidElements != null) { + invalidElements.error(errorMessage); + } + + done(elementList, type.getNodeType(myJavaElementTypeContainer), builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + } + + public @Nullable PsiBuilder.Marker parseParameter(PsiBuilder builder, boolean ellipsis, boolean disjunctiveType, boolean varType) { + return parseParameterOrRecordComponent(builder, ellipsis, disjunctiveType, varType, true); + } + + public @Nullable PsiBuilder.Marker parseParameterOrRecordComponent(PsiBuilder builder, + boolean ellipsis, + boolean disjunctiveType, + boolean varType, + boolean isParameter) { + int typeFlags = 0; + if (ellipsis) typeFlags |= ReferenceParser.ELLIPSIS; + if (disjunctiveType) typeFlags |= ReferenceParser.DISJUNCTIONS; + if (varType) typeFlags |= ReferenceParser.VAR_TYPE; + return parseListElement(builder, true, typeFlags, + isParameter ? myJavaElementTypeContainer.PARAMETER : myJavaElementTypeContainer.RECORD_COMPONENT); + } + + public @Nullable PsiBuilder.Marker parseResource(PsiBuilder builder) { + PsiBuilder.Marker marker = builder.mark(); + + PsiBuilder.Marker expr = myParser.getExpressionParser().parse(builder); + if (expr != null && RESOURCE_EXPRESSIONS.contains(exprType(expr)) && builder.getTokenType() != IDENTIFIER) { + marker.done(myJavaElementTypeContainer.RESOURCE_EXPRESSION); + return marker; + } + + marker.rollbackTo(); + + return parseListElement(builder, true, ReferenceParser.VAR_TYPE, myJavaElementTypeContainer.RESOURCE_VARIABLE); + } + + public @Nullable PsiBuilder.Marker parseLambdaParameter(PsiBuilder builder, boolean typed) { + int flags = ReferenceParser.ELLIPSIS; + if (JavaFeature.VAR_LAMBDA_PARAMETER.isSufficient(getLanguageLevel(builder))) flags |= ReferenceParser.VAR_TYPE; + return parseListElement(builder, typed, flags, myJavaElementTypeContainer.PARAMETER); + } + + private @Nullable PsiBuilder.Marker parseListElement(PsiBuilder builder, boolean typed, int typeFlags, IElementType type) { + PsiBuilder.Marker param = builder.mark(); + + Pair modListInfo = parseModifierList(builder); + + if (typed) { + int flags = ReferenceParser.EAT_LAST_DOT | ReferenceParser.WILDCARD | typeFlags; + ReferenceParser.TypeInfo typeInfo = myParser.getReferenceParser().parseTypeInfo(builder, flags); + + if (typeInfo == null) { + if (Boolean.TRUE.equals(modListInfo.second)) { + param.rollbackTo(); + return null; + } + else { + error(builder, JavaPsiBundle.message("expected.type")); + emptyElement(builder, myJavaElementTypeContainer.TYPE); + } + } + } + + if (typed) { + IElementType tokenType = builder.getTokenType(); + if (tokenType == THIS_KEYWORD || tokenType == IDENTIFIER && builder.lookAhead(1) == DOT) { + PsiBuilder.Marker mark = builder.mark(); + + PsiBuilder.Marker expr = myParser.getExpressionParser().parse(builder); + if (expr != null && exprType(expr) == myJavaElementTypeContainer.THIS_EXPRESSION) { + mark.drop(); + done(param, myJavaElementTypeContainer.RECEIVER_PARAMETER, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + return param; + } + + mark.rollbackTo(); + } + } + + if (expect(builder, IDENTIFIER)) { + if (type == myJavaElementTypeContainer.PARAMETER || type == myJavaElementTypeContainer.RECORD_COMPONENT) { + eatBrackets(builder, null); + done(param, type, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + return param; + } + } + else { + error(builder, JavaPsiBundle.message("expected.identifier")); + param.drop(); + return modListInfo.first; + } + + if (expectOrError(builder, EQ, "expected.eq")) { + if (myParser.getExpressionParser().parse(builder) == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + } + + done(param, myJavaElementTypeContainer.RESOURCE_VARIABLE, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + return param; + } + + private @Nullable PsiBuilder.Marker parseFieldOrLocalVariable(PsiBuilder builder, + PsiBuilder.Marker declaration, + int declarationStart, + Context context) { + final IElementType varType; + if (context == Context.CLASS || context == Context.ANNOTATION_INTERFACE || context == Context.FILE + || context == Context.JSHELL) { + varType = myJavaElementTypeContainer.FIELD; + } + else if (context == Context.CODE_BLOCK) { + varType = myJavaElementTypeContainer.LOCAL_VARIABLE; + } + else { + declaration.drop(); + assert false : "Unexpected context: " + context; + return null; + } + + PsiBuilder.Marker variable = declaration; + boolean unclosed = false; + boolean eatSemicolon = true; + boolean shouldRollback; + boolean openMarker = true; + while (true) { + shouldRollback = true; + + if (!eatBrackets(builder, null)) { + unclosed = true; + } + + if (expect(builder, EQ)) { + final PsiBuilder.Marker expr = myParser.getExpressionParser().parse(builder); + if (expr != null) { + shouldRollback = false; + } + else { + error(builder, JavaPsiBundle.message("expected.expression")); + unclosed = true; + break; + } + } + + if (builder.getTokenType() != COMMA) break; + done(variable, varType, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + builder.advanceLexer(); + + if (builder.getTokenType() != IDENTIFIER) { + error(builder, JavaPsiBundle.message("expected.identifier")); + unclosed = true; + eatSemicolon = false; + openMarker = false; + break; + } + + variable = builder.mark(); + builder.advanceLexer(); + } + + if (builder.getTokenType() == SEMICOLON && eatSemicolon) { + builder.advanceLexer(); + } + else { + // special treatment (see DeclarationParserTest.testMultiLineUnclosed()) + if (!builder.eof() && shouldRollback) { + final CharSequence text = builder.getOriginalText(); + final int spaceEnd = builder.getCurrentOffset(); + final int spaceStart = CharArrayUtil.shiftBackward(text, spaceEnd - 1, WHITESPACES); + final int lineStart = CharArrayUtil.shiftBackwardUntil(text, spaceEnd, LINE_ENDS); + + if (declarationStart < lineStart && lineStart < spaceStart) { + final int newBufferEnd = CharArrayUtil.shiftForward(text, lineStart, WHITESPACES); + declaration.rollbackTo(); + return parse(stoppingBuilder(builder, newBufferEnd), context); + } + } + + if (!unclosed) { + error(builder, JavaPsiBundle.message("expected.semicolon")); + } + } + + if (openMarker) { + done(variable, varType, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + } + + return declaration; + } + + private boolean eatBrackets(PsiBuilder builder, @Nullable @PropertyKey(resourceBundle = JavaPsiBundle.BUNDLE) String errorKey) { + IElementType tokenType = builder.getTokenType(); + if (tokenType != LBRACKET && tokenType != AT) return true; + + PsiBuilder.Marker marker = builder.mark(); + + int count = 0; + while (true) { + parseAnnotations(builder); + if (!expect(builder, LBRACKET)) { + break; + } + ++count; + if (!expect(builder, RBRACKET)) { + break; + } + ++count; + } + + if (count == 0) { + // just annotation, most probably belongs to a next declaration + marker.rollbackTo(); + return true; + } + + if (errorKey != null) { + marker.error(JavaPsiBundle.message(errorKey)); + } + else { + marker.drop(); + } + + boolean paired = count % 2 == 0; + if (!paired) { + error(builder, JavaPsiBundle.message("expected.rbracket")); + } + return paired; + } + + public @Nullable PsiBuilder.Marker parseAnnotations(PsiBuilder builder) { + PsiBuilder.Marker firstAnno = null; + + while (builder.getTokenType() == AT) { + final PsiBuilder.Marker anno = parseAnnotation(builder); + if (firstAnno == null) firstAnno = anno; + } + + return firstAnno; + } + + public @NotNull PsiBuilder.Marker parseAnnotation(PsiBuilder builder) { + assert builder.getTokenType() == AT : builder.getTokenType(); + final PsiBuilder.Marker anno = builder.mark(); + builder.advanceLexer(); + + PsiBuilder.Marker classRef = null; + if (builder.getTokenType() == IDENTIFIER) { + classRef = myParser.getReferenceParser().parseJavaCodeReference(builder, true, false, false, false); + } + if (classRef == null) { + error(builder, JavaPsiBundle.message("expected.class.reference")); + } + + parseAnnotationParameterList(builder); + + done(anno, myJavaElementTypeContainer.ANNOTATION, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + return anno; + } + + private void parseAnnotationParameterList(PsiBuilder builder) { + PsiBuilder.Marker list = builder.mark(); + + if (!expect(builder, LPARENTH) || expect(builder, RPARENTH)) { + done(list, myJavaElementTypeContainer.ANNOTATION_PARAMETER_LIST, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + return; + } + + if (builder.getTokenType() == null) { + error(builder, JavaPsiBundle.message("expected.parameter.or.rparen")); + done(list, myJavaElementTypeContainer.ANNOTATION_PARAMETER_LIST, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + return; + } + PsiBuilder.Marker elementMarker = parseAnnotationElement(builder); + while (true) { + IElementType tokenType = builder.getTokenType(); + if (tokenType == null) { + error(builder, JavaPsiBundle.message(elementMarker == null ? "expected.parameter.or.rparen" : "expected.comma.or.rparen")); + break; + } + else if (expect(builder, RPARENTH)) { + break; + } + else if (tokenType == COMMA) { + builder.advanceLexer(); + elementMarker = parseAnnotationElement(builder); + if (elementMarker == null) { + error(builder, JavaPsiBundle.message("annotation.name.is.missing")); + tokenType = builder.getTokenType(); + if (tokenType != COMMA && tokenType != RPARENTH) { + break; + } + } + } + else { + error(builder, JavaPsiBundle.message(elementMarker == null ? "expected.parameter.or.rparen" : "expected.comma.or.rparen")); + tokenType = builder.lookAhead(1); + if (tokenType != COMMA && tokenType != RPARENTH) break; + builder.advanceLexer(); + } + } + + done(list, myJavaElementTypeContainer.ANNOTATION_PARAMETER_LIST, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + } + + private PsiBuilder.Marker parseAnnotationElement(PsiBuilder builder) { + PsiBuilder.Marker pair = builder.mark(); + + PsiBuilder.Marker valueMarker = parseAnnotationValue(builder); + if (valueMarker == null && builder.getTokenType() != EQ) { + pair.drop(); + return null; + } + if (builder.getTokenType() != EQ) { + done(pair, myJavaElementTypeContainer.NAME_VALUE_PAIR, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + return pair; + } + + pair.rollbackTo(); + pair = builder.mark(); + + expectOrError(builder, IDENTIFIER, "expected.identifier"); + expect(builder, EQ); + valueMarker = parseAnnotationValue(builder); + if (valueMarker == null) error(builder, JavaPsiBundle.message("expected.value")); + + done(pair, myJavaElementTypeContainer.NAME_VALUE_PAIR, builder, OldParserWhiteSpaceAndCommentSetHolder.INSTANCE); + return pair; + } + + public @Nullable PsiBuilder.Marker parseAnnotationValue(PsiBuilder builder) { + IElementType tokenType = builder.getTokenType(); + if (tokenType == AT) { + return parseAnnotation(builder); + } + else if (tokenType == LBRACE) { + return myParser.getExpressionParser().parseArrayInitializer( + builder, myJavaElementTypeContainer.ANNOTATION_ARRAY_INITIALIZER, this::parseAnnotationValue, "expected.value"); + } + else { + return myParser.getExpressionParser().parseConditional(builder); + } + } + + static boolean isRecordToken(PsiBuilder builder, IElementType tokenType) { + if (tokenType == IDENTIFIER && JavaKeywords.RECORD.equals(builder.getTokenText())) { + IElementType nextToken = builder.lookAhead(1); + if (nextToken == IDENTIFIER || + // The following tokens cannot be part of a valid record declaration, + // but we assume it to be a malformed record, rather than a malformed type. + MODIFIER_BIT_SET.contains(nextToken) || CLASS_KEYWORD_BIT_SET.contains(nextToken) || TYPE_START.contains(nextToken) || + nextToken == AT || nextToken == LBRACE || nextToken == RBRACE) { + return JavaFeature.RECORDS.isSufficient(getLanguageLevel(builder)); + } + } + return false; + } + + private static boolean isSealedToken(PsiBuilder builder, IElementType tokenType) { + return JavaFeature.SEALED_CLASSES.isSufficient(getLanguageLevel(builder)) && + tokenType == IDENTIFIER && + JavaKeywords.SEALED.equals(builder.getTokenText()); + } + + private static boolean isValueToken(PsiBuilder builder, IElementType tokenType) { + return JavaFeature.VALHALLA_VALUE_CLASSES.isSufficient(getLanguageLevel(builder)) && + tokenType == IDENTIFIER && + JavaKeywords.VALUE.equals(builder.getTokenText()); + } + + static boolean isNonSealedToken(PsiBuilder builder, IElementType tokenType) { + if (!JavaFeature.SEALED_CLASSES.isSufficient(getLanguageLevel(builder)) || + tokenType != IDENTIFIER || + !"non".equals(builder.getTokenText()) || + builder.lookAhead(1) != MINUS || + builder.lookAhead(2) != IDENTIFIER) { + return false; + } + PsiBuilder.Marker maybeNonSealed = builder.mark(); + PsiBuilderUtil.advance(builder, 2); + boolean isNonSealed = JavaKeywords.SEALED.equals(builder.getTokenText()); + maybeNonSealed.rollbackTo(); + return isNonSealed; + } + + public DeclarationParser(final @NotNull JavaParser javaParser) { + this.myParser = javaParser; + this.myJavaElementTypeContainer = javaParser.getJavaElementTypeFactory().getContainer(); + this.RESOURCE_EXPRESSIONS = TokenSet.create( + this.myJavaElementTypeContainer.REFERENCE_EXPRESSION, this.myJavaElementTypeContainer.THIS_EXPRESSION, + this.myJavaElementTypeContainer.METHOD_CALL_EXPRESSION, this.myJavaElementTypeContainer.NEW_EXPRESSION); + } + public enum Context { FILE, CLASS, CODE_BLOCK, ANNOTATION_INTERFACE, JSHELL } - public DeclarationParser(final @NotNull JavaParser javaParser) { - super(javaParser); - } + private enum ListType { + METHOD, + RESOURCE, + LAMBDA_TYPED, + LAMBDA_UNTYPED, + RECORD_COMPONENTS; - //for backward compatibility - @Override - public @Nullable PsiBuilder.Marker parse(@NotNull PsiBuilder builder, BaseContext context) { - return parse(builder, toContext(context)); - } - - private static Context toContext(BaseContext context) { - switch (context) { - case FILE: - return Context.FILE; - case CLASS: - return Context.CLASS; - case CODE_BLOCK: - return Context.CODE_BLOCK; - case ANNOTATION_INTERFACE: - return Context.ANNOTATION_INTERFACE; - case JSHELL: - return Context.JSHELL; - default: - throw new UnsupportedOperationException(); + IElementType getNodeType(AbstractBasicJavaElementTypeFactory.JavaElementTypeContainer javaElementTypeContainer) { + if (this == RESOURCE) { + return javaElementTypeContainer.RESOURCE_LIST; + } + if (this == RECORD_COMPONENTS) { + return javaElementTypeContainer.RECORD_HEADER; + } + return javaElementTypeContainer.PARAMETER_LIST; } - } - public PsiBuilder.Marker parse(@NotNull PsiBuilder builder, Context context) { - return super.parse(builder, toThinContext(context)); - } - - private static BaseContext toThinContext(Context context) { - switch (context) { - case FILE: - return BaseContext.FILE; - case CLASS: - return BaseContext.CLASS; - case CODE_BLOCK: - return BaseContext.CODE_BLOCK; - case ANNOTATION_INTERFACE: - return BaseContext.ANNOTATION_INTERFACE; - case JSHELL: - return BaseContext.JSHELL; - default: - throw new UnsupportedOperationException(); + TokenSet getStopperTypes() { + if (this == METHOD) { + return METHOD_PARAM_LIST_STOPPERS; + } + return PARAM_LIST_STOPPERS; } } } \ No newline at end of file diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/ExpressionParser.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/ExpressionParser.java index 2b2f624055ce..efc69bb20360 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/ExpressionParser.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/parser/ExpressionParser.java @@ -1,23 +1,86 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.lang.java.parser; +import com.intellij.core.JavaPsiBundle; import com.intellij.lang.PsiBuilder; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.PropertyKey; + +import java.util.function.Function; /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated -public class ExpressionParser extends BasicExpressionParser { +public class ExpressionParser { + static final int FORBID_LAMBDA_MASK = 0x1; + private static final boolean useNewImplementation = Registry.is("pratt.java.expression.parser", true); + private final OldExpressionParser myOldExpressionParser; + private final PrattExpressionParser myNewExpressionParser; + private final JavaParser myParser; + public ExpressionParser(@NotNull JavaParser javaParser) { - super(javaParser, new OldExpressionParser(javaParser), new PrattExpressionParser(javaParser)); + @NotNull OldExpressionParser oldExpressionParser = new OldExpressionParser(javaParser); + @NotNull PrattExpressionParser prattExpressionParser = new PrattExpressionParser(javaParser); + this.myOldExpressionParser = oldExpressionParser; + this.myNewExpressionParser = prattExpressionParser; + this.myParser = javaParser; } - @Override + @Deprecated public @Nullable PsiBuilder.Marker parseCaseLabel(@NotNull PsiBuilder builder) { - return super.parseCaseLabel(builder); + return myParser.getStatementParser().parseCaseLabel(builder).first; + } + + public @Nullable PsiBuilder.Marker parse(@NotNull PsiBuilder builder) { + if (useNewImplementation) { + return myNewExpressionParser.parse(builder); + } + return myOldExpressionParser.parse(builder); + } + + PsiBuilder.Marker parseConditionalAndForbiddingLambda(final PsiBuilder builder) { + if (useNewImplementation) { + return myNewExpressionParser.tryParseWithPrecedenceAtMost(builder, PrattExpressionParser.CONDITIONAL_EXPR_PRECEDENCE, + FORBID_LAMBDA_MASK); + } + return myOldExpressionParser.parseConditionalAnd(builder, FORBID_LAMBDA_MASK); + } + + public PsiBuilder.Marker parseArgumentList(final PsiBuilder builder) { + if (useNewImplementation) { + return myNewExpressionParser.parseArgumentList(builder); + } + return myOldExpressionParser.parseArgumentList(builder); + } + + PsiBuilder.Marker parseArrayInitializer(@NotNull PsiBuilder builder, + @NotNull IElementType type, + @NotNull Function elementParser, + @NotNull @PropertyKey(resourceBundle = JavaPsiBundle.BUNDLE) String missingElementKey) { + if (useNewImplementation) { + return myNewExpressionParser.parseArrayInitializer(builder, type, elementParser, missingElementKey); + } + return myOldExpressionParser.parseArrayInitializer(builder, type, elementParser, missingElementKey); + } + + PsiBuilder.Marker parseConditional(final PsiBuilder builder) { + if (useNewImplementation) { + return myNewExpressionParser.tryParseWithPrecedenceAtMost(builder, PrattExpressionParser.CONDITIONAL_EXPR_PRECEDENCE, + FORBID_LAMBDA_MASK); + } + return myOldExpressionParser.parseConditional(builder, 0); + } + + PsiBuilder.Marker parseAssignmentForbiddingLambda(final PsiBuilder builder) { + if (useNewImplementation) { + return myNewExpressionParser.parse(builder, FORBID_LAMBDA_MASK); + } + return myOldExpressionParser.parseAssignment(builder, FORBID_LAMBDA_MASK); } } \ No newline at end of file diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/FileParser.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/FileParser.java index c0da4b1e1045..ed881a5d3926 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/FileParser.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/parser/FileParser.java @@ -1,16 +1,244 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.lang.java.parser; +import com.intellij.AbstractBundle; +import com.intellij.core.JavaPsiBundle; +import com.intellij.java.syntax.parser.JavaKeywords; +import com.intellij.lang.PsiBuilder; +import com.intellij.openapi.util.NlsContexts; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.JavaTokenType; +import com.intellij.psi.impl.source.AbstractBasicJavaElementTypeFactory; +import com.intellij.psi.impl.source.BasicElementTypes; +import com.intellij.psi.impl.source.OldParserWhiteSpaceAndCommentSetHolder; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.TokenSet; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Predicate; + +import static com.intellij.lang.PsiBuilderUtil.expect; +import static com.intellij.lang.java.parser.JavaParserUtil.*; /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated -public class FileParser extends BasicFileParser { +public class FileParser { + + protected final TokenSet IMPORT_LIST_STOPPER_SET; + private final JavaParser myParser; + private final AbstractBasicJavaElementTypeFactory.JavaElementTypeContainer myJavaElementTypeContainer; + private final TokenSet IMPLICIT_CLASS_INDICATORS; + private final OldParserWhiteSpaceAndCommentSetHolder myWhiteSpaceAndCommentSetHolder = OldParserWhiteSpaceAndCommentSetHolder.INSTANCE; public FileParser(@NotNull JavaParser javaParser) { - super(javaParser); + this.myParser = javaParser; + this.myJavaElementTypeContainer = (javaParser).getJavaElementTypeFactory().getContainer(); + this.IMPORT_LIST_STOPPER_SET = TokenSet.orSet( + BasicElementTypes.BASIC_MODIFIER_BIT_SET, + TokenSet.create(JavaTokenType.CLASS_KEYWORD, JavaTokenType.INTERFACE_KEYWORD, JavaTokenType.ENUM_KEYWORD, JavaTokenType.AT)); + this.IMPLICIT_CLASS_INDICATORS = + TokenSet.create(this.myJavaElementTypeContainer.METHOD, this.myJavaElementTypeContainer.FIELD, + this.myJavaElementTypeContainer.CLASS_INITIALIZER); + } + + public void parse(@NotNull PsiBuilder builder) { + parseFile(builder, this::stopImportListParsing, JavaPsiBundle.INSTANCE, "expected.class.or.interface"); + } + + public void parseFile(@NotNull PsiBuilder builder, + @NotNull Predicate importListStopper, + @NotNull AbstractBundle bundle, + @NotNull String errorMessageKey) { + parsePackageStatement(builder); + + Pair impListInfo = parseImportList(builder, importListStopper); // (importList, isEmpty) + Boolean firstDeclarationOk = null; + PsiBuilder.Marker firstDeclaration = null; + + PsiBuilder.Marker invalidElements = null; + boolean isImplicitClass = false; + while (!builder.eof()) { + if (builder.getTokenType() == JavaTokenType.SEMICOLON) { + builder.advanceLexer(); + continue; + } + + PsiBuilder.Marker declaration = myParser.getModuleParser().parse(builder); + if (declaration == null) declaration = parseInitial(builder); + if (declaration != null) { + if (invalidElements != null) { + invalidElements.errorBefore(error(bundle, errorMessageKey), declaration); + invalidElements = null; + } + if (firstDeclarationOk == null) { + firstDeclarationOk = exprType(declaration) != myJavaElementTypeContainer.MODIFIER_LIST; + } + if (firstDeclaration == null) { + firstDeclaration = declaration; + } + if (!isImplicitClass && IMPLICIT_CLASS_INDICATORS.contains(exprType(declaration))) { + isImplicitClass = true; + } + continue; + } + + if (invalidElements == null) { + invalidElements = builder.mark(); + } + builder.advanceLexer(); + if (firstDeclarationOk == null) firstDeclarationOk = false; + } + + if (invalidElements != null) { + invalidElements.error(error(bundle, errorMessageKey)); + } + + if (impListInfo.second && firstDeclarationOk == Boolean.TRUE) { + impListInfo.first.setCustomEdgeTokenBinders(myWhiteSpaceAndCommentSetHolder.getPrecedingCommentBinder(getLanguageLevel(builder)), + null); // pass comments behind fake import list + firstDeclaration.setCustomEdgeTokenBinders( + myWhiteSpaceAndCommentSetHolder.getSpecialPrecedingCommentBinder(getLanguageLevel(builder)), null); + } + if (isImplicitClass) { + PsiBuilder.Marker beforeFirst = firstDeclaration.precede(); + done(beforeFirst, myJavaElementTypeContainer.IMPLICIT_CLASS, builder, myWhiteSpaceAndCommentSetHolder); + } + } + + private boolean stopImportListParsing(PsiBuilder b) { + IElementType type = b.getTokenType(); + if (IMPORT_LIST_STOPPER_SET.contains(type) || DeclarationParser.isRecordToken(b, type)) return true; + if (type == JavaTokenType.IDENTIFIER) { + String text = b.getTokenText(); + if (JavaKeywords.OPEN.equals(text) || JavaKeywords.MODULE.equals(text)) return true; + } + return false; + } + + protected @Nullable PsiBuilder.Marker parseInitial(PsiBuilder builder) { + return myParser.getDeclarationParser().parse(builder, DeclarationParser.Context.FILE); + } + + private void parsePackageStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + + if (!expect(builder, JavaTokenType.PACKAGE_KEYWORD)) { + PsiBuilder.Marker modList = builder.mark(); + myParser.getDeclarationParser().parseAnnotations(builder); + done(modList, myJavaElementTypeContainer.MODIFIER_LIST, builder, myWhiteSpaceAndCommentSetHolder); + if (!expect(builder, JavaTokenType.PACKAGE_KEYWORD)) { + statement.rollbackTo(); + return; + } + } + + PsiBuilder.Marker ref = myParser.getReferenceParser().parseJavaCodeReference(builder, true, false, false, false); + if (ref == null) { + statement.error(JavaPsiBundle.message("expected.class.or.interface")); + return; + } + + semicolon(builder); + + done(statement, myJavaElementTypeContainer.PACKAGE_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + } + + protected @NotNull Pair parseImportList(PsiBuilder builder, Predicate stopper) { + PsiBuilder.Marker list = builder.mark(); + + boolean isEmpty = true; + PsiBuilder.Marker invalidElements = null; + while (!builder.eof()) { + if (stopper.test(builder)) { + break; + } + else if (builder.getTokenType() == JavaTokenType.SEMICOLON) { + builder.advanceLexer(); + continue; + } + + PsiBuilder.Marker statement = parseImportStatement(builder); + if (statement != null) { + isEmpty = false; + if (invalidElements != null) { + invalidElements.errorBefore(JavaPsiBundle.message("unexpected.token"), statement); + invalidElements = null; + } + continue; + } + + if (invalidElements == null) { + invalidElements = builder.mark(); + } + builder.advanceLexer(); + } + + if (invalidElements != null) { + invalidElements.rollbackTo(); + } + + if (isEmpty) { + PsiBuilder.Marker precede = list.precede(); + list.rollbackTo(); + list = precede; + } + + done(list, myJavaElementTypeContainer.IMPORT_LIST, builder, myWhiteSpaceAndCommentSetHolder); + return Pair.create(list, isEmpty); + } + + protected @Nullable PsiBuilder.Marker parseImportStatement(PsiBuilder builder) { + if (builder.getTokenType() != JavaTokenType.IMPORT_KEYWORD) return null; + + PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + String identifierText = builder.getTokenText(); + IElementType type = getImportType(builder); + boolean isStatic = type == myJavaElementTypeContainer.IMPORT_STATIC_STATEMENT; + boolean isModule = type == myJavaElementTypeContainer.IMPORT_MODULE_STATEMENT; + final boolean isOk; + if (isModule) { + isOk = myParser.getModuleParser().parseName(builder) != null; + } + else { + isOk = myParser.getReferenceParser().parseImportCodeReference(builder, isStatic); + } + + //if it is `module` we should expect either `;` or `identifier` + if (isOk && !isModule && !isStatic && builder.getTokenType() != JavaTokenType.SEMICOLON && + JavaKeywords.MODULE.equals(identifierText)) { + JavaParserUtil.error(builder, JavaPsiBundle.message("expected.identifier.or.semicolon")); + } + else if (isOk) { + semicolon(builder); + } + + done(statement, type, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + private @NotNull IElementType getImportType(@NotNull PsiBuilder builder) { + IElementType type = builder.getTokenType(); + if (type == JavaTokenType.STATIC_KEYWORD) { + builder.advanceLexer(); + return myJavaElementTypeContainer.IMPORT_STATIC_STATEMENT; + } + if (type == JavaTokenType.IDENTIFIER && + JavaKeywords.MODULE.equals(builder.getTokenText()) && + builder.lookAhead(1) == JavaTokenType.IDENTIFIER) { + builder.remapCurrentToken(JavaTokenType.MODULE_KEYWORD); + builder.advanceLexer(); + return myJavaElementTypeContainer.IMPORT_MODULE_STATEMENT; + } + return myJavaElementTypeContainer.IMPORT_STATEMENT; + } + + private static @NotNull @NlsContexts.ParsingError String error(@NotNull AbstractBundle bundle, @NotNull String errorMessageKey) { + return bundle.getMessage(errorMessageKey); } } \ No newline at end of file diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/JavaParser.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/JavaParser.java index 73786221ff5e..541559e6c52a 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/JavaParser.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/parser/JavaParser.java @@ -9,7 +9,7 @@ import org.jetbrains.annotations.NotNull; * @deprecated Use {@link com.intellij.java.syntax.parser.JavaParser} instead */ @Deprecated -public class JavaParser extends BasicJavaParser { +public class JavaParser { public static final JavaParser INSTANCE = new JavaParser(); private final FileParser myFileParser; @@ -30,42 +30,34 @@ public class JavaParser extends BasicJavaParser { myPatternParser = new PatternParser(this); } - @Override public @NotNull FileParser getFileParser() { return myFileParser; } - @Override public @NotNull ModuleParser getModuleParser() { return myModuleParser; } - @Override public @NotNull DeclarationParser getDeclarationParser() { return myDeclarationParser; } - @Override public @NotNull StatementParser getStatementParser() { return myStatementParser; } - @Override public @NotNull ExpressionParser getExpressionParser() { return myExpressionParser; } - @Override public @NotNull ReferenceParser getReferenceParser() { return myReferenceParser; } - @Override public @NotNull PatternParser getPatternParser() { return myPatternParser; } - @Override public AbstractBasicJavaElementTypeFactory getJavaElementTypeFactory() { return JavaElementTypeFactory.INSTANCE; } diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/JavaParserUtil.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/JavaParserUtil.java index 7270537cc256..31e88335eb8d 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/JavaParserUtil.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/parser/JavaParserUtil.java @@ -3,37 +3,51 @@ package com.intellij.lang.java.parser; import com.intellij.core.JavaPsiBundle; import com.intellij.java.syntax.element.SyntaxElementTypes; +import com.intellij.java.syntax.element.lazyParser.ParsingUtil; import com.intellij.java.syntax.lexer.JavaLexer; -import com.intellij.lang.ASTNode; -import com.intellij.lang.LighterLazyParseableNode; -import com.intellij.lang.PsiBuilder; -import com.intellij.lang.WhitespacesAndCommentsBinder; +import com.intellij.lang.*; +import com.intellij.lang.impl.PsiBuilderAdapter; import com.intellij.lang.impl.TokenSequence; +import com.intellij.lang.java.JavaLanguage; import com.intellij.lang.java.JavaParserDefinition; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; import com.intellij.openapi.util.NlsContexts; import com.intellij.openapi.util.Pair; +import com.intellij.platform.syntax.SyntaxElementType; import com.intellij.platform.syntax.SyntaxElementTypeSet; import com.intellij.platform.syntax.element.SyntaxTokenTypes; +import com.intellij.platform.syntax.lexer.Lexer; import com.intellij.platform.syntax.lexer.TokenList; import com.intellij.platform.syntax.lexer.TokenListUtil; -import com.intellij.platform.syntax.psi.IntelliJLogger; +import com.intellij.platform.syntax.parser.SyntaxTreeBuilder; +import com.intellij.platform.syntax.psi.*; import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.JavaTokenType; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.source.OldParserWhiteSpaceAndCommentSetHolder; import com.intellij.psi.impl.source.WhiteSpaceAndCommentSetHolder; +import com.intellij.psi.impl.source.tree.TreeUtil; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiUtil; +import com.intellij.util.indexing.IndexingDataKeys; +import kotlin.Unit; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.PropertyKey; +import java.util.function.Function; import java.util.function.Predicate; +import static com.intellij.platform.syntax.lexer.TokenListUtil.tokenListLexer; + public final class JavaParserUtil { public static final SyntaxElementTypeSet WS_COMMENTS = SyntaxElementTypes.INSTANCE.getJAVA_COMMENT_BIT_SET().plus(SyntaxTokenTypes.getWHITE_SPACE()); @@ -41,6 +55,9 @@ public final class JavaParserUtil { private static final com.intellij.platform.syntax.Logger SYNTAX_LOGGER = IntelliJLogger.asSyntaxLogger(Logger.getInstance(JavaParserUtil.class)); + private static final Key LANG_LEVEL_KEY = Key.create("JavaParserUtil.LanguageLevel"); + private static final Key DEEP_PARSE_BLOCKS_IN_STATEMENTS = Key.create("JavaParserUtil.ParserExtender"); + public static @NotNull TokenList obtainTokens(@NotNull PsiFile file) { return CachedValuesManager.getCachedValue(file, () -> CachedValueProvider.Result.create( @@ -64,7 +81,8 @@ public final class JavaParserUtil { } @FunctionalInterface - public interface ParserWrapper extends BasicJavaParserUtil.ParserWrapper { + public interface ParserWrapper { + void parse(@NotNull SyntaxTreeBuilder builder, @NotNull LanguageLevel languageLevel); } /** @@ -94,183 +112,417 @@ public final class JavaParserUtil { private JavaParserUtil() { } - /** - * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} - */ - @Deprecated - public static void setLanguageLevel(final PsiBuilder builder, final LanguageLevel level) { - BasicJavaParserUtil.setLanguageLevel(builder, level); - } - - /** - * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} - */ - @Deprecated - public static @NotNull LanguageLevel getLanguageLevel(final PsiBuilder builder) { - return BasicJavaParserUtil.getLanguageLevel(builder); - } - - /** - * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} - */ - @Deprecated - public static void setParseStatementCodeBlocksDeep(final PsiBuilder builder, final boolean deep) { - BasicJavaParserUtil.setParseStatementCodeBlocksDeep(builder, deep); - } - - /** - * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} - */ - @Deprecated - public static boolean isParseStatementCodeBlocksDeep(final PsiBuilder builder) { - return BasicJavaParserUtil.isParseStatementCodeBlocksDeep(builder); - } - public static @NotNull PsiSyntaxBuilderWithLanguageLevel createSyntaxBuilder(final ASTNode chameleon) { - return BasicJavaParserUtil.createSyntaxBuilder(chameleon, - (psi) -> PsiUtil.getLanguageLevel(psi), - (psi) -> obtainTokens(psi)); + return createSyntaxBuilder(chameleon, + (psi) -> PsiUtil.getLanguageLevel(psi), + (psi) -> obtainTokens(psi)); } /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static @NotNull PsiBuilder createBuilder(final ASTNode chameleon) { - return BasicJavaParserUtil.createBuilder(chameleon, - (psi) -> PsiUtil.getLanguageLevel(psi), - (level) -> JavaParserDefinition.createLexer(level), - (psi) -> obtainTokensOutdated(psi)); + return createBuilder(chameleon, + (psi) -> PsiUtil.getLanguageLevel(psi), + (level) -> JavaParserDefinition.createLexer(level), + (psi) -> obtainTokensOutdated(psi)); } /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static @NotNull PsiBuilder createBuilder(final LighterLazyParseableNode chameleon) { - return BasicJavaParserUtil.createBuilder(chameleon, + return createBuilder(chameleon, (psi) -> PsiUtil.getLanguageLevel(psi), (level) -> JavaParserDefinition.createLexer(level)); } /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static void done(final PsiBuilder.Marker marker, final IElementType type) { - BasicJavaParserUtil.done(marker, type, WhiteSpaceAndCommentSetHolder.INSTANCE); + done(marker, type, WhiteSpaceAndCommentSetHolder.INSTANCE); + } + + + /** + * @deprecated Use the new Java syntax library instead. + * See {@link com.intellij.java.syntax.parser.JavaParser} + */ + @Deprecated + public static void setLanguageLevel(final PsiBuilder builder, final LanguageLevel level) { + builder.putUserData(LANG_LEVEL_KEY, level); } /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} + */ + @Deprecated + public static @NotNull LanguageLevel getLanguageLevel(final PsiBuilder builder) { + final LanguageLevel level = builder.getUserData(LANG_LEVEL_KEY); + assert level != null : builder; + return level; + } + + /** + * @deprecated Use the new Java syntax library instead. + * See {@link com.intellij.java.syntax.parser.JavaParser} + */ + @Deprecated + public static void setParseStatementCodeBlocksDeep(final PsiBuilder builder, final boolean deep) { + builder.putUserData(DEEP_PARSE_BLOCKS_IN_STATEMENTS, deep); + } + + /** + * @deprecated Use the new Java syntax library instead. + * See {@link com.intellij.java.syntax.parser.JavaParser} + */ + @Deprecated + public static boolean isParseStatementCodeBlocksDeep(final PsiBuilder builder) { + return Boolean.TRUE.equals(builder.getUserData(DEEP_PARSE_BLOCKS_IN_STATEMENTS)); + } + + /** + * @deprecated use {@link JavaParserUtil#done(PsiBuilder.Marker, IElementType, PsiBuilder, WhiteSpaceAndCommentSetHolder)} + */ + @Deprecated + public static void done(final @NotNull PsiBuilder.Marker marker, + final @NotNull IElementType type, + final @NotNull WhiteSpaceAndCommentSetHolder commentSetHolder) { + marker.done(type); + final WhitespacesAndCommentsBinder left = + commentSetHolder.getPrecedingCommentSet().contains(type) ? commentSetHolder.getPrecedingCommentBinder(LanguageLevel.HIGHEST) + : null; + final WhitespacesAndCommentsBinder right = + commentSetHolder.getTrailingCommentSet().contains(type) ? commentSetHolder.getTrailingCommentBinder() + : null; + marker.setCustomEdgeTokenBinders(left, right); + } + + /** + * @deprecated Use the new Java syntax library instead. + * See {@link com.intellij.java.syntax.parser.JavaParser} + */ + @Deprecated + public static void done(final @NotNull PsiBuilder.Marker marker, + final @NotNull IElementType type, + final @NotNull PsiBuilder builder, + final @NotNull OldParserWhiteSpaceAndCommentSetHolder commentSetHolder) { + marker.done(type); + final WhitespacesAndCommentsBinder left = + commentSetHolder.getPrecedingCommentSet().contains(type) ? commentSetHolder.getPrecedingCommentBinder(getLanguageLevel(builder)) + : null; + final WhitespacesAndCommentsBinder right = + commentSetHolder.getTrailingCommentSet().contains(type) ? commentSetHolder.getTrailingCommentBinder() + : null; + marker.setCustomEdgeTokenBinders(left, right); + } + + /** + * @deprecated Use the new Java syntax library instead. + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static @Nullable IElementType exprType(final @Nullable PsiBuilder.Marker marker) { - return BasicJavaParserUtil.exprType(marker); + return marker != null ? marker.getTokenType() : null; } + // used instead of PsiBuilder.error() as it keeps all subsequent error messages + /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static void error(final PsiBuilder builder, @NotNull @NlsContexts.ParsingError String message) { - BasicJavaParserUtil.error(builder, message); + builder.mark().error(message); } /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static void error(final PsiBuilder builder, @NotNull @NlsContexts.ParsingError String message, final @Nullable PsiBuilder.Marker before) { - BasicJavaParserUtil.error(builder, message, before); + if (before == null) { + error(builder, message); + } + else { + before.precede().errorBefore(message, before); + } } /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static boolean expectOrError(PsiBuilder builder, TokenSet expected, @PropertyKey(resourceBundle = JavaPsiBundle.BUNDLE) String key) { - return BasicJavaParserUtil.expectOrError(builder, expected, key); + if (!PsiBuilderUtil.expect(builder, expected)) { + error(builder, JavaPsiBundle.message(key)); + return false; + } + return true; } /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static boolean expectOrError(PsiBuilder builder, IElementType expected, @PropertyKey(resourceBundle = JavaPsiBundle.BUNDLE) String key) { - return BasicJavaParserUtil.expectOrError(builder, expected, key); + if (!PsiBuilderUtil.expect(builder, expected)) { + error(builder, JavaPsiBundle.message(key)); + return false; + } + return true; } - /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static void emptyElement(final PsiBuilder builder, final IElementType type) { - BasicJavaParserUtil.emptyElement(builder, type); + builder.mark().done(type); } /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static void emptyElement(final PsiBuilder.Marker before, final IElementType type) { - BasicJavaParserUtil.emptyElement(before, type); + before.precede().doneBefore(type, before); } /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static void semicolon(final PsiBuilder builder) { - BasicJavaParserUtil.semicolon(builder); + expectOrError(builder, JavaTokenType.SEMICOLON, "expected.semicolon"); } /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static PsiBuilder braceMatchingBuilder(final PsiBuilder builder) { - return BasicJavaParserUtil.braceMatchingBuilder(builder); + final PsiBuilder.Marker pos = builder.mark(); + + int braceCount = 1; + while (!builder.eof()) { + final IElementType tokenType = builder.getTokenType(); + if (tokenType == JavaTokenType.LBRACE) { + braceCount++; + } + else if (tokenType == JavaTokenType.RBRACE) braceCount--; + if (braceCount == 0) break; + builder.advanceLexer(); + } + final int stopAt = builder.getCurrentOffset(); + + pos.rollbackTo(); + + return stoppingBuilder(builder, stopAt); } /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static PsiBuilder stoppingBuilder(final PsiBuilder builder, final int stopAt) { - return BasicJavaParserUtil.stoppingBuilder(builder, stopAt); + return new PsiBuilderAdapter(builder) { + @Override + public IElementType getTokenType() { + return getCurrentOffset() < stopAt ? super.getTokenType() : null; + } + + @Override + public boolean eof() { + return getCurrentOffset() >= stopAt || super.eof(); + } + }; } /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated public static PsiBuilder stoppingBuilder(final PsiBuilder builder, final Predicate> condition) { - return BasicJavaParserUtil.stoppingBuilder(builder, condition); + return new PsiBuilderAdapter(builder) { + @Override + public IElementType getTokenType() { + final Pair input = Pair.create(builder.getTokenType(), builder.getTokenText()); + return condition.test(input) ? null : super.getTokenType(); + } + + @Override + public boolean eof() { + final Pair input = Pair.create(builder.getTokenType(), builder.getTokenText()); + return condition.test(input) || super.eof(); + } + }; + } + + public static @Nullable ASTNode parseFragmentWithHighestLanguageLevel( + @NotNull ASTNode chameleon, + @NotNull JavaParserUtil.ParserWrapper wrapper, + @NotNull Function lexer + ) { + return parseFragment(chameleon, wrapper, true, LanguageLevel.HIGHEST, lexer); + } + + public static @Nullable ASTNode parseFragment( + @NotNull ASTNode chameleon, + @NotNull JavaParserUtil.ParserWrapper wrapper, + boolean eatAll, + @NotNull LanguageLevel level) { + return parseFragment(chameleon, wrapper, eatAll, level, JavaLexer::new); + } + + public static @Nullable ASTNode parseFragment( + @NotNull ASTNode chameleon, + @NotNull JavaParserUtil.ParserWrapper wrapper, + boolean eatAll, + @NotNull LanguageLevel level, + @NotNull Function javaLexer + ) { + final PsiElement psi = chameleon.getTreeParent() != null ? chameleon.getTreeParent().getPsi() : chameleon.getPsi(); + assert psi != null : chameleon; + + PsiSyntaxBuilderFactory factory = PsiSyntaxBuilderFactory.getInstance(); + Lexer lexer = javaLexer.apply(level); + PsiSyntaxBuilder psiBuilder = factory.createBuilder(chameleon, lexer, chameleon.getElementType().getLanguage(), chameleon.getChars()); + + long startTime = System.nanoTime(); + SyntaxTreeBuilder builder = psiBuilder.getSyntaxTreeBuilder(); + ElementTypeConverter converter = ElementTypeConverters.getConverter(JavaLanguage.INSTANCE); + SyntaxElementType type = ElementTypeConverterKt.convertNotNull(converter, chameleon.getElementType()); + ParsingUtil.parseFragment(builder, type, eatAll, () -> { + wrapper.parse(builder, level); + return Unit.INSTANCE; + }); + + ASTNode result = psiBuilder.getTreeBuilt().getFirstChildNode(); + ParsingDiagnostics.registerParse(builder, chameleon.getElementType().getLanguage(), System.nanoTime() - startTime); + return result; + } + + + public static @NotNull PsiSyntaxBuilderWithLanguageLevel createSyntaxBuilder(@NotNull ASTNode chameleon, + @NotNull Function languageLevelFunction, + @NotNull Function psiAsLexer) { + PsiElement psi = chameleon.getPsi(); + assert psi != null : chameleon; + + CharSequence indexedText = psi.getUserData(IndexingDataKeys.FILE_TEXT_CONTENT_KEY); + + CharSequence text; + if (TreeUtil.isCollapsedChameleon(chameleon)) { + text = chameleon.getChars(); + } + else { + text = indexedText; + if (text == null) text = chameleon.getChars(); + } + + LanguageLevel level = languageLevelFunction.apply(psi); + Lexer lexer = psi instanceof PsiFile && indexedText != null ? tokenListLexer(psiAsLexer.apply((PsiFile)psi), SYNTAX_LOGGER) + : new JavaLexer(level); + Language language = psi.getLanguage(); + if (!language.isKindOf(JavaLanguage.INSTANCE)) language = JavaLanguage.INSTANCE; + PsiSyntaxBuilderFactory factory = PsiSyntaxBuilderFactory.getInstance(); + PsiSyntaxBuilder builder = factory.createBuilder(chameleon, lexer, language, text); + + return new PsiSyntaxBuilderWithLanguageLevel(builder, level); + } + + public static @NotNull PsiSyntaxBuilderWithLanguageLevel createSyntaxBuilder(@NotNull LighterLazyParseableNode chameleon, + @NotNull Function languageLevelFunction) { + PsiElement psi = chameleon.getContainingFile(); + assert psi != null : chameleon; + + PsiSyntaxBuilderFactory factory = PsiSyntaxBuilderFactory.getInstance(); + LanguageLevel level = languageLevelFunction.apply(psi); + Lexer lexer = new JavaLexer(level); + PsiSyntaxBuilder builder = factory.createBuilder(chameleon, lexer, chameleon.getTokenType().getLanguage(), chameleon.getText()); + + return new PsiSyntaxBuilderWithLanguageLevel(builder, level); + } + + /** + * @deprecated Use the new Java syntax library instead. + * See {@link com.intellij.java.syntax.parser.JavaParser} + */ + @Deprecated + public static @NotNull PsiBuilder createBuilder(final @NotNull ASTNode chameleon, + @NotNull Function languageLevelFunction, + @NotNull Function lexerFunction, + @NotNull Function psiAsLexer) { + final PsiElement psi = chameleon.getPsi(); + assert psi != null : chameleon; + final Project project = psi.getProject(); + + CharSequence indexedText = psi.getUserData(IndexingDataKeys.FILE_TEXT_CONTENT_KEY); + + CharSequence text; + if (TreeUtil.isCollapsedChameleon(chameleon)) { + text = chameleon.getChars(); + } + else { + text = indexedText; + if (text == null) text = chameleon.getChars(); + } + + LanguageLevel level = languageLevelFunction.apply(psi); + com.intellij.lexer.Lexer lexer = psi instanceof PsiFile && indexedText != null ? psiAsLexer.apply((PsiFile)psi).asLexer() + : lexerFunction.apply(level); + Language language = psi.getLanguage(); + if (!language.isKindOf(JavaLanguage.INSTANCE)) language = JavaLanguage.INSTANCE; + PsiBuilder builder = PsiBuilderFactory.getInstance().createBuilder(project, chameleon, lexer, language, text); + setLanguageLevel(builder, level); + + return builder; + } + + /** + * @deprecated Use the new Java syntax library instead. + * See {@link com.intellij.java.syntax.parser.JavaParser} + */ + @Deprecated + public static @NotNull PsiBuilder createBuilder(@NotNull LighterLazyParseableNode chameleon, + @NotNull Function languageLevelFunction, + @NotNull Function lexerFunction) { + final PsiElement psi = chameleon.getContainingFile(); + assert psi != null : chameleon; + final Project project = psi.getProject(); + + final PsiBuilderFactory factory = PsiBuilderFactory.getInstance(); + final LanguageLevel level = languageLevelFunction.apply(psi); + final com.intellij.lexer.Lexer lexer = lexerFunction.apply(level); + final PsiBuilder builder = + factory.createBuilder(project, chameleon, lexer, chameleon.getTokenType().getLanguage(), chameleon.getText()); + setLanguageLevel(builder, level); + + return builder; } } \ No newline at end of file diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/JavadocParser.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/JavadocParser.java deleted file mode 100644 index acc401902997..000000000000 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/JavadocParser.java +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.lang.java.parser; - -import com.intellij.lang.PsiBuilder; -import com.intellij.psi.impl.source.tree.JavaDocElementTypeFactory; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -/** - * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} - */ -@ApiStatus.ScheduledForRemoval -@Deprecated -public final class JavadocParser { - - private JavadocParser() { } - - public static void parseJavadocReference(@NotNull PsiBuilder builder) { - BasicJavaDocParser.parseJavadocReference(builder, JavaParser.INSTANCE); - } - - public static void parseJavadocType(@NotNull PsiBuilder builder) { - BasicJavaDocParser.parseJavadocType(builder, JavaParser.INSTANCE); - } - - public static void parseDocCommentText(@NotNull PsiBuilder builder) { - BasicJavaDocParser.parseDocCommentText(builder, JavaDocElementTypeFactory.INSTANCE.getContainer()); - } -} \ No newline at end of file diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/ModuleParser.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/ModuleParser.java index fb555907d747..1a747ae3db4b 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/ModuleParser.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/parser/ModuleParser.java @@ -15,15 +15,328 @@ */ package com.intellij.lang.java.parser; +import com.intellij.core.JavaPsiBundle; +import com.intellij.java.syntax.parser.JavaKeywords; +import com.intellij.lang.PsiBuilder; +import com.intellij.openapi.util.NlsContexts; +import com.intellij.psi.JavaTokenType; +import com.intellij.psi.impl.source.AbstractBasicJavaElementTypeFactory; +import com.intellij.psi.impl.source.BasicElementTypes; +import com.intellij.psi.impl.source.OldParserWhiteSpaceAndCommentSetHolder; +import com.intellij.psi.tree.IElementType; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +import static com.intellij.lang.PsiBuilderUtil.expect; +import static com.intellij.lang.java.parser.JavaParserUtil.error; +import static com.intellij.lang.java.parser.JavaParserUtil.semicolon; /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated -public class ModuleParser extends BasicModuleParser { +public class ModuleParser { + private static final Set STATEMENT_KEYWORDS = + ContainerUtil.newHashSet(JavaKeywords.REQUIRES, JavaKeywords.EXPORTS, JavaKeywords.USES, JavaKeywords.PROVIDES); + private final JavaParser myParser; + private final AbstractBasicJavaElementTypeFactory.JavaElementTypeContainer myJavaElementTypeContainer; + private final OldParserWhiteSpaceAndCommentSetHolder myWhiteSpaceAndCommentSetHolder = OldParserWhiteSpaceAndCommentSetHolder.INSTANCE; + public ModuleParser(@NotNull JavaParser parser) { - super(parser); + this.myParser = parser; + this.myJavaElementTypeContainer = parser.getJavaElementTypeFactory().getContainer(); + } + + public @Nullable PsiBuilder.Marker parse(@NotNull PsiBuilder builder) { + PsiBuilder.Marker module = builder.mark(); + + PsiBuilder.Marker firstAnnotation = myParser.getDeclarationParser().parseAnnotations(builder); + + IElementType type = builder.getTokenType(); + String text = type == JavaTokenType.IDENTIFIER ? builder.getTokenText() : null; + if (!(JavaKeywords.OPEN.equals(text) || JavaKeywords.MODULE.equals(text))) { + module.rollbackTo(); + return null; + } + + PsiBuilder.Marker modifierList = firstAnnotation != null ? firstAnnotation.precede() : builder.mark(); + if (JavaKeywords.OPEN.equals(text)) { + mapAndAdvance(builder, JavaTokenType.OPEN_KEYWORD); + text = builder.getTokenText(); + } + JavaParserUtil.done(modifierList, myJavaElementTypeContainer.MODIFIER_LIST, builder, myWhiteSpaceAndCommentSetHolder); + + if (JavaKeywords.MODULE.equals(text)) { + mapAndAdvance(builder, JavaTokenType.MODULE_KEYWORD); + } + else { + module.drop(); + parseExtras(builder, JavaPsiBundle.message("expected.module.declaration")); + return module; + } + + if (parseName(builder) == null) { + module.drop(); + if (builder.getTokenType() != null) { + parseExtras(builder, JavaPsiBundle.message("expected.module.declaration")); + } + else { + error(builder, JavaPsiBundle.message("expected.identifier")); + } + return module; + } + + if (!expect(builder, JavaTokenType.LBRACE)) { + if (builder.getTokenType() != null) { + parseExtras(builder, JavaPsiBundle.message("expected.module.declaration")); + } + else { + error(builder, JavaPsiBundle.message("expected.lbrace")); + } + } + else { + parseModuleContent(builder); + } + + JavaParserUtil.done(module, myJavaElementTypeContainer.MODULE, builder, myWhiteSpaceAndCommentSetHolder); + + if (builder.getTokenType() != null) { + parseExtras(builder, JavaPsiBundle.message("unexpected.tokens")); + } + + return module; + } + + public PsiBuilder.Marker parseName(PsiBuilder builder) { + PsiBuilder.Marker nameElement = builder.mark(); + boolean empty = true; + + boolean idExpected = true; + while (true) { + IElementType t = builder.getTokenType(); + if (t == JavaTokenType.IDENTIFIER) { + if (!idExpected) error(builder, JavaPsiBundle.message("expected.dot")); + idExpected = false; + } + else if (t == JavaTokenType.DOT) { + if (idExpected) error(builder, JavaPsiBundle.message("expected.identifier")); + idExpected = true; + } + else { + break; + } + builder.advanceLexer(); + empty = false; + } + + if (!empty) { + if (idExpected) error(builder, JavaPsiBundle.message("expected.identifier")); + nameElement.done(myJavaElementTypeContainer.MODULE_REFERENCE); + return nameElement; + } + else { + nameElement.drop(); + return null; + } + } + + private void parseModuleContent(PsiBuilder builder) { + IElementType token; + PsiBuilder.Marker invalid = null; + + while ((token = builder.getTokenType()) != null) { + if (token == JavaTokenType.RBRACE) { + break; + } + + if (token == JavaTokenType.SEMICOLON) { + if (invalid != null) { + invalid.error(JavaPsiBundle.message("expected.module.statement")); + invalid = null; + } + builder.advanceLexer(); + continue; + } + + PsiBuilder.Marker statement = parseStatement(builder); + if (statement == null) { + if (invalid == null) invalid = builder.mark(); + builder.advanceLexer(); + } + else if (invalid != null) { + invalid.errorBefore(JavaPsiBundle.message("expected.module.statement"), statement); + invalid = null; + } + } + + if (invalid != null) { + invalid.error(JavaPsiBundle.message("expected.module.statement")); + } + + if (!expect(builder, JavaTokenType.RBRACE) && invalid == null) { + error(builder, JavaPsiBundle.message("expected.rbrace")); + } + } + + private PsiBuilder.Marker parseStatement(PsiBuilder builder) { + String kw = builder.getTokenText(); + if (JavaKeywords.REQUIRES.equals(kw)) return parseRequiresStatement(builder); + if (JavaKeywords.EXPORTS.equals(kw)) return parseExportsStatement(builder); + if (JavaKeywords.OPENS.equals(kw)) return parseOpensStatement(builder); + if (JavaKeywords.USES.equals(kw)) return parseUsesStatement(builder); + if (JavaKeywords.PROVIDES.equals(kw)) return parseProvidesStatement(builder); + return null; + } + + private PsiBuilder.Marker parseRequiresStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + mapAndAdvance(builder, JavaTokenType.REQUIRES_KEYWORD); + + PsiBuilder.Marker modifierList = builder.mark(); + while (true) { + if (expect(builder, BasicElementTypes.BASIC_MODIFIER_BIT_SET)) continue; + if (builder.getTokenType() == JavaTokenType.IDENTIFIER && JavaKeywords.TRANSITIVE.equals(builder.getTokenText())) { + mapAndAdvance(builder, JavaTokenType.TRANSITIVE_KEYWORD); + continue; + } + break; + } + JavaParserUtil.done(modifierList, myJavaElementTypeContainer.MODIFIER_LIST, builder, myWhiteSpaceAndCommentSetHolder); + + if (parseNameRef(builder) != null) { + semicolon(builder); + } + else { + expect(builder, JavaTokenType.SEMICOLON); + } + + statement.done(myJavaElementTypeContainer.REQUIRES_STATEMENT); + return statement; + } + + private PsiBuilder.Marker parseExportsStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + mapAndAdvance(builder, JavaTokenType.EXPORTS_KEYWORD); + return parsePackageStatement(builder, statement, myJavaElementTypeContainer.EXPORTS_STATEMENT); + } + + private PsiBuilder.Marker parseOpensStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + mapAndAdvance(builder, JavaTokenType.OPENS_KEYWORD); + return parsePackageStatement(builder, statement, myJavaElementTypeContainer.OPENS_STATEMENT); + } + + private @NotNull PsiBuilder.Marker parsePackageStatement(PsiBuilder builder, PsiBuilder.Marker statement, IElementType type) { + boolean hasError = false; + + if (parseClassOrPackageRef(builder) != null) { + if (JavaKeywords.TO.equals(builder.getTokenText())) { + mapAndAdvance(builder, JavaTokenType.TO_KEYWORD); + + while (true) { + PsiBuilder.Marker ref = parseNameRef(builder); + if (!expect(builder, JavaTokenType.COMMA)) { + if (ref == null) hasError = true; + break; + } + } + } + } + else { + error(builder, JavaPsiBundle.message("expected.package.reference")); + hasError = true; + } + + if (!hasError) { + semicolon(builder); + } + else { + expect(builder, JavaTokenType.SEMICOLON); + } + + statement.done(type); + return statement; + } + + private PsiBuilder.Marker parseUsesStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + mapAndAdvance(builder, JavaTokenType.USES_KEYWORD); + + if (parseClassOrPackageRef(builder) != null) { + semicolon(builder); + } + else { + error(builder, JavaPsiBundle.message("expected.class.reference")); + expect(builder, JavaTokenType.SEMICOLON); + } + + statement.done(myJavaElementTypeContainer.USES_STATEMENT); + return statement; + } + + private PsiBuilder.Marker parseProvidesStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + boolean hasError = false; + mapAndAdvance(builder, JavaTokenType.PROVIDES_KEYWORD); + + if (parseClassOrPackageRef(builder) == null) { + error(builder, JavaPsiBundle.message("expected.class.reference")); + hasError = true; + } + + if (JavaKeywords.WITH.equals(builder.getTokenText())) { + builder.remapCurrentToken(JavaTokenType.WITH_KEYWORD); + hasError = myParser.getReferenceParser() + .parseReferenceList(builder, JavaTokenType.WITH_KEYWORD, myJavaElementTypeContainer.PROVIDES_WITH_LIST, JavaTokenType.COMMA); + } + else if (!hasError) { + IElementType next = builder.getTokenType(); + if (next == JavaTokenType.IDENTIFIER && !STATEMENT_KEYWORDS.contains(builder.getTokenText())) { + PsiBuilder.Marker marker = builder.mark(); + builder.advanceLexer(); + marker.error(JavaPsiBundle.message("expected.with")); + } + else { + error(builder, JavaPsiBundle.message("expected.with")); + } + hasError = true; + } + + if (!hasError) { + semicolon(builder); + } + else { + expect(builder, JavaTokenType.SEMICOLON); + } + + statement.done(myJavaElementTypeContainer.PROVIDES_STATEMENT); + return statement; + } + + private PsiBuilder.Marker parseNameRef(PsiBuilder builder) { + PsiBuilder.Marker name = parseName(builder); + if (name == null) { + error(builder, JavaPsiBundle.message("expected.identifier")); + } + return name; + } + + private PsiBuilder.Marker parseClassOrPackageRef(PsiBuilder builder) { + return myParser.getReferenceParser().parseJavaCodeReference(builder, true, false, false, false); + } + + private static void mapAndAdvance(PsiBuilder builder, IElementType keyword) { + builder.remapCurrentToken(keyword); + builder.advanceLexer(); + } + + private static void parseExtras(PsiBuilder builder, @NotNull @NlsContexts.ParsingError String message) { + PsiBuilder.Marker extras = builder.mark(); + while (builder.getTokenType() != null) builder.advanceLexer(); + extras.error(message); } } \ No newline at end of file diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/OldExpressionParser.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/OldExpressionParser.java index d55713e50039..6ebf38bd0855 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/OldExpressionParser.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/parser/OldExpressionParser.java @@ -1,15 +1,1056 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.lang.java.parser; +import com.intellij.core.JavaPsiBundle; +import com.intellij.lang.PsiBuilder; +import com.intellij.lang.WhitespacesBinders; +import com.intellij.psi.impl.source.AbstractBasicJavaElementTypeFactory; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.TokenSet; +import com.intellij.util.BitUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.PropertyKey; + +import java.util.function.Function; + +import static com.intellij.lang.PsiBuilderUtil.*; +import static com.intellij.lang.java.parser.JavaParserUtil.*; +import static com.intellij.psi.JavaTokenType.*; +import static com.intellij.psi.impl.source.tree.ElementType.*; /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated -public class OldExpressionParser extends BasicOldExpressionParser { +public class OldExpressionParser { + static final TokenSet RELATIONAL_OPS = TokenSet.create(LT, GT, LE, GE); + static final TokenSet POSTFIX_OPS = TokenSet.create(PLUSPLUS, MINUSMINUS); + static final TokenSet PREF_ARITHMETIC_OPS = TokenSet.orSet(POSTFIX_OPS, TokenSet.create(PLUS, MINUS)); + static final TokenSet PREFIX_OPS = TokenSet.orSet(PREF_ARITHMETIC_OPS, TokenSet.create(TILDE, EXCL)); + static final TokenSet CONDITIONAL_OR_OPS = TokenSet.create(OROR); + static final TokenSet CONDITIONAL_AND_OPS = TokenSet.create(ANDAND); + static final TokenSet OR_OPS = TokenSet.create(OR); + static final TokenSet XOR_OPS = TokenSet.create(XOR); + static final TokenSet AND_OPS = TokenSet.create(AND); + static final TokenSet EQUALITY_OPS = TokenSet.create(EQEQ, NE); + static final TokenSet ARGS_LIST_END = TokenSet.create(RPARENTH, RBRACE, RBRACKET); + static final TokenSet ARGS_LIST_CONTINUE = TokenSet.create( + IDENTIFIER, BAD_CHARACTER, COMMA, INTEGER_LITERAL, STRING_LITERAL); + static final TokenSet THIS_OR_SUPER = TokenSet.create(THIS_KEYWORD, SUPER_KEYWORD); + static final TokenSet ID_OR_SUPER = TokenSet.create(IDENTIFIER, SUPER_KEYWORD); + final TokenSet TYPE_START; + private final JavaParser myParser; + private final AbstractBasicJavaElementTypeFactory.JavaElementTypeContainer myJavaElementTypeContainer; + public OldExpressionParser(@NotNull JavaParser javaParser) { - super(javaParser); + this.myParser = javaParser; + this.myJavaElementTypeContainer = (javaParser).getJavaElementTypeFactory().getContainer(); + this.TYPE_START = TokenSet.orSet( + PRIMITIVE_TYPE_BIT_SET, TokenSet.create(IDENTIFIER, AT)); } + + public @Nullable PsiBuilder.Marker parse(@NotNull PsiBuilder builder) { + return parseAssignment(builder); + } + + //for compatibility + @SuppressWarnings("unused") + @Nullable + PsiBuilder.Marker parse(@NotNull PsiBuilder builder, final int mode) { + return parseAssignment(builder, mode); + } + + //for compatibility + @SuppressWarnings("unused") + public @Nullable PsiBuilder.Marker parseCaseLabel(@NotNull PsiBuilder builder) { + return myParser.getStatementParser().parseCaseLabel(builder).first; + } + + @Nullable PsiBuilder.Marker parseAssignment(final PsiBuilder builder) { + return parseAssignment(builder, 0); + } + + @Nullable PsiBuilder.Marker parseAssignment(final PsiBuilder builder, final int mode) { + final PsiBuilder.Marker left = parseConditional(builder, mode); + if (left == null) return null; + + final IElementType tokenType = getGtTokenType(builder); + if (tokenType != null && ASSIGNMENT_OPS.contains(tokenType)) { + final PsiBuilder.Marker assignment = left.precede(); + advanceGtToken(builder, tokenType); + + final PsiBuilder.Marker right = parse(builder); + if (right == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + + assignment.done(myJavaElementTypeContainer.ASSIGNMENT_EXPRESSION); + return assignment; + } + + return left; + } + + @Nullable + PsiBuilder.Marker parseConditionalAnd(final PsiBuilder builder, final int mode) { + return parseExpression(builder, ExprType.CONDITIONAL_AND, mode); + } + + public @Nullable PsiBuilder.Marker parseConditional(final PsiBuilder builder, final int mode) { + final PsiBuilder.Marker condition = parseExpression(builder, ExprType.CONDITIONAL_OR, mode); + if (condition == null) return null; + + if (builder.getTokenType() != QUEST) return condition; + final PsiBuilder.Marker ternary = condition.precede(); + builder.advanceLexer(); + + final PsiBuilder.Marker truePart = parse(builder); + if (truePart == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + ternary.done(myJavaElementTypeContainer.CONDITIONAL_EXPRESSION); + return ternary; + } + + if (builder.getTokenType() != COLON) { + error(builder, JavaPsiBundle.message("expected.colon")); + ternary.done(myJavaElementTypeContainer.CONDITIONAL_EXPRESSION); + return ternary; + } + builder.advanceLexer(); + + final PsiBuilder.Marker falsePart = parseConditional(builder, mode); + if (falsePart == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + ternary.done(myJavaElementTypeContainer.CONDITIONAL_EXPRESSION); + return ternary; + } + + ternary.done(myJavaElementTypeContainer.CONDITIONAL_EXPRESSION); + return ternary; + } + + private @Nullable PsiBuilder.Marker parseExpression(final PsiBuilder builder, final ExprType type, final int mode) { + switch (type) { + case CONDITIONAL_OR: + return parseBinary(builder, ExprType.CONDITIONAL_AND, CONDITIONAL_OR_OPS, mode); + + case CONDITIONAL_AND: + return parseBinary(builder, ExprType.OR, CONDITIONAL_AND_OPS, mode); + + case OR: + return parseBinary(builder, ExprType.XOR, OR_OPS, mode); + + case XOR: + return parseBinary(builder, ExprType.AND, XOR_OPS, mode); + + case AND: + return parseBinary(builder, ExprType.EQUALITY, AND_OPS, mode); + + case EQUALITY: + return parseBinary(builder, ExprType.RELATIONAL, EQUALITY_OPS, mode); + + case RELATIONAL: + return parseRelational(builder, mode); + + case SHIFT: + return parseBinary(builder, ExprType.ADDITIVE, SHIFT_OPS, mode); + + case ADDITIVE: + return parseBinary(builder, ExprType.MULTIPLICATIVE, ADDITIVE_OPS, mode); + + case MULTIPLICATIVE: + return parseBinary(builder, ExprType.UNARY, MULTIPLICATIVE_OPS, mode); + + case UNARY: + return parseUnary(builder, mode); + + case TYPE: + return myParser.getReferenceParser().parseType(builder, ReferenceParser.EAT_LAST_DOT | ReferenceParser.WILDCARD); + + default: + assert false : "Unexpected type: " + type; + return null; + } + } + + private @Nullable PsiBuilder.Marker parseBinary(final PsiBuilder builder, final ExprType type, final TokenSet ops, final int mode) { + PsiBuilder.Marker result = parseExpression(builder, type, mode); + if (result == null) return null; + int operandCount = 1; + + IElementType tokenType = getGtTokenType(builder); + IElementType currentExprTokenType = tokenType; + while (true) { + if (tokenType == null || !ops.contains(tokenType)) break; + + advanceGtToken(builder, tokenType); + + final PsiBuilder.Marker right = parseExpression(builder, type, mode); + if (right == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + operandCount++; + tokenType = getGtTokenType(builder); + if (tokenType == null || !ops.contains(tokenType) || tokenType != currentExprTokenType) { + // save + result = result.precede(); + result.done(operandCount > 2 ? myJavaElementTypeContainer.POLYADIC_EXPRESSION : myJavaElementTypeContainer.BINARY_EXPRESSION); + if (right == null) break; + currentExprTokenType = tokenType; + operandCount = 1; + } + } + + return result; + } + + private @Nullable PsiBuilder.Marker parseRelational(final PsiBuilder builder, final int mode) { + PsiBuilder.Marker left = parseExpression(builder, ExprType.SHIFT, mode); + if (left == null) return null; + + IElementType tokenType; + while ((tokenType = getGtTokenType(builder)) != null) { + final IElementType toCreate; + final boolean patternExpected; // Otherwise ExprType.SHIFT is expected + if (RELATIONAL_OPS.contains(tokenType)) { + toCreate = myJavaElementTypeContainer.BINARY_EXPRESSION; + patternExpected = false; + } + else if (tokenType == INSTANCEOF_KEYWORD) { + toCreate = myJavaElementTypeContainer.INSTANCE_OF_EXPRESSION; + patternExpected = true; + } + else { + break; + } + + final PsiBuilder.Marker expression = left.precede(); + advanceGtToken(builder, tokenType); + if (patternExpected) { + if (!myParser.getPatternParser().isPattern(builder)) { + PsiBuilder.Marker type = parseExpression(builder, ExprType.TYPE, mode); + if (type == null) { + error(builder, JavaPsiBundle.message("expected.type")); + } + expression.done(toCreate); + return expression; + } + myParser.getPatternParser().parsePrimaryPattern(builder, false); + } + else { + final PsiBuilder.Marker right = parseExpression(builder, ExprType.SHIFT, mode); + if (right == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + expression.done(toCreate); + return expression; + } + } + + expression.done(toCreate); + left = expression; + } + + return left; + } + + private @Nullable PsiBuilder.Marker parseUnary(final PsiBuilder builder, final int mode) { + final IElementType tokenType = builder.getTokenType(); + + if (PREFIX_OPS.contains(tokenType)) { + final PsiBuilder.Marker unary = builder.mark(); + builder.advanceLexer(); + + final PsiBuilder.Marker operand = parseUnary(builder, mode); + if (operand == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + + unary.done(myJavaElementTypeContainer.PREFIX_EXPRESSION); + return unary; + } + else if (tokenType == LPARENTH) { + final PsiBuilder.Marker typeCast = builder.mark(); + builder.advanceLexer(); + + ReferenceParser.TypeInfo typeInfo = myParser.getReferenceParser().parseTypeInfo( + builder, ReferenceParser.EAT_LAST_DOT | + ReferenceParser.WILDCARD | + ReferenceParser.CONJUNCTIONS | + ReferenceParser.INCOMPLETE_ANNO); + if (typeInfo == null || !expect(builder, RPARENTH)) { + typeCast.rollbackTo(); + return parsePostfix(builder, mode); + } + + if (PREF_ARITHMETIC_OPS.contains(builder.getTokenType()) && !typeInfo.isPrimitive) { + typeCast.rollbackTo(); + return parsePostfix(builder, mode); + } + + final PsiBuilder.Marker expr = parseUnary(builder, mode); + if (expr == null) { + if (!typeInfo.isParameterized) { // cannot parse correct parenthesized expression after correct parameterized type + typeCast.rollbackTo(); + return parsePostfix(builder, mode); + } + else { + error(builder, JavaPsiBundle.message("expected.expression")); + } + } + + typeCast.done(myJavaElementTypeContainer.TYPE_CAST_EXPRESSION); + return typeCast; + } + else if (tokenType == SWITCH_KEYWORD) { + return myParser.getStatementParser().parseExprInParenthWithBlock(builder, myJavaElementTypeContainer.SWITCH_EXPRESSION, true); + } + else { + return parsePostfix(builder, mode); + } + } + + private @Nullable PsiBuilder.Marker parsePostfix(final PsiBuilder builder, final int mode) { + PsiBuilder.Marker operand = parsePrimary(builder, null, -1, mode); + if (operand == null) return null; + + while (POSTFIX_OPS.contains(builder.getTokenType())) { + final PsiBuilder.Marker postfix = operand.precede(); + builder.advanceLexer(); + postfix.done(myJavaElementTypeContainer.POSTFIX_EXPRESSION); + operand = postfix; + } + + return operand; + } + + private @Nullable PsiBuilder.Marker parsePrimary(PsiBuilder builder, @Nullable BreakPoint breakPoint, int breakOffset, final int mode) { + PsiBuilder.Marker startMarker = builder.mark(); + + PsiBuilder.Marker expr = parsePrimaryExpressionStart(builder, mode); + if (expr == null) { + startMarker.drop(); + return null; + } + + while (true) { + final IElementType tokenType = builder.getTokenType(); + if (tokenType == DOT) { + final PsiBuilder.Marker dotPos = builder.mark(); + final int dotOffset = builder.getCurrentOffset(); + builder.advanceLexer(); + + IElementType dotTokenType = builder.getTokenType(); + if (dotTokenType == AT) { + myParser.getDeclarationParser().parseAnnotations(builder); + dotTokenType = builder.getTokenType(); + } + + if (dotTokenType == CLASS_KEYWORD && exprType(expr) == myJavaElementTypeContainer.REFERENCE_EXPRESSION) { + if (breakPoint == BreakPoint.P1 && builder.getCurrentOffset() == breakOffset) { + error(builder, JavaPsiBundle.message("expected.identifier")); + drop(startMarker, dotPos); + return expr; + } + + final PsiBuilder.Marker copy = startMarker.precede(); + final int offset = builder.getCurrentOffset(); + startMarker.rollbackTo(); + + final PsiBuilder.Marker classObjAccess = parseClassAccessOrMethodReference(builder); + if (classObjAccess == null || builder.getCurrentOffset() < offset) { + copy.rollbackTo(); + return parsePrimary(builder, BreakPoint.P1, offset, mode); + } + + startMarker = copy; + expr = classObjAccess; + } + else if (dotTokenType == NEW_KEYWORD) { + dotPos.drop(); + expr = parseNew(builder, expr); + } + else if (dotTokenType == SUPER_KEYWORD && builder.lookAhead(1) == LPARENTH) { + dotPos.drop(); + PsiBuilder.Marker refExpr = expr.precede(); + builder.mark().done(myJavaElementTypeContainer.REFERENCE_PARAMETER_LIST); + builder.advanceLexer(); + refExpr.done(myJavaElementTypeContainer.REFERENCE_EXPRESSION); + expr = refExpr; + } + else if (dotTokenType == STRING_TEMPLATE_BEGIN || dotTokenType == TEXT_BLOCK_TEMPLATE_BEGIN) { + dotPos.drop(); + expr = parseStringTemplate(builder, expr, dotTokenType == TEXT_BLOCK_TEMPLATE_BEGIN); + } + else if (dotTokenType == STRING_LITERAL || dotTokenType == TEXT_BLOCK_LITERAL) { + dotPos.drop(); + final PsiBuilder.Marker templateExpression = expr.precede(); + final PsiBuilder.Marker literal = builder.mark(); + builder.advanceLexer(); + literal.done(myJavaElementTypeContainer.LITERAL_EXPRESSION); + templateExpression.done(myJavaElementTypeContainer.TEMPLATE_EXPRESSION); + expr = templateExpression; + } + else if (THIS_OR_SUPER.contains(dotTokenType) && exprType(expr) == myJavaElementTypeContainer.REFERENCE_EXPRESSION) { + if (breakPoint == BreakPoint.P2 && builder.getCurrentOffset() == breakOffset) { + dotPos.rollbackTo(); + startMarker.drop(); + return expr; + } + + PsiBuilder.Marker copy = startMarker.precede(); + int offset = builder.getCurrentOffset(); + startMarker.rollbackTo(); + + PsiBuilder.Marker ref = myParser.getReferenceParser().parseJavaCodeReference(builder, false, true, false, false); + if (ref == null || builder.getTokenType() != DOT || builder.getCurrentOffset() != dotOffset) { + copy.rollbackTo(); + return parsePrimary(builder, BreakPoint.P2, offset, mode); + } + builder.advanceLexer(); + + if (builder.getTokenType() != dotTokenType) { + copy.rollbackTo(); + return parsePrimary(builder, BreakPoint.P2, offset, mode); + } + builder.advanceLexer(); + + startMarker = copy; + expr = ref.precede(); + expr.done(dotTokenType == THIS_KEYWORD + ? myJavaElementTypeContainer.THIS_EXPRESSION + : myJavaElementTypeContainer.SUPER_EXPRESSION); + } + else { + PsiBuilder.Marker refExpr = expr.precede(); + + myParser.getReferenceParser().parseReferenceParameterList(builder, false, false); + + if (!expect(builder, ID_OR_SUPER)) { + dotPos.rollbackTo(); + builder.advanceLexer(); + myParser.getReferenceParser().parseReferenceParameterList(builder, false, false); + error(builder, JavaPsiBundle.message("expected.identifier")); + refExpr.done(myJavaElementTypeContainer.REFERENCE_EXPRESSION); + startMarker.drop(); + return refExpr; + } + + dotPos.drop(); + refExpr.done(myJavaElementTypeContainer.REFERENCE_EXPRESSION); + expr = refExpr; + } + } + else if (tokenType == LPARENTH) { + if (exprType(expr) != myJavaElementTypeContainer.REFERENCE_EXPRESSION) { + startMarker.drop(); + return expr; + } + + PsiBuilder.Marker callExpr = expr.precede(); + parseArgumentList(builder); + callExpr.done(myJavaElementTypeContainer.METHOD_CALL_EXPRESSION); + expr = callExpr; + } + else if (tokenType == LBRACKET) { + if (breakPoint == BreakPoint.P4) { + startMarker.drop(); + return expr; + } + + builder.advanceLexer(); + + if (builder.getTokenType() == RBRACKET && exprType(expr) == myJavaElementTypeContainer.REFERENCE_EXPRESSION) { + final int pos = builder.getCurrentOffset(); + final PsiBuilder.Marker copy = startMarker.precede(); + startMarker.rollbackTo(); + + final PsiBuilder.Marker classObjAccess = parseClassAccessOrMethodReference(builder); + if (classObjAccess == null || builder.getCurrentOffset() <= pos) { + copy.rollbackTo(); + return parsePrimary(builder, BreakPoint.P4, -1, mode); + } + + startMarker = copy; + expr = classObjAccess; + } + else { + final PsiBuilder.Marker arrayAccess = expr.precede(); + + final PsiBuilder.Marker index = parse(builder); + if (index == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + arrayAccess.done(myJavaElementTypeContainer.ARRAY_ACCESS_EXPRESSION); + startMarker.drop(); + return arrayAccess; + } + + if (builder.getTokenType() != RBRACKET) { + error(builder, JavaPsiBundle.message("expected.rbracket")); + arrayAccess.done(myJavaElementTypeContainer.ARRAY_ACCESS_EXPRESSION); + startMarker.drop(); + return arrayAccess; + } + builder.advanceLexer(); + + arrayAccess.done(myJavaElementTypeContainer.ARRAY_ACCESS_EXPRESSION); + expr = arrayAccess; + } + } + else if (tokenType == DOUBLE_COLON) { + return parseMethodReference(builder, startMarker); + } + else { + startMarker.drop(); + return expr; + } + } + } + + private @Nullable PsiBuilder.Marker parsePrimaryExpressionStart(final PsiBuilder builder, final int mode) { + IElementType tokenType = builder.getTokenType(); + + if (tokenType == TEXT_BLOCK_TEMPLATE_BEGIN || tokenType == STRING_TEMPLATE_BEGIN) { + return parseStringTemplate(builder, null, tokenType == TEXT_BLOCK_TEMPLATE_BEGIN); + } + + if (ALL_LITERALS.contains(tokenType)) { + final PsiBuilder.Marker literal = builder.mark(); + builder.advanceLexer(); + literal.done(myJavaElementTypeContainer.LITERAL_EXPRESSION); + return literal; + } + + if (tokenType == LBRACE) { + return parseArrayInitializer(builder); + } + + if (tokenType == NEW_KEYWORD) { + return parseNew(builder, null); + } + + if (tokenType == LPARENTH) { + if (!BitUtil.isSet(mode, ExpressionParser.FORBID_LAMBDA_MASK)) { + final PsiBuilder.Marker lambda = parseLambdaAfterParenth(builder); + if (lambda != null) { + return lambda; + } + } + + final PsiBuilder.Marker parenth = builder.mark(); + builder.advanceLexer(); + + final PsiBuilder.Marker inner = parse(builder); + if (inner == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + + if (!expect(builder, RPARENTH)) { + if (inner != null) { + error(builder, JavaPsiBundle.message("expected.rparen")); + } + } + + parenth.done(myJavaElementTypeContainer.PARENTH_EXPRESSION); + return parenth; + } + + if (TYPE_START.contains(tokenType)) { + final PsiBuilder.Marker mark = builder.mark(); + + final ReferenceParser.TypeInfo typeInfo = myParser.getReferenceParser().parseTypeInfo(builder, 0); + if (typeInfo != null) { + boolean optionalClassKeyword = typeInfo.isPrimitive || typeInfo.isArray; + if (optionalClassKeyword || !typeInfo.hasErrors && typeInfo.isParameterized) { + final PsiBuilder.Marker result = parseClassAccessOrMethodReference(builder, mark, optionalClassKeyword); + if (result != null) { + return result; + } + } + } + + mark.rollbackTo(); + } + + PsiBuilder.Marker annotation = null; + if (tokenType == AT) { + annotation = myParser.getDeclarationParser().parseAnnotations(builder); + tokenType = builder.getTokenType(); + } + + if (tokenType == VAR_KEYWORD) { + builder.remapCurrentToken(tokenType = IDENTIFIER); + } + if (tokenType == IDENTIFIER) { + if (!BitUtil.isSet(mode, ExpressionParser.FORBID_LAMBDA_MASK) && builder.lookAhead(1) == ARROW) { + return parseLambdaExpression(builder, false); + } + + final PsiBuilder.Marker refExpr; + if (annotation != null) { + final PsiBuilder.Marker refParam = annotation.precede(); + refParam.doneBefore(myJavaElementTypeContainer.REFERENCE_PARAMETER_LIST, annotation); + refExpr = refParam.precede(); + } + else { + refExpr = builder.mark(); + builder.mark().done(myJavaElementTypeContainer.REFERENCE_PARAMETER_LIST); + } + + builder.advanceLexer(); + refExpr.done(myJavaElementTypeContainer.REFERENCE_EXPRESSION); + return refExpr; + } + + if (annotation != null) { + annotation.rollbackTo(); + tokenType = builder.getTokenType(); + } + + PsiBuilder.Marker expr = null; + if (tokenType == LT) { + expr = builder.mark(); + + if (!myParser.getReferenceParser().parseReferenceParameterList(builder, false, false)) { + expr.rollbackTo(); + return null; + } + + tokenType = builder.getTokenType(); + if (!THIS_OR_SUPER.contains(tokenType)) { + expr.rollbackTo(); + return null; + } + } + + if (THIS_OR_SUPER.contains(tokenType)) { + if (expr == null) { + expr = builder.mark(); + builder.mark().done(myJavaElementTypeContainer.REFERENCE_PARAMETER_LIST); + } + builder.advanceLexer(); + expr.done(builder.getTokenType() == LPARENTH + ? myJavaElementTypeContainer.REFERENCE_EXPRESSION + : tokenType == THIS_KEYWORD + ? myJavaElementTypeContainer.THIS_EXPRESSION + : myJavaElementTypeContainer.SUPER_EXPRESSION); + return expr; + } + + return null; + } + + private @NotNull PsiBuilder.Marker parseArrayInitializer(PsiBuilder builder) { + return parseArrayInitializer(builder, myJavaElementTypeContainer.ARRAY_INITIALIZER_EXPRESSION, this::parse, "expected.expression"); + } + + public @NotNull PsiBuilder.Marker parseArrayInitializer(@NotNull PsiBuilder builder, + @NotNull IElementType type, + @NotNull Function elementParser, + @NotNull @PropertyKey(resourceBundle = JavaPsiBundle.BUNDLE) String missingElementKey) { + PsiBuilder.Marker arrayInit = builder.mark(); + builder.advanceLexer(); + + boolean first = true; + while (true) { + if (builder.getTokenType() == RBRACE) { + builder.advanceLexer(); + break; + } + + if (builder.getTokenType() == null) { + error(builder, JavaPsiBundle.message("expected.rbrace")); + break; + } + + if (elementParser.apply(builder) == null) { + if (builder.getTokenType() == COMMA) { + if (first && builder.lookAhead(1) == RBRACE) { + advance(builder, 2); + break; + } + builder.error(JavaPsiBundle.message(missingElementKey)); + } + else if (builder.getTokenType() != RBRACE) { + error(builder, JavaPsiBundle.message("expected.rbrace")); + break; + } + } + + first = false; + + IElementType tokenType = builder.getTokenType(); + if (!expect(builder, COMMA) && tokenType != RBRACE) { + error(builder, JavaPsiBundle.message("expected.comma")); + } + } + + arrayInit.done(type); + return arrayInit; + } + + private PsiBuilder.Marker parseStringTemplate(PsiBuilder builder, PsiBuilder.Marker start, boolean textBlock) { + final PsiBuilder.Marker templateExpression = start == null ? builder.mark() : start.precede(); + final PsiBuilder.Marker template = builder.mark(); + IElementType tokenType; + do { + builder.advanceLexer(); + tokenType = builder.getTokenType(); + if (textBlock + ? tokenType == TEXT_BLOCK_TEMPLATE_MID || tokenType == TEXT_BLOCK_TEMPLATE_END + : tokenType == STRING_TEMPLATE_MID || tokenType == STRING_TEMPLATE_END) { + emptyExpression(builder); + } + else { + parse(builder); + tokenType = builder.getTokenType(); + } + } + while (textBlock ? tokenType == TEXT_BLOCK_TEMPLATE_MID : tokenType == STRING_TEMPLATE_MID); + if (textBlock ? tokenType != TEXT_BLOCK_TEMPLATE_END : tokenType != STRING_TEMPLATE_END) { + builder.error(JavaPsiBundle.message("expected.template.fragment")); + } + else { + builder.advanceLexer(); + } + template.done(myJavaElementTypeContainer.TEMPLATE); + templateExpression.done(myJavaElementTypeContainer.TEMPLATE_EXPRESSION); + return templateExpression; + } + + private @NotNull PsiBuilder.Marker parseNew(PsiBuilder builder, @Nullable PsiBuilder.Marker start) { + PsiBuilder.Marker newExpr = (start != null ? start.precede() : builder.mark()); + builder.advanceLexer(); + + myParser.getReferenceParser().parseReferenceParameterList(builder, false, true); + + PsiBuilder.Marker refOrType; + PsiBuilder.Marker anno = myParser.getDeclarationParser().parseAnnotations(builder); + IElementType tokenType = builder.getTokenType(); + if (tokenType == IDENTIFIER) { + rollbackTo(anno); + refOrType = myParser.getReferenceParser().parseJavaCodeReference(builder, true, true, true, true); + if (refOrType == null) { + error(builder, JavaPsiBundle.message("expected.identifier")); + newExpr.done(myJavaElementTypeContainer.NEW_EXPRESSION); + return newExpr; + } + } + else if (PRIMITIVE_TYPE_BIT_SET.contains(tokenType)) { + refOrType = null; + builder.advanceLexer(); + } + else { + error(builder, JavaPsiBundle.message("expected.identifier")); + newExpr.done(myJavaElementTypeContainer.NEW_EXPRESSION); + return newExpr; + } + + if (refOrType != null && builder.getTokenType() == LPARENTH) { + parseArgumentList(builder); + if (builder.getTokenType() == LBRACE) { + final PsiBuilder.Marker classElement = refOrType.precede(); + myParser.getDeclarationParser().parseClassBodyWithBraces(builder, false, false); + classElement.done(myJavaElementTypeContainer.ANONYMOUS_CLASS); + } + newExpr.done(myJavaElementTypeContainer.NEW_EXPRESSION); + return newExpr; + } + + anno = myParser.getDeclarationParser().parseAnnotations(builder); + + if (builder.getTokenType() != LBRACKET) { + rollbackTo(anno); + error(builder, JavaPsiBundle.message(refOrType == null ? "expected.lbracket" : "expected.lparen.or.lbracket")); + newExpr.done(myJavaElementTypeContainer.NEW_EXPRESSION); + return newExpr; + } + + int bracketCount = 0; + int dimCount = 0; + while (true) { + anno = myParser.getDeclarationParser().parseAnnotations(builder); + + if (builder.getTokenType() != LBRACKET) { + rollbackTo(anno); + break; + } + builder.advanceLexer(); + + if (bracketCount == dimCount) { + final PsiBuilder.Marker dimExpr = parse(builder); + if (dimExpr != null) { + dimCount++; + } + } + bracketCount++; + + if (!expectOrError(builder, RBRACKET, "expected.rbracket")) { + newExpr.done(myJavaElementTypeContainer.NEW_EXPRESSION); + return newExpr; + } + } + + if (dimCount == 0) { + if (builder.getTokenType() == LBRACE) { + parseArrayInitializer(builder); + } + else { + error(builder, JavaPsiBundle.message("expected.array.initializer")); + } + } + + newExpr.done(myJavaElementTypeContainer.NEW_EXPRESSION); + return newExpr; + } + + private @Nullable PsiBuilder.Marker parseClassAccessOrMethodReference(PsiBuilder builder) { + PsiBuilder.Marker expr = builder.mark(); + + boolean primitive = PRIMITIVE_TYPE_BIT_SET.contains(builder.getTokenType()); + if (myParser.getReferenceParser().parseType(builder, 0) == null) { + expr.drop(); + return null; + } + + PsiBuilder.Marker result = parseClassAccessOrMethodReference(builder, expr, primitive); + if (result == null) expr.rollbackTo(); + return result; + } + + private @Nullable PsiBuilder.Marker parseClassAccessOrMethodReference(PsiBuilder builder, + PsiBuilder.Marker expr, + boolean optionalClassKeyword) { + IElementType tokenType = builder.getTokenType(); + if (tokenType == DOT) { + return parseClassObjectAccess(builder, expr, optionalClassKeyword); + } + else if (tokenType == DOUBLE_COLON) { + return parseMethodReference(builder, expr); + } + + return null; + } + + private @Nullable PsiBuilder.Marker parseClassObjectAccess(PsiBuilder builder, PsiBuilder.Marker expr, boolean optionalClassKeyword) { + final PsiBuilder.Marker mark = builder.mark(); + builder.advanceLexer(); + + if (builder.getTokenType() == CLASS_KEYWORD) { + mark.drop(); + builder.advanceLexer(); + } + else { + if (!optionalClassKeyword) return null; + mark.rollbackTo(); + builder.error(JavaPsiBundle.message("class.literal.expected")); + } + + expr.done(myJavaElementTypeContainer.CLASS_OBJECT_ACCESS_EXPRESSION); + return expr; + } + + private @NotNull PsiBuilder.Marker parseMethodReference(final PsiBuilder builder, final PsiBuilder.Marker start) { + builder.advanceLexer(); + + myParser.getReferenceParser().parseReferenceParameterList(builder, false, false); + + if (!expect(builder, IDENTIFIER) && !expect(builder, NEW_KEYWORD)) { + error(builder, JavaPsiBundle.message("expected.identifier")); + } + + start.done(myJavaElementTypeContainer.METHOD_REF_EXPRESSION); + return start; + } + + private @Nullable PsiBuilder.Marker parseLambdaAfterParenth(final PsiBuilder builder) { + final boolean isLambda; + final boolean isTyped; + + final IElementType nextToken1 = builder.lookAhead(1); + final IElementType nextToken2 = builder.lookAhead(2); + if (nextToken1 == RPARENTH && nextToken2 == ARROW) { + isLambda = true; + isTyped = false; + } + else if (nextToken1 == AT || + MODIFIER_BIT_SET.contains(nextToken1) || + PRIMITIVE_TYPE_BIT_SET.contains(nextToken1)) { + isLambda = true; + isTyped = true; + } + else if (nextToken1 == IDENTIFIER) { + if (nextToken2 == COMMA || nextToken2 == RPARENTH && builder.lookAhead(3) == ARROW) { + isLambda = true; + isTyped = false; + } + else if (nextToken2 == ARROW) { + isLambda = false; + isTyped = false; + } + else { + boolean lambda = false; + + PsiBuilder.Marker marker = builder.mark(); + builder.advanceLexer(); + ReferenceParser.TypeInfo typeInfo = myParser.getReferenceParser().parseTypeInfo( + builder, ReferenceParser.ELLIPSIS | ReferenceParser.WILDCARD); + if (typeInfo != null) { + IElementType t = builder.getTokenType(); + lambda = t == IDENTIFIER || + t == THIS_KEYWORD || + t == RPARENTH && builder.lookAhead(1) == ARROW; + } + marker.rollbackTo(); + + isLambda = lambda; + isTyped = true; + } + } + else { + isLambda = false; + isTyped = false; + } + + return isLambda ? parseLambdaExpression(builder, isTyped) : null; + } + + private @Nullable PsiBuilder.Marker parseLambdaExpression(final PsiBuilder builder, final boolean typed) { + final PsiBuilder.Marker start = builder.mark(); + + myParser.getDeclarationParser().parseLambdaParameterList(builder, typed); + + if (!expect(builder, ARROW)) { + start.rollbackTo(); + return null; + } + + final PsiBuilder.Marker body; + if (builder.getTokenType() == LBRACE) { + body = myParser.getStatementParser().parseCodeBlock(builder); + } + else { + body = parse(builder); + } + + if (body == null) { + builder.error(JavaPsiBundle.message("expected.lbrace")); + } + + start.done(myJavaElementTypeContainer.LAMBDA_EXPRESSION); + return start; + } + + public @NotNull PsiBuilder.Marker parseArgumentList(final PsiBuilder builder) { + final PsiBuilder.Marker list = builder.mark(); + builder.advanceLexer(); + + boolean first = true; + while (true) { + final IElementType tokenType = builder.getTokenType(); + if (first && (ARGS_LIST_END.contains(tokenType) || builder.eof())) break; + if (!first && !ARGS_LIST_CONTINUE.contains(tokenType)) break; + + boolean hasError = false; + if (!first) { + if (builder.getTokenType() == COMMA) { + builder.advanceLexer(); + } + else { + hasError = true; + error(builder, JavaPsiBundle.message("expected.comma.or.rparen")); + emptyExpression(builder); + } + } + first = false; + + final PsiBuilder.Marker arg = parse(builder); + if (arg == null) { + if (!hasError) { + error(builder, JavaPsiBundle.message("expected.expression")); + emptyExpression(builder); + } + if (!ARGS_LIST_CONTINUE.contains(builder.getTokenType())) break; + if (builder.getTokenType() != COMMA && !builder.eof()) { + builder.advanceLexer(); + } + } + } + + boolean closed = true; + if (!expect(builder, RPARENTH)) { + if (first) { + error(builder, JavaPsiBundle.message("expected.rparen")); + } + else { + error(builder, JavaPsiBundle.message("expected.comma.or.rparen")); + } + closed = false; + } + + list.done(myJavaElementTypeContainer.EXPRESSION_LIST); + if (!closed) { + list.setCustomEdgeTokenBinders(null, WhitespacesBinders.GREEDY_RIGHT_BINDER); + } + return list; + } + + private void emptyExpression(final PsiBuilder builder) { + emptyElement(builder, myJavaElementTypeContainer.EMPTY_EXPRESSION); + } + + private static @Nullable IElementType getGtTokenType(final PsiBuilder builder) { + IElementType tokenType = builder.getTokenType(); + if (tokenType != GT) return tokenType; + + if (builder.rawLookup(1) == GT) { + if (builder.rawLookup(2) == GT) { + if (builder.rawLookup(3) == EQ) { + tokenType = GTGTGTEQ; + } + else { + tokenType = GTGTGT; + } + } + else if (builder.rawLookup(2) == EQ) { + tokenType = GTGTEQ; + } + else { + tokenType = GTGT; + } + } + else if (builder.rawLookup(1) == EQ) { + tokenType = GE; + } + + return tokenType; + } + + private static void advanceGtToken(final PsiBuilder builder, final IElementType type) { + final PsiBuilder.Marker gtToken = builder.mark(); + + if (type == GTGTGTEQ) { + advance(builder, 4); + } + else if (type == GTGTGT || type == GTGTEQ) { + advance(builder, 3); + } + else if (type == GTGT || type == GE) { + advance(builder, 2); + } + else { + gtToken.drop(); + builder.advanceLexer(); + return; + } + + gtToken.collapse(type); + } + + private enum ExprType { + CONDITIONAL_OR, CONDITIONAL_AND, OR, XOR, AND, EQUALITY, RELATIONAL, SHIFT, ADDITIVE, MULTIPLICATIVE, UNARY, TYPE + } + + private enum BreakPoint {P1, P2, P4} } diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/PatternParser.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/PatternParser.java index 1b0c9cb5a7a2..a7792a2eabf3 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/PatternParser.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/parser/PatternParser.java @@ -1,21 +1,187 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.lang.java.parser; +import com.intellij.core.JavaPsiBundle; +import com.intellij.java.syntax.parser.JavaKeywords; import com.intellij.lang.PsiBuilder; +import com.intellij.psi.JavaTokenType; +import com.intellij.psi.impl.source.AbstractBasicJavaElementTypeFactory; +import com.intellij.psi.impl.source.OldParserWhiteSpaceAndCommentSetHolder; +import com.intellij.psi.tree.TokenSet; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static com.intellij.lang.PsiBuilderUtil.expect; +import static com.intellij.lang.java.parser.JavaParserUtil.*; /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated -public class PatternParser extends BasicPatternParser { +public class PatternParser { + private static final TokenSet PATTERN_MODIFIERS = TokenSet.create(JavaTokenType.FINAL_KEYWORD); + private final JavaParser myParser; + private final AbstractBasicJavaElementTypeFactory.JavaElementTypeContainer myJavaElementTypeContainer; + private final OldParserWhiteSpaceAndCommentSetHolder myWhiteSpaceAndCommentSetHolder = OldParserWhiteSpaceAndCommentSetHolder.INSTANCE; + public PatternParser(@NotNull JavaParser javaParser) { - super(javaParser); + this.myParser = javaParser; + this.myJavaElementTypeContainer = (javaParser).getJavaElementTypeFactory().getContainer(); } - @Override + @Contract(pure = true) public boolean isPattern(PsiBuilder builder) { - return super.isPattern(builder); + PsiBuilder.Marker patternStart = preParsePattern(builder); + if (patternStart == null) { + return false; + } + patternStart.rollbackTo(); + return true; + } + + private boolean parseUnnamedPattern(final PsiBuilder builder) { + PsiBuilder.Marker patternStart = builder.mark(); + if (builder.getTokenType() == JavaTokenType.IDENTIFIER && + "_".equals(builder.getTokenText())) { + emptyElement(builder, myJavaElementTypeContainer.TYPE); + builder.advanceLexer(); + done(patternStart, myJavaElementTypeContainer.UNNAMED_PATTERN, builder, myWhiteSpaceAndCommentSetHolder); + return true; + } + patternStart.rollbackTo(); + return false; + } + + @Nullable("when not pattern") + PsiBuilder.Marker preParsePattern(final PsiBuilder builder) { + PsiBuilder.Marker patternStart = builder.mark(); + Boolean hasNoModifier = myParser.getDeclarationParser().parseModifierList(builder, PATTERN_MODIFIERS).second; + PsiBuilder.Marker type = + myParser.getReferenceParser().parseType(builder, ReferenceParser.EAT_LAST_DOT | ReferenceParser.WILDCARD); + boolean isPattern = type != null && (builder.getTokenType() == JavaTokenType.IDENTIFIER || + (builder.getTokenType() == JavaTokenType.LPARENTH && hasNoModifier)); + if (!isPattern) { + patternStart.rollbackTo(); + return null; + } + return patternStart; + } + + /** + * Must be called only if isPattern returned true + */ + public PsiBuilder.@NotNull Marker parsePattern(final PsiBuilder builder) { + return parsePattern(builder, false); + } + + private PsiBuilder.@NotNull Marker parsePattern(final PsiBuilder builder, boolean expectVar) { + return parsePrimaryPattern(builder, expectVar); + } + + PsiBuilder.@NotNull Marker parsePrimaryPattern(final PsiBuilder builder, boolean expectVar) { + return parseTypeOrRecordPattern(builder, expectVar); + } + + private void parseRecordStructurePattern(final PsiBuilder builder) { + PsiBuilder.Marker recordStructure = builder.mark(); + boolean hasLparen = expect(builder, JavaTokenType.LPARENTH); + assert hasLparen; + + boolean isFirst = true; + while (builder.getTokenType() != JavaTokenType.RPARENTH) { + if (!isFirst) { + expectOrError(builder, JavaTokenType.COMMA, "expected.comma"); + } + + if (builder.getTokenType() == null) { + break; + } + + if (isPattern(builder)) { + parsePattern(builder, true); + isFirst = false; + } + else if (parseUnnamedPattern(builder)) { + isFirst = false; + } + else { + int flags = ReferenceParser.EAT_LAST_DOT | ReferenceParser.WILDCARD | ReferenceParser.VAR_TYPE; + myParser.getReferenceParser().parseType(builder, flags); + error(builder, JavaPsiBundle.message("expected.pattern")); + if (builder.getTokenType() == JavaTokenType.RPARENTH) { + break; + } + builder.advanceLexer(); + } + } + if (!expect(builder, JavaTokenType.RPARENTH)) { + builder.error(JavaPsiBundle.message("expected.rparen")); + } + recordStructure.done(myJavaElementTypeContainer.DECONSTRUCTION_LIST); + } + + private PsiBuilder.@NotNull Marker parseTypeOrRecordPattern(final PsiBuilder builder, boolean expectVar) { + PsiBuilder.Marker pattern = builder.mark(); + PsiBuilder.Marker patternVariable = builder.mark(); + Boolean hasNoModifiers = myParser.getDeclarationParser().parseModifierList(builder, PATTERN_MODIFIERS).second; + + int flags = ReferenceParser.EAT_LAST_DOT | ReferenceParser.WILDCARD; + if (expectVar) { + flags |= ReferenceParser.VAR_TYPE; + } + PsiBuilder.Marker type = myParser.getReferenceParser().parseType(builder, flags); + assert type != null; // guarded by isPattern + boolean isRecord = false; + if (builder.getTokenType() == JavaTokenType.LPARENTH && hasNoModifiers) { + parseRecordStructurePattern(builder); + isRecord = true; + } + + final boolean hasIdentifier; + if (builder.getTokenType() == JavaTokenType.IDENTIFIER && + (!JavaKeywords.WHEN.equals(builder.getTokenText()) || isWhenAsIdentifier(isRecord))) { + // pattern variable after the record structure pattern + if (isRecord) { + PsiBuilder.Marker variable = builder.mark(); + builder.advanceLexer(); + variable.done(myJavaElementTypeContainer.DECONSTRUCTION_PATTERN_VARIABLE); + } + else { + builder.advanceLexer(); + } + hasIdentifier = true; + } + else { + hasIdentifier = false; + } + + if (isRecord) { + patternVariable.drop(); + done(pattern, myJavaElementTypeContainer.DECONSTRUCTION_PATTERN, builder, myWhiteSpaceAndCommentSetHolder); + } + else { + if (hasIdentifier) { + done(patternVariable, myJavaElementTypeContainer.PATTERN_VARIABLE, builder, myWhiteSpaceAndCommentSetHolder); + } + else { + patternVariable.drop(); + } + done(pattern, myJavaElementTypeContainer.TYPE_TEST_PATTERN, builder, myWhiteSpaceAndCommentSetHolder); + } + return pattern; + } + + // There may be valid code samples: + // Rec(int i) when when when.foo() -> {} //now it is unsupported, let's skip it + // ^name ^keyword ^guard expr + //case When when -> {} + // ^name + //case When(when) when when ->{} + // ^keyword ^guard expr + private static boolean isWhenAsIdentifier(boolean previousIsRecord) { + if (previousIsRecord) return false; + return true; } } diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/PrattExpressionParser.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/PrattExpressionParser.java index 43f8328639ae..3c1cca21f2c9 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/PrattExpressionParser.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/parser/PrattExpressionParser.java @@ -1,15 +1,1064 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.lang.java.parser; +import com.intellij.core.JavaPsiBundle; +import com.intellij.lang.PsiBuilder; +import com.intellij.lang.WhitespacesBinders; +import com.intellij.psi.impl.source.AbstractBasicJavaElementTypeFactory; +import com.intellij.psi.impl.source.OldParserWhiteSpaceAndCommentSetHolder; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.TokenSet; +import com.intellij.util.BitUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.PropertyKey; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import static com.intellij.lang.PsiBuilderUtil.*; +import static com.intellij.lang.java.parser.JavaParserUtil.*; +import static com.intellij.psi.JavaTokenType.*; +import static com.intellij.psi.impl.source.tree.ElementType.*; /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated -public final class PrattExpressionParser extends BasicPrattExpressionParser { +public final class PrattExpressionParser { + static final int CONDITIONAL_EXPR_PRECEDENCE = 12; + private static final TokenSet THIS_OR_SUPER = TokenSet.create(THIS_KEYWORD, SUPER_KEYWORD); + private static final TokenSet ID_OR_SUPER = TokenSet.create(IDENTIFIER, SUPER_KEYWORD); + private static final TokenSet ARGS_LIST_CONTINUE = TokenSet.create( + IDENTIFIER, BAD_CHARACTER, COMMA, INTEGER_LITERAL, STRING_LITERAL); + private static final TokenSet ARGS_LIST_END = TokenSet.create(RPARENTH, RBRACE, RBRACKET); + private static final TokenSet POSTFIX_OPS = TokenSet.create(PLUSPLUS, MINUSMINUS); + private static final TokenSet PREF_ARITHMETIC_OPS = TokenSet.orSet(POSTFIX_OPS, TokenSet.create(PLUS, MINUS)); + private static final TokenSet PREFIX_OPS = TokenSet.orSet(PREF_ARITHMETIC_OPS, TokenSet.create(TILDE, EXCL)); + private static final int MULTIPLICATION_PRECEDENCE = 2; + private static final int ADDITIVE_PRECEDENCE = 3; + private static final int SHIFT_PRECEDENCE = 4; + private static final int COMPARISON_AND_INSTANCEOF_PRECEDENCE = 5; + private static final int EQUALITY_PRECEDENCE = 6; + private static final int BITWISE_AND_PRECEDENCE = 7; + private static final int BITWISE_XOR_PRECEDENCE = 8; + private static final int BITWISE_OR_PRECEDENCE = 9; + private static final int LOGICAL_AND_PRECEDENCE = 10; + private static final int LOGICAL_OR_PRECEDENCE = 11; + private static final int ASSIGNMENT_PRECEDENCE = 13; + private final Map ourInfixParsers; + private final TokenSet TYPE_START = + TokenSet.orSet(PRIMITIVE_TYPE_BIT_SET, TokenSet.create(IDENTIFIER, AT)); + private final JavaParser myParser; + private final AbstractBasicJavaElementTypeFactory.JavaElementTypeContainer myJavaElementTypeContainer; + private final OldParserWhiteSpaceAndCommentSetHolder myWhiteSpaceAndCommentSetHolder = OldParserWhiteSpaceAndCommentSetHolder.INSTANCE; + public PrattExpressionParser(@NotNull JavaParser parser) { - super(parser); + this.myParser = parser; + this.myJavaElementTypeContainer = (parser).getJavaElementTypeFactory().getContainer(); + + this.ourInfixParsers = new HashMap<>(); + AssignmentParser assignmentParser = new PrattExpressionParser.AssignmentParser(); + PolyExprParser polyExprParser = new PrattExpressionParser.PolyExprParser(); + InstanceofParser instanceofParser = new PrattExpressionParser.InstanceofParser(); + ConditionalExprParser conditionalExprParser = new PrattExpressionParser.ConditionalExprParser(); + + for (IElementType type : Arrays.asList(EQ, ASTERISKEQ, DIVEQ, PERCEQ, + PLUSEQ, MINUSEQ, + LTLTEQ, GTGTEQ, GTGTGTEQ, ANDEQ, + OREQ, XOREQ)) { + this.ourInfixParsers.put(type, + new PrattExpressionParser.ParserData(ASSIGNMENT_PRECEDENCE, assignmentParser)); + } + for (IElementType type : Arrays.asList(PLUS, MINUS)) { + this.ourInfixParsers.put(type, new PrattExpressionParser.ParserData(ADDITIVE_PRECEDENCE, polyExprParser)); + } + for (IElementType type : Arrays.asList(DIV, ASTERISK, PERC)) { + this.ourInfixParsers.put(type, + new PrattExpressionParser.ParserData(MULTIPLICATION_PRECEDENCE, polyExprParser)); + } + for (IElementType type : Arrays.asList(LTLT, GTGT, GTGTGT)) { + this.ourInfixParsers.put(type, new PrattExpressionParser.ParserData(SHIFT_PRECEDENCE, polyExprParser)); + } + for (IElementType type : Arrays.asList(LT, GT, LE, GE)) { + this.ourInfixParsers.put(type, new PrattExpressionParser.ParserData(COMPARISON_AND_INSTANCEOF_PRECEDENCE, + polyExprParser)); + } + this.ourInfixParsers.put(INSTANCEOF_KEYWORD, + new PrattExpressionParser.ParserData(COMPARISON_AND_INSTANCEOF_PRECEDENCE, + instanceofParser)); + for (IElementType type : Arrays.asList(EQEQ, NE)) { + this.ourInfixParsers.put(type, new PrattExpressionParser.ParserData(EQUALITY_PRECEDENCE, polyExprParser)); + } + this.ourInfixParsers.put(OR, + new PrattExpressionParser.ParserData(BITWISE_OR_PRECEDENCE, polyExprParser)); + this.ourInfixParsers.put(AND, + new PrattExpressionParser.ParserData(BITWISE_AND_PRECEDENCE, polyExprParser)); + this.ourInfixParsers.put(XOR, + new PrattExpressionParser.ParserData(BITWISE_XOR_PRECEDENCE, polyExprParser)); + this.ourInfixParsers.put(ANDAND, + new PrattExpressionParser.ParserData(LOGICAL_AND_PRECEDENCE, polyExprParser)); + this.ourInfixParsers.put(OROR, + new PrattExpressionParser.ParserData(LOGICAL_OR_PRECEDENCE, polyExprParser)); + this.ourInfixParsers.put(QUEST, + new PrattExpressionParser.ParserData(CONDITIONAL_EXPR_PRECEDENCE, + conditionalExprParser)); + } + + public @Nullable PsiBuilder.Marker parse(@NotNull PsiBuilder builder) { + return tryParseWithPrecedenceAtMost(builder, ASSIGNMENT_PRECEDENCE, 0); + } + + public @Nullable PsiBuilder.Marker parse(@NotNull PsiBuilder builder, int mode) { + return tryParseWithPrecedenceAtMost(builder, ASSIGNMENT_PRECEDENCE, mode); + } + + /** + * Traditional Pratt parser for infix expressions. + * If marker is null it is guaranteed that nothing is parsed + */ + public @Nullable PsiBuilder.Marker tryParseWithPrecedenceAtMost(@NotNull PsiBuilder builder, int maxPrecedence, int mode) { + PsiBuilder.Marker lhs = parseUnary(builder, mode); + if (lhs == null) return null; + + while (true) { + IElementType type = getBinOpToken(builder); + if (type == null) { + break; + } + ParserData data = ourInfixParsers.get(type); + if (data == null) { + break; + } + int opPrecedence = data.myPrecedence; + if (maxPrecedence < opPrecedence) { + break; + } + PsiBuilder.Marker beforeLhs = lhs.precede(); + data.myParser.parse(this, builder, beforeLhs, type, opPrecedence, mode); + lhs = beforeLhs; + } + return lhs; + } + + private @Nullable PsiBuilder.Marker parseUnary(final PsiBuilder builder, final int mode) { + final IElementType tokenType = builder.getTokenType(); + + if (PREFIX_OPS.contains(tokenType)) { + final PsiBuilder.Marker unary = builder.mark(); + builder.advanceLexer(); + + final PsiBuilder.Marker operand = parseUnary(builder, mode); + if (operand == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + + unary.done(myJavaElementTypeContainer.PREFIX_EXPRESSION); + return unary; + } + else if (tokenType == LPARENTH) { + final PsiBuilder.Marker typeCast = builder.mark(); + builder.advanceLexer(); + + ReferenceParser.TypeInfo typeInfo = myParser.getReferenceParser().parseTypeInfo( + builder, ReferenceParser.EAT_LAST_DOT | + ReferenceParser.WILDCARD | + ReferenceParser.CONJUNCTIONS | + ReferenceParser.INCOMPLETE_ANNO); + if (typeInfo == null || !expect(builder, RPARENTH)) { + typeCast.rollbackTo(); + return parsePostfix(builder, mode); + } + + if (PREF_ARITHMETIC_OPS.contains(builder.getTokenType()) && !typeInfo.isPrimitive) { + typeCast.rollbackTo(); + return parsePostfix(builder, mode); + } + + final PsiBuilder.Marker expr = parseUnary(builder, mode); + if (expr == null) { + if (!typeInfo.isParameterized) { // cannot parse correct parenthesized expression after correct parameterized type + typeCast.rollbackTo(); + return parsePostfix(builder, mode); + } + else { + error(builder, JavaPsiBundle.message("expected.expression")); + } + } + + typeCast.done(myJavaElementTypeContainer.TYPE_CAST_EXPRESSION); + return typeCast; + } + else if (tokenType == SWITCH_KEYWORD) { + return myParser.getStatementParser() + .parseExprInParenthWithBlock(builder, myJavaElementTypeContainer.SWITCH_EXPRESSION, true); + } + else { + return parsePostfix(builder, mode); + } + } + + private @Nullable PsiBuilder.Marker parsePostfix(final PsiBuilder builder, final int mode) { + PsiBuilder.Marker operand = parsePrimary(builder, null, -1, mode); + if (operand == null) return null; + + while (POSTFIX_OPS.contains(builder.getTokenType())) { + final PsiBuilder.Marker postfix = operand.precede(); + builder.advanceLexer(); + postfix.done(myJavaElementTypeContainer.POSTFIX_EXPRESSION); + operand = postfix; + } + + return operand; + } + + private @Nullable PsiBuilder.Marker parsePrimary(PsiBuilder builder, @Nullable BreakPoint breakPoint, int breakOffset, final int mode) { + PsiBuilder.Marker startMarker = builder.mark(); + + PsiBuilder.Marker expr = parsePrimaryExpressionStart(builder, mode); + if (expr == null) { + startMarker.drop(); + return null; + } + + while (true) { + final IElementType tokenType = builder.getTokenType(); + if (tokenType == DOT) { + final PsiBuilder.Marker dotPos = builder.mark(); + final int dotOffset = builder.getCurrentOffset(); + builder.advanceLexer(); + + IElementType dotTokenType = builder.getTokenType(); + if (dotTokenType == AT) { + myParser.getDeclarationParser().parseAnnotations(builder); + dotTokenType = builder.getTokenType(); + } + + if (dotTokenType == CLASS_KEYWORD && exprType(expr) == myJavaElementTypeContainer.REFERENCE_EXPRESSION) { + if (breakPoint == BreakPoint.P1 && builder.getCurrentOffset() == breakOffset) { + error(builder, JavaPsiBundle.message("expected.identifier")); + drop(startMarker, dotPos); + return expr; + } + + final PsiBuilder.Marker copy = startMarker.precede(); + final int offset = builder.getCurrentOffset(); + startMarker.rollbackTo(); + + final PsiBuilder.Marker classObjAccess = parseClassAccessOrMethodReference(builder); + if (classObjAccess == null || builder.getCurrentOffset() < offset) { + copy.rollbackTo(); + return parsePrimary(builder, BreakPoint.P1, offset, mode); + } + + startMarker = copy; + expr = classObjAccess; + } + else if (dotTokenType == NEW_KEYWORD) { + dotPos.drop(); + expr = parseNew(builder, expr); + } + else if (dotTokenType == SUPER_KEYWORD && builder.lookAhead(1) == LPARENTH) { + dotPos.drop(); + PsiBuilder.Marker refExpr = expr.precede(); + builder.mark().done(myJavaElementTypeContainer.REFERENCE_PARAMETER_LIST); + builder.advanceLexer(); + refExpr.done(myJavaElementTypeContainer.REFERENCE_EXPRESSION); + expr = refExpr; + } + else if (dotTokenType == STRING_TEMPLATE_BEGIN || dotTokenType == TEXT_BLOCK_TEMPLATE_BEGIN) { + dotPos.drop(); + expr = parseStringTemplate(builder, expr, dotTokenType == TEXT_BLOCK_TEMPLATE_BEGIN); + } + else if (dotTokenType == STRING_LITERAL || dotTokenType == TEXT_BLOCK_LITERAL) { + dotPos.drop(); + final PsiBuilder.Marker templateExpression = expr.precede(); + final PsiBuilder.Marker literal = builder.mark(); + builder.advanceLexer(); + literal.done(myJavaElementTypeContainer.LITERAL_EXPRESSION); + templateExpression.done(myJavaElementTypeContainer.TEMPLATE_EXPRESSION); + expr = templateExpression; + } + else if (THIS_OR_SUPER.contains(dotTokenType) && exprType(expr) == myJavaElementTypeContainer.REFERENCE_EXPRESSION) { + if (breakPoint == BreakPoint.P2 && builder.getCurrentOffset() == breakOffset) { + dotPos.rollbackTo(); + startMarker.drop(); + return expr; + } + + PsiBuilder.Marker copy = startMarker.precede(); + int offset = builder.getCurrentOffset(); + startMarker.rollbackTo(); + + PsiBuilder.Marker ref = myParser.getReferenceParser().parseJavaCodeReference(builder, false, true, false, false); + if (ref == null || builder.getTokenType() != DOT || builder.getCurrentOffset() != dotOffset) { + copy.rollbackTo(); + return parsePrimary(builder, BreakPoint.P2, offset, mode); + } + builder.advanceLexer(); + + if (builder.getTokenType() != dotTokenType) { + copy.rollbackTo(); + return parsePrimary(builder, BreakPoint.P2, offset, mode); + } + builder.advanceLexer(); + + startMarker = copy; + expr = ref.precede(); + expr.done(dotTokenType == THIS_KEYWORD + ? myJavaElementTypeContainer.THIS_EXPRESSION + : myJavaElementTypeContainer.SUPER_EXPRESSION); + } + else { + PsiBuilder.Marker refExpr = expr.precede(); + + myParser.getReferenceParser().parseReferenceParameterList(builder, false, false); + + if (!expect(builder, ID_OR_SUPER)) { + dotPos.rollbackTo(); + builder.advanceLexer(); + myParser.getReferenceParser().parseReferenceParameterList(builder, false, false); + error(builder, JavaPsiBundle.message("expected.identifier")); + refExpr.done(myJavaElementTypeContainer.REFERENCE_EXPRESSION); + startMarker.drop(); + return refExpr; + } + + dotPos.drop(); + refExpr.done(myJavaElementTypeContainer.REFERENCE_EXPRESSION); + expr = refExpr; + } + } + else if (tokenType == LPARENTH) { + if (exprType(expr) != myJavaElementTypeContainer.REFERENCE_EXPRESSION) { + startMarker.drop(); + return expr; + } + + PsiBuilder.Marker callExpr = expr.precede(); + parseArgumentList(builder); + callExpr.done(myJavaElementTypeContainer.METHOD_CALL_EXPRESSION); + expr = callExpr; + } + else if (tokenType == LBRACKET) { + if (breakPoint == BreakPoint.P4) { + startMarker.drop(); + return expr; + } + + builder.advanceLexer(); + + if (builder.getTokenType() == RBRACKET && + exprType(expr) == myJavaElementTypeContainer.REFERENCE_EXPRESSION) { + final int pos = builder.getCurrentOffset(); + final PsiBuilder.Marker copy = startMarker.precede(); + startMarker.rollbackTo(); + + final PsiBuilder.Marker classObjAccess = parseClassAccessOrMethodReference(builder); + if (classObjAccess == null || builder.getCurrentOffset() <= pos) { + copy.rollbackTo(); + return parsePrimary(builder, BreakPoint.P4, -1, mode); + } + + startMarker = copy; + expr = classObjAccess; + } + else { + final PsiBuilder.Marker arrayAccess = expr.precede(); + + final PsiBuilder.Marker index = parse(builder, mode); + if (index == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + arrayAccess.done(myJavaElementTypeContainer.ARRAY_ACCESS_EXPRESSION); + startMarker.drop(); + return arrayAccess; + } + + if (builder.getTokenType() != RBRACKET) { + error(builder, JavaPsiBundle.message("expected.rbracket")); + arrayAccess.done(myJavaElementTypeContainer.ARRAY_ACCESS_EXPRESSION); + startMarker.drop(); + return arrayAccess; + } + builder.advanceLexer(); + + arrayAccess.done(myJavaElementTypeContainer.ARRAY_ACCESS_EXPRESSION); + expr = arrayAccess; + } + } + else if (tokenType == DOUBLE_COLON) { + return parseMethodReference(builder, startMarker); + } + else { + startMarker.drop(); + return expr; + } + } + } + + private @Nullable PsiBuilder.Marker parsePrimaryExpressionStart(final PsiBuilder builder, final int mode) { + IElementType tokenType = builder.getTokenType(); + + if (tokenType == TEXT_BLOCK_TEMPLATE_BEGIN || tokenType == STRING_TEMPLATE_BEGIN) { + return parseStringTemplate(builder, null, tokenType == TEXT_BLOCK_TEMPLATE_BEGIN); + } + + if (ALL_LITERALS.contains(tokenType)) { + final PsiBuilder.Marker literal = builder.mark(); + builder.advanceLexer(); + literal.done(myJavaElementTypeContainer.LITERAL_EXPRESSION); + return literal; + } + + if (tokenType == LBRACE) { + return parseArrayInitializer(builder); + } + + if (tokenType == NEW_KEYWORD) { + return parseNew(builder, null); + } + + if (tokenType == LPARENTH) { + if (!BitUtil.isSet(mode, ExpressionParser.FORBID_LAMBDA_MASK)) { + final PsiBuilder.Marker lambda = parseLambdaAfterParenth(builder); + if (lambda != null) { + return lambda; + } + } + + final PsiBuilder.Marker parenth = builder.mark(); + builder.advanceLexer(); + + final PsiBuilder.Marker inner = parse(builder, mode); + if (inner == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + + if (!expect(builder, RPARENTH) && inner != null) { + error(builder, JavaPsiBundle.message("expected.rparen")); + } + + parenth.done(myJavaElementTypeContainer.PARENTH_EXPRESSION); + return parenth; + } + + if (TYPE_START.contains(tokenType)) { + final PsiBuilder.Marker mark = builder.mark(); + + final ReferenceParser.TypeInfo typeInfo = myParser.getReferenceParser().parseTypeInfo(builder, 0); + if (typeInfo != null) { + boolean optionalClassKeyword = typeInfo.isPrimitive || typeInfo.isArray; + if (optionalClassKeyword || !typeInfo.hasErrors && typeInfo.isParameterized) { + final PsiBuilder.Marker result = parseClassAccessOrMethodReference(builder, mark, optionalClassKeyword); + if (result != null) { + return result; + } + } + } + + mark.rollbackTo(); + } + + PsiBuilder.Marker annotation = null; + if (tokenType == AT) { + annotation = myParser.getDeclarationParser().parseAnnotations(builder); + tokenType = builder.getTokenType(); + } + + if (tokenType == VAR_KEYWORD) { + builder.remapCurrentToken(tokenType = IDENTIFIER); + } + if (tokenType == IDENTIFIER) { + if (!BitUtil.isSet(mode, ExpressionParser.FORBID_LAMBDA_MASK) && builder.lookAhead(1) == ARROW) { + return parseLambdaExpression(builder, false); + } + + final PsiBuilder.Marker refExpr; + if (annotation != null) { + final PsiBuilder.Marker refParam = annotation.precede(); + refParam.doneBefore(myJavaElementTypeContainer.REFERENCE_PARAMETER_LIST, annotation); + refExpr = refParam.precede(); + } + else { + refExpr = builder.mark(); + builder.mark().done(myJavaElementTypeContainer.REFERENCE_PARAMETER_LIST); + } + + builder.advanceLexer(); + refExpr.done(myJavaElementTypeContainer.REFERENCE_EXPRESSION); + return refExpr; + } + + if (annotation != null) { + annotation.rollbackTo(); + tokenType = builder.getTokenType(); + } + + PsiBuilder.Marker expr = null; + if (tokenType == LT) { + expr = builder.mark(); + + if (!myParser.getReferenceParser().parseReferenceParameterList(builder, false, false)) { + expr.rollbackTo(); + return null; + } + + tokenType = builder.getTokenType(); + if (!THIS_OR_SUPER.contains(tokenType)) { + expr.rollbackTo(); + return null; + } + } + + if (THIS_OR_SUPER.contains(tokenType)) { + if (expr == null) { + expr = builder.mark(); + builder.mark().done(myJavaElementTypeContainer.REFERENCE_PARAMETER_LIST); + } + builder.advanceLexer(); + expr.done(builder.getTokenType() == LPARENTH + ? myJavaElementTypeContainer.REFERENCE_EXPRESSION + : tokenType == THIS_KEYWORD + ? myJavaElementTypeContainer.THIS_EXPRESSION + : myJavaElementTypeContainer.SUPER_EXPRESSION); + return expr; + } + + return null; + } + + private @Nullable PsiBuilder.Marker parseClassAccessOrMethodReference(PsiBuilder builder) { + PsiBuilder.Marker expr = builder.mark(); + + boolean primitive = PRIMITIVE_TYPE_BIT_SET.contains(builder.getTokenType()); + if (myParser.getReferenceParser().parseType(builder, 0) == null) { + expr.drop(); + return null; + } + + PsiBuilder.Marker result = parseClassAccessOrMethodReference(builder, expr, primitive); + if (result == null) expr.rollbackTo(); + return result; + } + + private @Nullable PsiBuilder.Marker parseClassAccessOrMethodReference(PsiBuilder builder, + PsiBuilder.Marker expr, + boolean optionalClassKeyword) { + IElementType tokenType = builder.getTokenType(); + if (tokenType == DOT) { + return parseClassObjectAccess(builder, expr, optionalClassKeyword); + } + else if (tokenType == DOUBLE_COLON) { + return parseMethodReference(builder, expr); + } + + return null; + } + + private @NotNull PsiBuilder.Marker parseMethodReference(final PsiBuilder builder, final PsiBuilder.Marker start) { + builder.advanceLexer(); + + myParser.getReferenceParser().parseReferenceParameterList(builder, false, false); + + if (!expect(builder, IDENTIFIER) && !expect(builder, NEW_KEYWORD)) { + error(builder, JavaPsiBundle.message("expected.identifier")); + } + + start.done(myJavaElementTypeContainer.METHOD_REF_EXPRESSION); + return start; + } + + private PsiBuilder.Marker parseStringTemplate(PsiBuilder builder, PsiBuilder.Marker start, boolean textBlock) { + final PsiBuilder.Marker templateExpression = start == null ? builder.mark() : start.precede(); + final PsiBuilder.Marker template = builder.mark(); + IElementType tokenType; + do { + builder.advanceLexer(); + tokenType = builder.getTokenType(); + if (textBlock + ? tokenType == TEXT_BLOCK_TEMPLATE_MID || tokenType == TEXT_BLOCK_TEMPLATE_END + : tokenType == STRING_TEMPLATE_MID || tokenType == STRING_TEMPLATE_END) { + emptyExpression(builder); + } + else { + parse(builder); + tokenType = builder.getTokenType(); + } + } + while (textBlock ? tokenType == TEXT_BLOCK_TEMPLATE_MID : tokenType == STRING_TEMPLATE_MID); + if (textBlock ? tokenType != TEXT_BLOCK_TEMPLATE_END : tokenType != STRING_TEMPLATE_END) { + builder.error(JavaPsiBundle.message("expected.template.fragment")); + } + else { + builder.advanceLexer(); + } + template.done(myJavaElementTypeContainer.TEMPLATE); + templateExpression.done(myJavaElementTypeContainer.TEMPLATE_EXPRESSION); + return templateExpression; + } + + private @NotNull PsiBuilder.Marker parseNew(PsiBuilder builder, @Nullable PsiBuilder.Marker start) { + PsiBuilder.Marker newExpr = (start != null ? start.precede() : builder.mark()); + builder.advanceLexer(); + + myParser.getReferenceParser().parseReferenceParameterList(builder, false, true); + + PsiBuilder.Marker refOrType; + PsiBuilder.Marker anno = myParser.getDeclarationParser().parseAnnotations(builder); + IElementType tokenType = builder.getTokenType(); + if (tokenType == IDENTIFIER) { + rollbackTo(anno); + refOrType = myParser.getReferenceParser().parseJavaCodeReference(builder, true, true, true, true); + if (refOrType == null) { + error(builder, JavaPsiBundle.message("expected.identifier")); + newExpr.done(myJavaElementTypeContainer.NEW_EXPRESSION); + return newExpr; + } + } + else if (PRIMITIVE_TYPE_BIT_SET.contains(tokenType)) { + refOrType = null; + builder.advanceLexer(); + } + else { + error(builder, JavaPsiBundle.message("expected.identifier")); + newExpr.done(myJavaElementTypeContainer.NEW_EXPRESSION); + return newExpr; + } + + if (refOrType != null && builder.getTokenType() == LPARENTH) { + parseArgumentList(builder); + if (builder.getTokenType() == LBRACE) { + final PsiBuilder.Marker classElement = refOrType.precede(); + myParser.getDeclarationParser().parseClassBodyWithBraces(builder, false, false); + classElement.done(myJavaElementTypeContainer.ANONYMOUS_CLASS); + } + newExpr.done(myJavaElementTypeContainer.NEW_EXPRESSION); + return newExpr; + } + + anno = myParser.getDeclarationParser().parseAnnotations(builder); + + if (builder.getTokenType() != LBRACKET) { + rollbackTo(anno); + error(builder, JavaPsiBundle.message(refOrType == null ? "expected.lbracket" : "expected.lparen.or.lbracket")); + newExpr.done(myJavaElementTypeContainer.NEW_EXPRESSION); + return newExpr; + } + + int bracketCount = 0; + int dimCount = 0; + while (true) { + anno = myParser.getDeclarationParser().parseAnnotations(builder); + + if (builder.getTokenType() != LBRACKET) { + rollbackTo(anno); + break; + } + builder.advanceLexer(); + + if (bracketCount == dimCount) { + final PsiBuilder.Marker dimExpr = parse(builder, 0); + if (dimExpr != null) { + dimCount++; + } + } + bracketCount++; + + if (!expectOrError(builder, RBRACKET, "expected.rbracket")) { + newExpr.done(myJavaElementTypeContainer.NEW_EXPRESSION); + return newExpr; + } + } + + if (dimCount == 0) { + if (builder.getTokenType() == LBRACE) { + parseArrayInitializer(builder); + } + else { + error(builder, JavaPsiBundle.message("expected.array.initializer")); + } + } + + newExpr.done(myJavaElementTypeContainer.NEW_EXPRESSION); + return newExpr; + } + + private @NotNull PsiBuilder.Marker parseArrayInitializer(PsiBuilder builder) { + return parseArrayInitializer(builder, myJavaElementTypeContainer.ARRAY_INITIALIZER_EXPRESSION, this::parse, + "expected.expression"); + } + + public @NotNull PsiBuilder.Marker parseArrayInitializer(@NotNull PsiBuilder builder, + @NotNull IElementType type, + @NotNull Function elementParser, + @NotNull @PropertyKey(resourceBundle = JavaPsiBundle.BUNDLE) String missingElementKey) { + PsiBuilder.Marker arrayInit = builder.mark(); + builder.advanceLexer(); + + boolean first = true; + while (true) { + if (builder.getTokenType() == RBRACE) { + builder.advanceLexer(); + break; + } + + if (builder.getTokenType() == null) { + error(builder, JavaPsiBundle.message("expected.rbrace")); + break; + } + + if (elementParser.apply(builder) == null) { + if (builder.getTokenType() == COMMA) { + if (first && builder.lookAhead(1) == RBRACE) { + advance(builder, 2); + break; + } + builder.error(JavaPsiBundle.message(missingElementKey)); + } + else if (builder.getTokenType() != RBRACE) { + error(builder, JavaPsiBundle.message("expected.rbrace")); + break; + } + } + + first = false; + + IElementType tokenType = builder.getTokenType(); + if (!expect(builder, COMMA) && tokenType != RBRACE) { + error(builder, JavaPsiBundle.message("expected.comma")); + } + } + + arrayInit.done(type); + return arrayInit; + } + + public @NotNull PsiBuilder.Marker parseArgumentList(final PsiBuilder builder) { + final PsiBuilder.Marker list = builder.mark(); + builder.advanceLexer(); + + boolean first = true; + while (true) { + final IElementType tokenType = builder.getTokenType(); + if (first && (ARGS_LIST_END.contains(tokenType) || builder.eof())) break; + if (!first && !ARGS_LIST_CONTINUE.contains(tokenType)) break; + + boolean hasError = false; + if (!first) { + if (builder.getTokenType() == COMMA) { + builder.advanceLexer(); + } + else { + hasError = true; + error(builder, JavaPsiBundle.message("expected.comma.or.rparen")); + emptyExpression(builder); + } + } + first = false; + + final PsiBuilder.Marker arg = parse(builder, 0); + if (arg == null) { + if (!hasError) { + error(builder, JavaPsiBundle.message("expected.expression")); + emptyExpression(builder); + } + if (!ARGS_LIST_CONTINUE.contains(builder.getTokenType())) break; + if (builder.getTokenType() != COMMA && !builder.eof()) { + builder.advanceLexer(); + } + } + } + + boolean closed = true; + if (!expect(builder, RPARENTH)) { + if (first) { + error(builder, JavaPsiBundle.message("expected.rparen")); + } + else { + error(builder, JavaPsiBundle.message("expected.comma.or.rparen")); + } + closed = false; + } + + list.done(myJavaElementTypeContainer.EXPRESSION_LIST); + if (!closed) { + list.setCustomEdgeTokenBinders(null, WhitespacesBinders.GREEDY_RIGHT_BINDER); + } + return list; + } + + private @Nullable PsiBuilder.Marker parseLambdaAfterParenth(final PsiBuilder builder) { + final boolean isLambda; + final boolean isTyped; + + final IElementType nextToken1 = builder.lookAhead(1); + final IElementType nextToken2 = builder.lookAhead(2); + if (nextToken1 == RPARENTH && nextToken2 == ARROW) { + isLambda = true; + isTyped = false; + } + else if (nextToken1 == AT || + MODIFIER_BIT_SET.contains(nextToken1) || + PRIMITIVE_TYPE_BIT_SET.contains(nextToken1)) { + isLambda = true; + isTyped = true; + } + else if (nextToken1 == IDENTIFIER) { + if (nextToken2 == COMMA || nextToken2 == RPARENTH && builder.lookAhead(3) == ARROW) { + isLambda = true; + isTyped = false; + } + else if (nextToken2 == ARROW) { + isLambda = false; + isTyped = false; + } + else { + boolean lambda = false; + + PsiBuilder.Marker marker = builder.mark(); + builder.advanceLexer(); + ReferenceParser.TypeInfo typeInfo = myParser.getReferenceParser().parseTypeInfo( + builder, ReferenceParser.ELLIPSIS | ReferenceParser.WILDCARD); + if (typeInfo != null) { + IElementType t = builder.getTokenType(); + lambda = t == IDENTIFIER || + t == THIS_KEYWORD || + t == RPARENTH && builder.lookAhead(1) == ARROW; + } + marker.rollbackTo(); + + isLambda = lambda; + isTyped = true; + } + } + else { + isLambda = false; + isTyped = false; + } + + return isLambda ? parseLambdaExpression(builder, isTyped) : null; + } + + private @Nullable PsiBuilder.Marker parseLambdaExpression(final PsiBuilder builder, final boolean typed) { + final PsiBuilder.Marker start = builder.mark(); + + myParser.getDeclarationParser().parseLambdaParameterList(builder, typed); + + if (!expect(builder, ARROW)) { + start.rollbackTo(); + return null; + } + + final PsiBuilder.Marker body; + if (builder.getTokenType() == LBRACE) { + body = myParser.getStatementParser().parseCodeBlock(builder); + } + else { + body = parse(builder, 0); + } + + if (body == null) { + builder.error(JavaPsiBundle.message("expected.lbrace")); + } + + start.done(myJavaElementTypeContainer.LAMBDA_EXPRESSION); + return start; + } + + private @Nullable PsiBuilder.Marker parseClassObjectAccess(PsiBuilder builder, PsiBuilder.Marker expr, boolean optionalClassKeyword) { + final PsiBuilder.Marker mark = builder.mark(); + builder.advanceLexer(); + + if (builder.getTokenType() == CLASS_KEYWORD) { + mark.drop(); + builder.advanceLexer(); + } + else { + if (!optionalClassKeyword) return null; + mark.rollbackTo(); + builder.error(JavaPsiBundle.message("class.literal.expected")); + } + + expr.done(myJavaElementTypeContainer.CLASS_OBJECT_ACCESS_EXPRESSION); + return expr; + } + + private void emptyExpression(final PsiBuilder builder) { + emptyElement(builder, myJavaElementTypeContainer.EMPTY_EXPRESSION); + } + + private static IElementType getBinOpToken(PsiBuilder builder) { + IElementType tokenType = builder.getTokenType(); + if (tokenType != GT) return tokenType; + + if (builder.rawLookup(1) == GT) { + if (builder.rawLookup(2) == GT) { + if (builder.rawLookup(3) == EQ) { + return GTGTGTEQ; + } + return GTGTGT; + } + if (builder.rawLookup(2) == EQ) { + return GTGTEQ; + } + return GTGT; + } + else if (builder.rawLookup(1) == EQ) { + return GE; + } + + return tokenType; + } + + private static void advanceBinOpToken(final PsiBuilder builder, final IElementType type) { + final PsiBuilder.Marker gtToken = builder.mark(); + + if (type == GTGTGTEQ) { + advance(builder, 4); + } + else if (type == GTGTGT || type == GTGTEQ) { + advance(builder, 3); + } + else if (type == GTGT || type == GE) { + advance(builder, 2); + } + else { + gtToken.drop(); + builder.advanceLexer(); + return; + } + + gtToken.collapse(type); + } + + private enum BreakPoint {P1, P2, P4} + + private interface InfixParser { + /** + * Starts to parse before the token with binOpType. + */ + void parse(PrattExpressionParser parser, + PsiBuilder builder, + PsiBuilder.Marker beforeLhs, + IElementType binOpType, + int currentPrecedence, + int mode); + } + + private static final class ParserData { + private final int myPrecedence; + private final InfixParser myParser; + + private ParserData(int precedence, InfixParser parser) { + this.myPrecedence = precedence; + myParser = parser; + } + } + + private final class AssignmentParser implements PrattExpressionParser.InfixParser { + @Override + public void parse(PrattExpressionParser parser, + PsiBuilder builder, + PsiBuilder.Marker beforeLhs, + IElementType binOpType, + int currentPrecedence, + int mode) { + advanceBinOpToken(builder, binOpType); + PsiBuilder.Marker right = parser.tryParseWithPrecedenceAtMost(builder, ASSIGNMENT_PRECEDENCE, mode); + if (right == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + done(beforeLhs, myJavaElementTypeContainer.ASSIGNMENT_EXPRESSION, builder, myWhiteSpaceAndCommentSetHolder); + } + } + + private final class PolyExprParser implements PrattExpressionParser.InfixParser { + + @Override + public void parse(PrattExpressionParser parser, + PsiBuilder builder, + PsiBuilder.Marker beforeLhs, + IElementType binOpType, + int currentPrecedence, + int mode) { + int operandCount = 1; + while (true) { + advanceBinOpToken(builder, binOpType); + PsiBuilder.Marker rhs = parser.tryParseWithPrecedenceAtMost(builder, currentPrecedence - 1, mode); + if (rhs == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + operandCount++; + IElementType nextToken = getBinOpToken(builder); + if (nextToken != binOpType) { + break; + } + } + done(beforeLhs, operandCount > 2 + ? myJavaElementTypeContainer.POLYADIC_EXPRESSION + : myJavaElementTypeContainer.BINARY_EXPRESSION, builder, myWhiteSpaceAndCommentSetHolder); + } + } + + private final class ConditionalExprParser implements PrattExpressionParser.InfixParser { + @Override + public void parse(PrattExpressionParser parser, + PsiBuilder builder, + PsiBuilder.Marker beforeLhs, + IElementType binOpType, + int currentPrecedence, + int mode) { + builder.advanceLexer(); // skipping ? + + final PsiBuilder.Marker truePart = parser.parse(builder, mode); + if (truePart == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + beforeLhs.done(myJavaElementTypeContainer.CONDITIONAL_EXPRESSION); + return; + } + + if (builder.getTokenType() != COLON) { + error(builder, JavaPsiBundle.message("expected.colon")); + beforeLhs.done(myJavaElementTypeContainer.CONDITIONAL_EXPRESSION); + return; + } + builder.advanceLexer(); + + final PsiBuilder.Marker falsePart = parser.tryParseWithPrecedenceAtMost(builder, CONDITIONAL_EXPR_PRECEDENCE, mode); + if (falsePart == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + + beforeLhs.done(myJavaElementTypeContainer.CONDITIONAL_EXPRESSION); + } + } + + private final class InstanceofParser implements PrattExpressionParser.InfixParser { + + @Override + public void parse(PrattExpressionParser parser, + PsiBuilder builder, + PsiBuilder.Marker beforeLhs, + IElementType binOpType, + int currentPrecedence, + int mode) { + builder.advanceLexer(); // skipping 'instanceof' + + JavaParser javaParser = parser.myParser; + if (!javaParser.getPatternParser().isPattern(builder)) { + PsiBuilder.Marker type = + javaParser.getReferenceParser().parseType(builder, ReferenceParser.EAT_LAST_DOT | ReferenceParser.WILDCARD); + if (type == null) { + error(builder, JavaPsiBundle.message("expected.type")); + } + } + else { + javaParser.getPatternParser().parsePrimaryPattern(builder, false); + } + beforeLhs.done(myJavaElementTypeContainer.INSTANCE_OF_EXPRESSION); + } } } diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/ReferenceParser.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/ReferenceParser.java index 7193f73c768b..7333f57fd657 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/ReferenceParser.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/parser/ReferenceParser.java @@ -1,22 +1,393 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.lang.java.parser; +import com.intellij.core.JavaPsiBundle; +import com.intellij.java.syntax.parser.JavaKeywords; import com.intellij.lang.PsiBuilder; +import com.intellij.pom.java.JavaFeature; +import com.intellij.psi.JavaTokenType; +import com.intellij.psi.impl.source.AbstractBasicJavaElementTypeFactory; +import com.intellij.psi.impl.source.BasicElementTypes; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.TokenSet; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static com.intellij.lang.PsiBuilderUtil.expect; +import static com.intellij.lang.java.parser.JavaParserUtil.*; +import static com.intellij.util.BitUtil.isSet; +import static com.intellij.util.BitUtil.set; /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated -public class ReferenceParser extends BasicReferenceParser { - public static class TypeInfo extends BasicReferenceParser.TypeInfo { +public class ReferenceParser { + public static final int EAT_LAST_DOT = 0x01; + public static final int ELLIPSIS = 0x02; + public static final int WILDCARD = 0x04; + public static final int DIAMONDS = 0x08; + public static final int DISJUNCTIONS = 0x10; + public static final int CONJUNCTIONS = 0x20; + public static final int INCOMPLETE_ANNO = 0x40; + public static final int VAR_TYPE = 0x80; + private static final TokenSet WILDCARD_KEYWORD_SET = TokenSet.create(JavaTokenType.EXTENDS_KEYWORD, JavaTokenType.SUPER_KEYWORD); + private final JavaParser myParser; + private final AbstractBasicJavaElementTypeFactory.JavaElementTypeContainer myJavaElementTypeContainer; + + public @Nullable PsiBuilder.Marker parseType(PsiBuilder builder, int flags) { + TypeInfo typeInfo = parseTypeInfo(builder, flags); + return typeInfo != null ? typeInfo.marker : null; + } + + private @Nullable TypeInfo parseTypeInfo(PsiBuilder builder, int flags, boolean badWildcard) { + if (builder.getTokenType() == null) return null; + + TypeInfo typeInfo = new TypeInfo(); + + PsiBuilder.Marker type = builder.mark(); + PsiBuilder.Marker anno = myParser.getDeclarationParser().parseAnnotations(builder); + + IElementType tokenType = builder.getTokenType(); + if (tokenType == JavaTokenType.IDENTIFIER && + isSet(flags, VAR_TYPE) && + builder.lookAhead(1) != JavaTokenType.DOT && + builder.lookAhead(1) != JavaTokenType.COLON && + JavaKeywords.VAR.equals(builder.getTokenText()) && + JavaFeature.LVTI.isSufficient(getLanguageLevel(builder))) { + builder.remapCurrentToken(tokenType = JavaTokenType.VAR_KEYWORD); + } + else if (tokenType == JavaTokenType.VAR_KEYWORD && !isSet(flags, VAR_TYPE)) { + builder.remapCurrentToken(tokenType = JavaTokenType.IDENTIFIER); + } + + if (expect(builder, BasicElementTypes.BASIC_PRIMITIVE_TYPE_BIT_SET)) { + typeInfo.isPrimitive = true; + } + else if ((isSet(flags, WILDCARD) || badWildcard) && (tokenType == JavaTokenType.QUEST)) { + builder.advanceLexer(); + completeWildcardType(builder, isSet(flags, WILDCARD), type); + typeInfo.marker = type; + return typeInfo; + } + else if (tokenType == JavaTokenType.IDENTIFIER) { + parseJavaCodeReference(builder, isSet(flags, EAT_LAST_DOT), true, false, false, false, isSet(flags, DIAMONDS), typeInfo); + } + else if (tokenType == JavaTokenType.VAR_KEYWORD) { + builder.advanceLexer(); + type.done(myJavaElementTypeContainer.TYPE); + typeInfo.marker = type; + return typeInfo; + } + else if (isSet(flags, DIAMONDS) && tokenType == JavaTokenType.GT) { + if (anno == null) { + emptyElement(builder, myJavaElementTypeContainer.DIAMOND_TYPE); + } + else { + error(builder, JavaPsiBundle.message("expected.identifier")); + typeInfo.hasErrors = true; + } + type.done(myJavaElementTypeContainer.TYPE); + typeInfo.marker = type; + return typeInfo; + } + else { + type.drop(); + if (anno != null && isSet(flags, INCOMPLETE_ANNO)) { + error(builder, JavaPsiBundle.message("expected.type")); + typeInfo.marker = anno; + typeInfo.hasErrors = true; + return typeInfo; + } + return null; + } + + type.done(myJavaElementTypeContainer.TYPE); + while (true) { + myParser.getDeclarationParser().parseAnnotations(builder); + + PsiBuilder.Marker bracket = builder.mark(); + if (!expect(builder, JavaTokenType.LBRACKET)) { + bracket.drop(); + break; + } + if (!expect(builder, JavaTokenType.RBRACKET)) { + bracket.rollbackTo(); + break; + } + bracket.drop(); + typeInfo.isArray = true; + } + + if (isSet(flags, ELLIPSIS) && builder.getTokenType() == JavaTokenType.ELLIPSIS) { + builder.advanceLexer(); + typeInfo.isVarArg = true; + } + + if (typeInfo.isVarArg || typeInfo.isArray) { + type = type.precede(); + type.done(myJavaElementTypeContainer.TYPE); + } + + typeInfo.marker = type; + return typeInfo; + } + + private void completeWildcardType(PsiBuilder builder, boolean wildcard, PsiBuilder.Marker type) { + if (expect(builder, WILDCARD_KEYWORD_SET)) { + if (parseTypeInfo(builder, EAT_LAST_DOT) == null) { + error(builder, JavaPsiBundle.message("expected.type")); + } + } + + if (wildcard) { + type.done(myJavaElementTypeContainer.TYPE); + } + else { + type.error(JavaPsiBundle.message("error.message.wildcard.not.expected")); + } + } + + public @Nullable PsiBuilder.Marker parseJavaCodeReference(PsiBuilder builder, + boolean eatLastDot, + boolean parameterList, + boolean isNew, + boolean diamonds) { + return parseJavaCodeReference(builder, eatLastDot, parameterList, false, false, isNew, diamonds, new TypeInfo()); + } + + public boolean parseImportCodeReference(PsiBuilder builder, boolean isStatic) { + TypeInfo typeInfo = new TypeInfo(); + parseJavaCodeReference(builder, true, false, true, isStatic, false, false, typeInfo); + return !typeInfo.hasErrors; + } + + private @Nullable PsiBuilder.Marker parseJavaCodeReference(PsiBuilder builder, + boolean eatLastDot, + boolean parameterList, + boolean isImport, + boolean isStaticImport, + boolean isNew, + boolean diamonds, + TypeInfo typeInfo) { + PsiBuilder.Marker refElement = builder.mark(); + + myParser.getDeclarationParser().parseAnnotations(builder); + + if (!expect(builder, JavaTokenType.IDENTIFIER)) { + refElement.rollbackTo(); + if (isImport) { + error(builder, JavaPsiBundle.message("expected.identifier")); + } + typeInfo.hasErrors = true; + return null; + } + + if (parameterList) { + typeInfo.isParameterized = parseReferenceParameterList(builder, true, diamonds); + } + else if (!isStaticImport) { + emptyElement(builder, myJavaElementTypeContainer.REFERENCE_PARAMETER_LIST); + } + + while (builder.getTokenType() == JavaTokenType.DOT) { + refElement.done(myJavaElementTypeContainer.JAVA_CODE_REFERENCE); + + if (isNew && !diamonds && typeInfo.isParameterized) { + return refElement; + } + + PsiBuilder.Marker dotPos = builder.mark(); + builder.advanceLexer(); + + myParser.getDeclarationParser().parseAnnotations(builder); + + if (isImport && expect(builder, JavaTokenType.ASTERISK)) { + dotPos.drop(); + return refElement; + } + else if (!expect(builder, JavaTokenType.IDENTIFIER)) { + if (!eatLastDot) { + dotPos.rollbackTo(); + return refElement; + } + + typeInfo.hasErrors = true; + if (isImport) { + error(builder, JavaPsiBundle.message("import.statement.identifier.or.asterisk.expected.")); + } + else { + error(builder, JavaPsiBundle.message("expected.identifier")); + } + dotPos.drop(); + return refElement; + } + dotPos.drop(); + + refElement = refElement.precede(); + + if (parameterList) { + typeInfo.isParameterized = parseReferenceParameterList(builder, true, diamonds); + } + else { + emptyElement(builder, myJavaElementTypeContainer.REFERENCE_PARAMETER_LIST); + } + } + + if (isStaticImport) { + refElement.done(myJavaElementTypeContainer.IMPORT_STATIC_REFERENCE); + } + else { + refElement.done(myJavaElementTypeContainer.JAVA_CODE_REFERENCE); + } + return refElement; + } + + public boolean parseReferenceParameterList(PsiBuilder builder, boolean wildcard, boolean diamonds) { + PsiBuilder.Marker list = builder.mark(); + if (!expect(builder, JavaTokenType.LT)) { + list.done(myJavaElementTypeContainer.REFERENCE_PARAMETER_LIST); + return false; + } + + int flags = set(set(EAT_LAST_DOT, WILDCARD, wildcard), DIAMONDS, diamonds); + boolean isOk = true; + while (true) { + if (parseTypeInfo(builder, flags, true) == null) { + error(builder, JavaPsiBundle.message("expected.identifier")); + } + else { + IElementType tokenType = builder.getTokenType(); + if (WILDCARD_KEYWORD_SET.contains(tokenType)) { + parseReferenceList(builder, tokenType, null, JavaTokenType.AND); + } + } + + if (expect(builder, JavaTokenType.GT)) { + break; + } + else if (!expectOrError(builder, JavaTokenType.COMMA, "expected.gt.or.comma")) { + isOk = false; + break; + } + flags = set(flags, DIAMONDS, false); + } + + list.done(myJavaElementTypeContainer.REFERENCE_PARAMETER_LIST); + return isOk; + } + + public @NotNull PsiBuilder.Marker parseTypeParameters(PsiBuilder builder) { + PsiBuilder.Marker list = builder.mark(); + if (!expect(builder, JavaTokenType.LT)) { + list.done(myJavaElementTypeContainer.TYPE_PARAMETER_LIST); + return list; + } + + do { + PsiBuilder.Marker param = parseTypeParameter(builder); + if (param == null) { + error(builder, JavaPsiBundle.message("expected.type.parameter")); + } + } + while (expect(builder, JavaTokenType.COMMA)); + + if (!expect(builder, JavaTokenType.GT)) { + // hack for completion + if (builder.getTokenType() == JavaTokenType.IDENTIFIER) { + if (builder.lookAhead(1) == JavaTokenType.GT) { + PsiBuilder.Marker errorElement = builder.mark(); + builder.advanceLexer(); + errorElement.error(JavaPsiBundle.message("unexpected.identifier")); + builder.advanceLexer(); + } + else { + error(builder, JavaPsiBundle.message("expected.gt")); + } + } + else { + error(builder, JavaPsiBundle.message("expected.gt")); + } + } + + list.done(myJavaElementTypeContainer.TYPE_PARAMETER_LIST); + return list; + } + + public @Nullable PsiBuilder.Marker parseTypeParameter(PsiBuilder builder) { + PsiBuilder.Marker param = builder.mark(); + + myParser.getDeclarationParser().parseAnnotations(builder); + + boolean wild = expect(builder, JavaTokenType.QUEST); + if (!wild && !expect(builder, JavaTokenType.IDENTIFIER)) { + param.rollbackTo(); + return null; + } + + parseReferenceList(builder, JavaTokenType.EXTENDS_KEYWORD, myJavaElementTypeContainer.EXTENDS_BOUND_LIST, JavaTokenType.AND); + + if (!wild) { + param.done(myJavaElementTypeContainer.TYPE_PARAMETER); + } + else { + param.error(JavaPsiBundle.message("error.message.wildcard.not.expected")); + } + return param; + } + + public boolean parseReferenceList(PsiBuilder builder, IElementType start, @Nullable IElementType type, IElementType delimiter) { + PsiBuilder.Marker element = builder.mark(); + + boolean endsWithError = false; + if (expect(builder, start)) { + do { + endsWithError = false; + PsiBuilder.Marker classReference = parseJavaCodeReference(builder, false, true, false, false); + if (classReference == null) { + error(builder, JavaPsiBundle.message("expected.identifier")); + endsWithError = true; + } + } + while (expect(builder, delimiter)); + } + + if (type != null) { + element.done(type); + } + else { + element.error(JavaPsiBundle.message("bound.not.expected")); + } + return endsWithError; } //for backward compatibility - @Override + @Nullable public ReferenceParser.TypeInfo parseTypeInfo(PsiBuilder builder, int flags) { - BasicReferenceParser.TypeInfo typeInfo = super.parseTypeInfo(builder, flags); + ReferenceParser.TypeInfo typeInfo1 = parseTypeInfo(builder, flags, false); + + if (typeInfo1 != null) { + assert !isSet(flags, DISJUNCTIONS) || !isSet(flags, CONJUNCTIONS) : "don't set both flags simultaneously"; + IElementType operator = isSet(flags, DISJUNCTIONS) ? JavaTokenType.OR : isSet(flags, CONJUNCTIONS) ? JavaTokenType.AND : null; + + if (operator != null && builder.getTokenType() == operator) { + typeInfo1.marker = typeInfo1.marker.precede(); + + while (builder.getTokenType() == operator) { + builder.advanceLexer(); + IElementType tokenType = builder.getTokenType(); + if (tokenType != JavaTokenType.IDENTIFIER && tokenType != JavaTokenType.AT) { + error(builder, JavaPsiBundle.message("expected.identifier")); + } + parseTypeInfo(builder, flags, false); + } + + typeInfo1.marker.done(myJavaElementTypeContainer.TYPE); + } + } + + TypeInfo typeInfo = typeInfo1; if (typeInfo == null) { return null; } @@ -31,6 +402,16 @@ public class ReferenceParser extends BasicReferenceParser { } public ReferenceParser(@NotNull JavaParser javaParser) { - super(javaParser); + this.myParser = javaParser; + this.myJavaElementTypeContainer = javaParser.getJavaElementTypeFactory().getContainer(); + } + + public static class TypeInfo { + public boolean isPrimitive; + public boolean isParameterized; + public boolean isArray; + public boolean isVarArg; + public boolean hasErrors; + public PsiBuilder.Marker marker; } } diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/StatementParser.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/StatementParser.java index d7c34ed705c1..43bd1f55f528 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/StatementParser.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/parser/StatementParser.java @@ -1,16 +1,847 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.lang.java.parser; +import com.intellij.core.JavaPsiBundle; +import com.intellij.java.syntax.parser.JavaKeywords; +import com.intellij.lang.PsiBuilder; +import com.intellij.lang.WhitespacesBinders; +import com.intellij.openapi.util.Pair; +import com.intellij.pom.java.JavaFeature; +import com.intellij.psi.JavaTokenType; +import com.intellij.psi.impl.source.AbstractBasicJavaElementTypeFactory; +import com.intellij.psi.impl.source.OldParserWhiteSpaceAndCommentSetHolder; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.ILazyParseableElementType; +import com.intellij.psi.tree.TokenSet; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; + +import static com.intellij.lang.PsiBuilderUtil.*; +import static com.intellij.lang.java.parser.JavaParserUtil.*; +import static com.intellij.psi.impl.source.tree.ElementType.JAVA_COMMENT_OR_WHITESPACE_BIT_SET; /** * @deprecated Use the new Java syntax library instead. - * See {@link com.intellij.java.syntax.parser.JavaParser} + * See {@link com.intellij.java.syntax.parser.JavaParser} */ @Deprecated -public class StatementParser extends BasicStatementParser { +public class StatementParser { + + private static final TokenSet YIELD_STMT_INDICATOR_TOKENS = TokenSet.create( + JavaTokenType.PLUS, JavaTokenType.MINUS, JavaTokenType.EXCL, JavaTokenType.TILDE, + JavaTokenType.SUPER_KEYWORD, JavaTokenType.THIS_KEYWORD, + + JavaTokenType.TRUE_KEYWORD, JavaTokenType.FALSE_KEYWORD, JavaTokenType.NULL_KEYWORD, + + JavaTokenType.STRING_LITERAL, JavaTokenType.INTEGER_LITERAL, JavaTokenType.DOUBLE_LITERAL, + JavaTokenType.FLOAT_LITERAL, JavaTokenType.LONG_LITERAL, JavaTokenType.CHARACTER_LITERAL, + JavaTokenType.TEXT_BLOCK_LITERAL, + + JavaTokenType.IDENTIFIER, JavaTokenType.SWITCH_KEYWORD, JavaTokenType.NEW_KEYWORD, + + JavaTokenType.LPARENTH, + + // recovery + JavaTokenType.RBRACE, JavaTokenType.SEMICOLON, JavaTokenType.CASE_KEYWORD + ); + private static final TokenSet TRY_CLOSERS_SET = TokenSet.create(JavaTokenType.CATCH_KEYWORD, JavaTokenType.FINALLY_KEYWORD); + private final JavaParser myParser; + private final AbstractBasicJavaElementTypeFactory.JavaElementTypeContainer myJavaElementTypeContainer; + private final OldParserWhiteSpaceAndCommentSetHolder myWhiteSpaceAndCommentSetHolder = OldParserWhiteSpaceAndCommentSetHolder.INSTANCE; public StatementParser(@NotNull JavaParser javaParser) { - super(javaParser); + this.myParser = javaParser; + this.myJavaElementTypeContainer = javaParser.getJavaElementTypeFactory().getContainer(); + } + + public @Nullable PsiBuilder.Marker parseCodeBlock(@NotNull PsiBuilder builder) { + return parseCodeBlock(builder, false); + } + + public @Nullable PsiBuilder.Marker parseCodeBlock(@NotNull PsiBuilder builder, boolean isStatement) { + if (builder.getTokenType() != JavaTokenType.LBRACE) return null; + if (isStatement && isParseStatementCodeBlocksDeep(builder)) return parseCodeBlockDeep(builder, false); + return parseBlockLazy(builder, JavaTokenType.LBRACE, JavaTokenType.RBRACE, myJavaElementTypeContainer.CODE_BLOCK); + } + + public @Nullable PsiBuilder.Marker parseCodeBlockDeep(@NotNull PsiBuilder builder, boolean parseUntilEof) { + if (builder.getTokenType() != JavaTokenType.LBRACE) return null; + + PsiBuilder.Marker codeBlock = builder.mark(); + builder.advanceLexer(); + + parseStatements(builder, parseUntilEof ? BraceMode.TILL_LAST : BraceMode.TILL_FIRST); + + boolean greedyBlock = !expectOrError(builder, JavaTokenType.RBRACE, "expected.rbrace"); + builder.getTokenType(); // eat spaces + + done(codeBlock, myJavaElementTypeContainer.CODE_BLOCK, builder, myWhiteSpaceAndCommentSetHolder); + if (greedyBlock) { + codeBlock.setCustomEdgeTokenBinders(null, WhitespacesBinders.GREEDY_RIGHT_BINDER); + } + return codeBlock; + } + + public void parseStatements(@NotNull PsiBuilder builder) { + parseStatements(builder, null); + } + + private void parseStatements(PsiBuilder builder, @Nullable BraceMode braceMode) { + while (builder.getTokenType() != null) { + PsiBuilder.Marker statement = parseStatement(builder); + if (statement != null) continue; + + IElementType tokenType = builder.getTokenType(); + if (tokenType == JavaTokenType.RBRACE && + (braceMode == BraceMode.TILL_FIRST || braceMode == BraceMode.TILL_LAST && builder.lookAhead(1) == null)) { + break; + } + + PsiBuilder.Marker error = builder.mark(); + builder.advanceLexer(); + if (tokenType == JavaTokenType.ELSE_KEYWORD) { + error.error(JavaPsiBundle.message("else.without.if")); + } + else if (tokenType == JavaTokenType.CATCH_KEYWORD) { + error.error(JavaPsiBundle.message("catch.without.try")); + } + else if (tokenType == JavaTokenType.FINALLY_KEYWORD) { + error.error(JavaPsiBundle.message("finally.without.try")); + } + else { + error.error(JavaPsiBundle.message("unexpected.token")); + } + } + } + + public @Nullable PsiBuilder.Marker parseStatement(@NotNull PsiBuilder builder) { + IElementType tokenType = builder.getTokenType(); + if (tokenType == JavaTokenType.IF_KEYWORD) { + return parseIfStatement(builder); + } + else if (tokenType == JavaTokenType.WHILE_KEYWORD) { + return parseWhileStatement(builder); + } + else if (tokenType == JavaTokenType.FOR_KEYWORD) { + return parseForStatement(builder); + } + else if (tokenType == JavaTokenType.DO_KEYWORD) { + return parseDoWhileStatement(builder); + } + else if (tokenType == JavaTokenType.SWITCH_KEYWORD) { + return parseSwitchStatement(builder); + } + else if (tokenType == JavaTokenType.CASE_KEYWORD || tokenType == JavaTokenType.DEFAULT_KEYWORD) { + return parseSwitchLabelStatement(builder); + } + else if (tokenType == JavaTokenType.BREAK_KEYWORD) { + return parseBreakStatement(builder); + } + else if (isStmtYieldToken(builder, tokenType)) { + return parseYieldStatement(builder); + } + else if (tokenType == JavaTokenType.CONTINUE_KEYWORD) { + return parseContinueStatement(builder); + } + else if (tokenType == JavaTokenType.RETURN_KEYWORD) { + return parseReturnStatement(builder); + } + else if (tokenType == JavaTokenType.THROW_KEYWORD) { + return parseThrowStatement(builder); + } + else if (tokenType == JavaTokenType.SYNCHRONIZED_KEYWORD) { + return parseSynchronizedStatement(builder); + } + else if (tokenType == JavaTokenType.TRY_KEYWORD) { + return parseTryStatement(builder); + } + else if (tokenType == JavaTokenType.ASSERT_KEYWORD) { + return parseAssertStatement(builder); + } + else if (tokenType == JavaTokenType.LBRACE) { + return parseBlockStatement(builder); + } + else if (tokenType instanceof ILazyParseableElementType) { + builder.advanceLexer(); + return null; + } + else if (tokenType == JavaTokenType.SEMICOLON) { + PsiBuilder.Marker empty = builder.mark(); + builder.advanceLexer(); + done(empty, myJavaElementTypeContainer.EMPTY_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return empty; + } + else if (tokenType == JavaTokenType.IDENTIFIER || tokenType == JavaTokenType.AT) { + PsiBuilder.Marker refPos = builder.mark(); + boolean nonSealed = DeclarationParser.isNonSealedToken(builder, tokenType); + myParser.getDeclarationParser().parseAnnotations(builder); + skipQualifiedName(builder); + IElementType current = builder.getTokenType(), next = builder.lookAhead(1); + refPos.rollbackTo(); + + if (current == JavaTokenType.LT || current == JavaTokenType.DOT && next == JavaTokenType.AT || nonSealed) { + PsiBuilder.Marker declStatement = builder.mark(); + + if (myParser.getDeclarationParser().parse(builder, DeclarationParser.Context.CODE_BLOCK) != null) { + done(declStatement, myJavaElementTypeContainer.DECLARATION_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return declStatement; + } + + ReferenceParser.TypeInfo type = myParser.getReferenceParser().parseTypeInfo(builder, 0); + if (current == JavaTokenType.LT && (type == null || !type.isParameterized)) { + declStatement.rollbackTo(); + } + else if (type == null || builder.getTokenType() != JavaTokenType.DOUBLE_COLON) { + error(builder, JavaPsiBundle.message("expected.identifier")); + if (type == null) builder.advanceLexer(); + done(declStatement, myJavaElementTypeContainer.DECLARATION_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return declStatement; + } + else { + declStatement.rollbackTo(); // generic type followed by the double colon is a good candidate for being a constructor reference + } + } + } + + PsiBuilder.Marker pos = builder.mark(); + PsiBuilder.Marker expr = myParser.getExpressionParser().parse(builder); + IElementType incompleteDeclarationRestrictedTokenType = null; + + if (expr != null) { + int count = 1; + PsiBuilder.Marker list = expr.precede(); + PsiBuilder.Marker statement = list.precede(); + while (builder.getTokenType() == JavaTokenType.COMMA) { + PsiBuilder.Marker commaPos = builder.mark(); + builder.advanceLexer(); + PsiBuilder.Marker expr1 = myParser.getExpressionParser().parse(builder); + if (expr1 == null) { + commaPos.rollbackTo(); + break; + } + commaPos.drop(); + count++; + } + if (count > 1) { + pos.drop(); + done(list, myJavaElementTypeContainer.EXPRESSION_LIST, builder, myWhiteSpaceAndCommentSetHolder); + semicolon(builder); + done(statement, myJavaElementTypeContainer.EXPRESSION_LIST_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + if (exprType(expr) != myJavaElementTypeContainer.REFERENCE_EXPRESSION) { + drop(list, pos); + semicolon(builder); + done(statement, myJavaElementTypeContainer.EXPRESSION_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + boolean singleToken = expr.getEndIndex() - expr.getStartIndex() == 1; + pos.rollbackTo(); + if (singleToken && builder.getTokenType() == JavaTokenType.IDENTIFIER) { + String text = builder.getTokenText(); + if (JavaKeywords.RECORD.equals(text) && JavaFeature.RECORDS.isSufficient(getLanguageLevel(builder))) { + incompleteDeclarationRestrictedTokenType = JavaTokenType.RECORD_KEYWORD; + } + if (JavaKeywords.VAR.equals(text) && JavaFeature.LVTI.isSufficient(getLanguageLevel(builder))) { + incompleteDeclarationRestrictedTokenType = JavaTokenType.VAR_KEYWORD; + } + } + } + else { + pos.drop(); + } + + PsiBuilder.Marker decl = myParser.getDeclarationParser().parse(builder, DeclarationParser.Context.CODE_BLOCK); + if (decl != null) { + PsiBuilder.Marker statement = decl.precede(); + done(statement, myJavaElementTypeContainer.DECLARATION_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + if (builder.getTokenType() == JavaTokenType.IDENTIFIER && builder.lookAhead(1) == JavaTokenType.COLON) { + PsiBuilder.Marker statement = builder.mark(); + advance(builder, 2); + parseStatement(builder); + done(statement, myJavaElementTypeContainer.LABELED_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + if (expr != null) { + PsiBuilder.Marker statement = builder.mark(); + IElementType statementType; + if (incompleteDeclarationRestrictedTokenType != null) { + builder.remapCurrentToken(incompleteDeclarationRestrictedTokenType); + builder.advanceLexer(); + error(builder, JavaPsiBundle.message("expected.identifier")); + statementType = myJavaElementTypeContainer.DECLARATION_STATEMENT; + } + else { + myParser.getExpressionParser().parse(builder); + statementType = myJavaElementTypeContainer.EXPRESSION_STATEMENT; + semicolon(builder); + } + done(statement, statementType, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + return null; + } + + private @NotNull PsiBuilder.Marker parseIfStatement(PsiBuilder builder) { + ArrayList stack = null; + PsiBuilder.Marker statement; + while (true) { + // replaced recursion with iteration plus stack to avoid huge call stack for extremely large else-if chains + statement = builder.mark(); + builder.advanceLexer(); + + if (parseExprInParenth(builder)) { + if (parseStatement(builder) == null) { + error(builder, JavaPsiBundle.message("expected.statement")); + } + else if (expect(builder, JavaTokenType.ELSE_KEYWORD)) { + if (builder.getTokenType() == JavaTokenType.IF_KEYWORD) { + if (stack == null) stack = new ArrayList<>(); + stack.add(statement); + continue; + } + else if (parseStatement(builder) == null) { + error(builder, JavaPsiBundle.message("expected.statement")); + } + } + } + break; + } + done(statement, myJavaElementTypeContainer.IF_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + if (stack != null) { + for (int i = stack.size() - 1; i >= 0; i--) { + statement = stack.get(i); + done(statement, myJavaElementTypeContainer.IF_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + } + } + return statement; + } + + private @NotNull PsiBuilder.Marker parseWhileStatement(PsiBuilder builder) { + return parseExprInParenthWithBlock(builder, myJavaElementTypeContainer.WHILE_STATEMENT, false); + } + + @Contract(pure = true) + private boolean isRecordPatternInForEach(final PsiBuilder builder) { + PsiBuilder.Marker patternStart = myParser.getPatternParser().preParsePattern(builder); + if (patternStart == null) { + return false; + } + if (builder.getTokenType() != JavaTokenType.LPARENTH) { + patternStart.rollbackTo(); + return false; + } + builder.advanceLexer(); + + // we must distinguish a record pattern from method call in for (foo();;) + int parenBalance = 1; + while (true) { + IElementType current = builder.getTokenType(); + if (current == null) { + patternStart.rollbackTo(); + return false; + } + if (current == JavaTokenType.LPARENTH) { + parenBalance++; + } + if (current == JavaTokenType.RPARENTH) { + parenBalance--; + if (parenBalance == 0) { + break; + } + } + builder.advanceLexer(); + } + builder.advanceLexer(); + boolean isRecordPattern = builder.getTokenType() != JavaTokenType.SEMICOLON && builder.getTokenType() != JavaTokenType.DOT; + patternStart.rollbackTo(); + return isRecordPattern; + } + + private @NotNull PsiBuilder.Marker parseForStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + if (!expect(builder, JavaTokenType.LPARENTH)) { + error(builder, JavaPsiBundle.message("expected.lparen")); + done(statement, myJavaElementTypeContainer.FOR_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + if (isRecordPatternInForEach(builder)) { + myParser.getPatternParser().parsePattern(builder); + if (builder.getTokenType() == JavaTokenType.COLON) { + return parseForEachFromColon(builder, statement, myJavaElementTypeContainer.FOREACH_PATTERN_STATEMENT); + } + error(builder, JavaPsiBundle.message("expected.colon")); + // recovery: just skip everything until ')' + while (true) { + IElementType tokenType = builder.getTokenType(); + if (tokenType == null) { + done(statement, myJavaElementTypeContainer.FOREACH_PATTERN_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + if (tokenType == JavaTokenType.RPARENTH) { + return parserForEachFromRparenth(builder, statement, myJavaElementTypeContainer.FOREACH_PATTERN_STATEMENT); + } + builder.advanceLexer(); + } + } + + PsiBuilder.Marker afterParenth = builder.mark(); + PsiBuilder.Marker param = myParser.getDeclarationParser().parseParameter(builder, false, false, true); + if (param == null || exprType(param) != myJavaElementTypeContainer.PARAMETER || builder.getTokenType() != JavaTokenType.COLON) { + afterParenth.rollbackTo(); + return parseForLoopFromInitializer(builder, statement); + } + else { + afterParenth.drop(); + return parseForEachFromColon(builder, statement, myJavaElementTypeContainer.FOREACH_STATEMENT); + } + } + + private @NotNull PsiBuilder.Marker parseForLoopFromInitializer(PsiBuilder builder, PsiBuilder.Marker statement) { + if (parseStatement(builder) == null) { + error(builder, JavaPsiBundle.message("expected.statement")); + if (!expect(builder, JavaTokenType.RPARENTH)) { + done(statement, myJavaElementTypeContainer.FOR_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + } + else { + boolean missingSemicolon = false; + if (getLastToken(builder) != JavaTokenType.SEMICOLON) { + missingSemicolon = !expectOrError(builder, JavaTokenType.SEMICOLON, "expected.semicolon"); + } + + PsiBuilder.Marker expr = myParser.getExpressionParser().parse(builder); + missingSemicolon &= expr == null; + + if (!expect(builder, JavaTokenType.SEMICOLON)) { + if (!missingSemicolon) { + error(builder, JavaPsiBundle.message("expected.semicolon")); + } + if (!expect(builder, JavaTokenType.RPARENTH)) { + done(statement, myJavaElementTypeContainer.FOR_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + } + else { + parseForUpdateExpressions(builder); + if (!expect(builder, JavaTokenType.RPARENTH)) { + error(builder, JavaPsiBundle.message("expected.rparen")); + done(statement, myJavaElementTypeContainer.FOR_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + } + } + + PsiBuilder.Marker bodyStatement = parseStatement(builder); + if (bodyStatement == null) { + error(builder, JavaPsiBundle.message("expected.statement")); + } + + done(statement, myJavaElementTypeContainer.FOR_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + private void parseForUpdateExpressions(PsiBuilder builder) { + PsiBuilder.Marker expr = myParser.getExpressionParser().parse(builder); + if (expr == null) return; + + PsiBuilder.Marker expressionStatement; + if (builder.getTokenType() != JavaTokenType.COMMA) { + expressionStatement = expr.precede(); + done(expressionStatement, myJavaElementTypeContainer.EXPRESSION_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + } + else { + PsiBuilder.Marker expressionList = expr.precede(); + expressionStatement = expressionList.precede(); + + do { + builder.advanceLexer(); + PsiBuilder.Marker nextExpression = myParser.getExpressionParser().parse(builder); + if (nextExpression == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + } + while (builder.getTokenType() == JavaTokenType.COMMA); + + done(expressionList, myJavaElementTypeContainer.EXPRESSION_LIST, builder, myWhiteSpaceAndCommentSetHolder); + done(expressionStatement, myJavaElementTypeContainer.EXPRESSION_LIST_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + } + + expressionStatement.setCustomEdgeTokenBinders(null, WhitespacesBinders.DEFAULT_RIGHT_BINDER); + } + + private @NotNull PsiBuilder.Marker parseForEachFromColon(PsiBuilder builder, PsiBuilder.Marker statement, IElementType foreachStatement) { + builder.advanceLexer(); + + if (myParser.getExpressionParser().parse(builder) == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + + return parserForEachFromRparenth(builder, statement, foreachStatement); + } + + private PsiBuilder.Marker parserForEachFromRparenth(PsiBuilder builder, PsiBuilder.Marker statement, IElementType forEachType) { + if (expectOrError(builder, JavaTokenType.RPARENTH, "expected.rparen") && parseStatement(builder) == null) { + error(builder, JavaPsiBundle.message("expected.statement")); + } + + done(statement, forEachType, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + private @NotNull PsiBuilder.Marker parseDoWhileStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + PsiBuilder.Marker body = parseStatement(builder); + if (body == null) { + error(builder, JavaPsiBundle.message("expected.statement")); + } + else if (!expect(builder, JavaTokenType.WHILE_KEYWORD)) { + error(builder, JavaPsiBundle.message("expected.while")); + } + else if (parseExprInParenth(builder)) { + semicolon(builder); + } + + done(statement, myJavaElementTypeContainer.DO_WHILE_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + private @NotNull PsiBuilder.Marker parseSwitchStatement(PsiBuilder builder) { + return parseExprInParenthWithBlock(builder, myJavaElementTypeContainer.SWITCH_STATEMENT, true); + } + + /** + * @return marker and whether it contains expression inside + */ + @ApiStatus.Internal + public @NotNull Pair parseCaseLabel(PsiBuilder builder) { + if (builder.getTokenType() == JavaTokenType.DEFAULT_KEYWORD) { + PsiBuilder.Marker defaultElement = builder.mark(); + builder.advanceLexer(); + done(defaultElement, myJavaElementTypeContainer.DEFAULT_CASE_LABEL_ELEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return Pair.create(defaultElement, false); + } + if (myParser.getPatternParser().isPattern(builder)) { + PsiBuilder.Marker pattern = myParser.getPatternParser().parsePattern(builder); + return Pair.create(pattern, false); + } + return Pair.create(myParser.getExpressionParser().parseAssignmentForbiddingLambda(builder), true); + } + + private PsiBuilder.Marker parseSwitchLabelStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + boolean isCase = builder.getTokenType() == JavaTokenType.CASE_KEYWORD; + builder.advanceLexer(); + + if (isCase) { + boolean patternsAllowed = JavaFeature.PATTERNS_IN_SWITCH.isSufficient(getLanguageLevel(builder)); + PsiBuilder.Marker list = builder.mark(); + do { + Pair markerAndIsExpression = parseCaseLabel(builder); + PsiBuilder.Marker caseLabel = markerAndIsExpression.first; + if (caseLabel == null) { + error(builder, JavaPsiBundle.message(patternsAllowed ? "expected.case.label.element" : "expected.expression")); + } + } + while (expect(builder, JavaTokenType.COMMA)); + done(list, myJavaElementTypeContainer.CASE_LABEL_ELEMENT_LIST, builder, myWhiteSpaceAndCommentSetHolder); + parseGuard(builder); + } + + if (expect(builder, JavaTokenType.ARROW)) { + PsiBuilder.Marker expr; + if (builder.getTokenType() == JavaTokenType.LBRACE) { + PsiBuilder.Marker body = builder.mark(); + parseCodeBlock(builder, true); + body.done(myJavaElementTypeContainer.BLOCK_STATEMENT); + if (builder.getTokenType() == JavaTokenType.SEMICOLON) { + PsiBuilder.Marker mark = builder.mark(); + while (builder.getTokenType() == JavaTokenType.SEMICOLON) builder.advanceLexer(); + mark.error(JavaPsiBundle.message("expected.switch.label")); + } + } + else if (builder.getTokenType() == JavaTokenType.THROW_KEYWORD) { + parseThrowStatement(builder); + } + else if ((expr = myParser.getExpressionParser().parse(builder)) != null) { + PsiBuilder.Marker body = expr.precede(); + semicolon(builder); + body.done(myJavaElementTypeContainer.EXPRESSION_STATEMENT); + } + else { + error(builder, JavaPsiBundle.message("expected.switch.rule")); + expect(builder, JavaTokenType.SEMICOLON); + } + done(statement, myJavaElementTypeContainer.SWITCH_LABELED_RULE, builder, myWhiteSpaceAndCommentSetHolder); + } + else { + expectOrError(builder, JavaTokenType.COLON, "expected.colon.or.arrow"); + done(statement, myJavaElementTypeContainer.SWITCH_LABEL_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + } + + return statement; + } + + private void parseGuard(PsiBuilder builder) { + if (builder.getTokenType() == JavaTokenType.IDENTIFIER && JavaKeywords.WHEN.equals(builder.getTokenText())) { + builder.remapCurrentToken(JavaTokenType.WHEN_KEYWORD); + builder.advanceLexer(); + PsiBuilder.Marker guardingExpression = myParser.getExpressionParser().parseAssignmentForbiddingLambda(builder); + if (guardingExpression == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + } + } + + private @NotNull PsiBuilder.Marker parseBreakStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + expect(builder, JavaTokenType.IDENTIFIER); + semicolon(builder); + done(statement, myJavaElementTypeContainer.BREAK_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + private @NotNull PsiBuilder.Marker parseYieldStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + builder.remapCurrentToken(JavaTokenType.YIELD_KEYWORD); + builder.advanceLexer(); + + if (myParser.getExpressionParser().parse(builder) == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + else { + semicolon(builder); + } + + done(statement, myJavaElementTypeContainer.YIELD_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + private @NotNull PsiBuilder.Marker parseContinueStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + expect(builder, JavaTokenType.IDENTIFIER); + semicolon(builder); + done(statement, myJavaElementTypeContainer.CONTINUE_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + private @NotNull PsiBuilder.Marker parseReturnStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + myParser.getExpressionParser().parse(builder); + semicolon(builder); + done(statement, myJavaElementTypeContainer.RETURN_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + private @NotNull PsiBuilder.Marker parseThrowStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + if (myParser.getExpressionParser().parse(builder) == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + else { + semicolon(builder); + } + + done(statement, myJavaElementTypeContainer.THROW_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + private @NotNull PsiBuilder.Marker parseSynchronizedStatement(PsiBuilder builder) { + return parseExprInParenthWithBlock(builder, myJavaElementTypeContainer.SYNCHRONIZED_STATEMENT, true); + } + + private @NotNull PsiBuilder.Marker parseTryStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + boolean hasResourceList = builder.getTokenType() == JavaTokenType.LPARENTH; + if (hasResourceList) { + myParser.getDeclarationParser().parseResourceList(builder); + } + + PsiBuilder.Marker tryBlock = parseCodeBlock(builder, true); + if (tryBlock == null) { + error(builder, JavaPsiBundle.message("expected.lbrace")); + } + else if (!hasResourceList && !TRY_CLOSERS_SET.contains(builder.getTokenType())) { + error(builder, JavaPsiBundle.message("expected.catch.or.finally")); + } + else { + while (builder.getTokenType() == JavaTokenType.CATCH_KEYWORD) { + if (!parseCatchBlock(builder)) break; + } + + if (expect(builder, JavaTokenType.FINALLY_KEYWORD)) { + PsiBuilder.Marker finallyBlock = parseCodeBlock(builder, true); + if (finallyBlock == null) { + error(builder, JavaPsiBundle.message("expected.lbrace")); + } + } + } + + done(statement, myJavaElementTypeContainer.TRY_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + public boolean parseCatchBlock(@NotNull PsiBuilder builder) { + assert builder.getTokenType() == JavaTokenType.CATCH_KEYWORD : builder.getTokenType(); + PsiBuilder.Marker section = builder.mark(); + builder.advanceLexer(); + + if (!expect(builder, JavaTokenType.LPARENTH)) { + error(builder, JavaPsiBundle.message("expected.lparen")); + done(section, myJavaElementTypeContainer.CATCH_SECTION, builder, myWhiteSpaceAndCommentSetHolder); + return false; + } + + PsiBuilder.Marker param = myParser.getDeclarationParser().parseParameter(builder, false, true, false); + if (param == null) { + error(builder, JavaPsiBundle.message("expected.parameter")); + } + + if (!expect(builder, JavaTokenType.RPARENTH)) { + error(builder, JavaPsiBundle.message("expected.rparen")); + done(section, myJavaElementTypeContainer.CATCH_SECTION, builder, myWhiteSpaceAndCommentSetHolder); + return false; + } + + PsiBuilder.Marker body = parseCodeBlock(builder, true); + if (body == null) { + error(builder, JavaPsiBundle.message("expected.lbrace")); + done(section, myJavaElementTypeContainer.CATCH_SECTION, builder, myWhiteSpaceAndCommentSetHolder); + return false; + } + + done(section, myJavaElementTypeContainer.CATCH_SECTION, builder, myWhiteSpaceAndCommentSetHolder); + return true; + } + + private @NotNull PsiBuilder.Marker parseAssertStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + if (myParser.getExpressionParser().parse(builder) == null) { + error(builder, JavaPsiBundle.message("expected.boolean.expression")); + } + else if (expect(builder, JavaTokenType.COLON) && myParser.getExpressionParser().parse(builder) == null) { + error(builder, JavaPsiBundle.message("expected.expression")); + } + else { + semicolon(builder); + } + + done(statement, myJavaElementTypeContainer.ASSERT_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + private @NotNull PsiBuilder.Marker parseBlockStatement(PsiBuilder builder) { + PsiBuilder.Marker statement = builder.mark(); + parseCodeBlock(builder, true); + done(statement, myJavaElementTypeContainer.BLOCK_STATEMENT, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + public @NotNull PsiBuilder.Marker parseExprInParenthWithBlock(@NotNull PsiBuilder builder, @NotNull IElementType type, boolean block) { + PsiBuilder.Marker statement = builder.mark(); + builder.advanceLexer(); + + if (parseExprInParenth(builder)) { + PsiBuilder.Marker body = block ? parseCodeBlock(builder, true) : parseStatement(builder); + if (body == null) { + error(builder, JavaPsiBundle.message(block ? "expected.lbrace" : "expected.statement")); + } + } + + done(statement, type, builder, myWhiteSpaceAndCommentSetHolder); + return statement; + } + + private boolean parseExprInParenth(PsiBuilder builder) { + if (!expect(builder, JavaTokenType.LPARENTH)) { + error(builder, JavaPsiBundle.message("expected.lparen")); + return false; + } + + PsiBuilder.Marker beforeExpr = builder.mark(); + PsiBuilder.Marker expr = myParser.getExpressionParser().parse(builder); + if (expr == null || builder.getTokenType() == JavaTokenType.SEMICOLON) { + beforeExpr.rollbackTo(); + error(builder, JavaPsiBundle.message("expected.expression")); + if (builder.getTokenType() != JavaTokenType.RPARENTH) { + return false; + } + } + else { + beforeExpr.drop(); + if (builder.getTokenType() != JavaTokenType.RPARENTH) { + error(builder, JavaPsiBundle.message("expected.rparen")); + return false; + } + } + + builder.advanceLexer(); + return true; + } + + private static boolean isStmtYieldToken(@NotNull PsiBuilder builder, IElementType tokenType) { + if (!(tokenType == JavaTokenType.IDENTIFIER && + JavaKeywords.YIELD.equals(builder.getTokenText()) && + JavaFeature.SWITCH_EXPRESSION.isSufficient(getLanguageLevel(builder)))) { + return false; + } + // we prefer to parse it as yield stmt wherever possible (even in incomplete syntax) + PsiBuilder.Marker maybeYieldStmt = builder.mark(); + builder.advanceLexer(); + IElementType tokenAfterYield = builder.getTokenType(); + if (tokenAfterYield == null || YIELD_STMT_INDICATOR_TOKENS.contains(tokenAfterYield)) { + maybeYieldStmt.rollbackTo(); + return true; + } + if (JavaTokenType.PLUSPLUS.equals(tokenAfterYield) || JavaTokenType.MINUSMINUS.equals(tokenAfterYield)) { + builder.advanceLexer(); + boolean isYieldStmt = !builder.getTokenType().equals(JavaTokenType.SEMICOLON); + maybeYieldStmt.rollbackTo(); + return isYieldStmt; + } + maybeYieldStmt.rollbackTo(); + return false; + } + + private static void skipQualifiedName(PsiBuilder builder) { + if (!expect(builder, JavaTokenType.IDENTIFIER)) return; + while (builder.getTokenType() == JavaTokenType.DOT && builder.lookAhead(1) == JavaTokenType.IDENTIFIER) { + advance(builder, 2); + } + } + + private static IElementType getLastToken(PsiBuilder builder) { + IElementType token; + int offset = -1; + while (JAVA_COMMENT_OR_WHITESPACE_BIT_SET.contains((token = builder.rawLookup(offset)))) offset--; + return token; + } + + private enum BraceMode { + TILL_FIRST, TILL_LAST } } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaDummyElement.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaDummyElement.java index 66580eecb582..926f0605c6c9 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaDummyElement.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/JavaDummyElement.java @@ -15,9 +15,12 @@ */ package com.intellij.psi.impl.source; -import com.intellij.lang.java.parser.BasicJavaParserUtil; +import com.intellij.java.syntax.element.lazyParser.IncompleteFragmentParsingException; +import com.intellij.lang.java.parser.JavaParserUtil; import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.impl.source.tree.FileElement; import com.intellij.psi.impl.source.tree.JavaElementTypeFactory; +import com.intellij.psi.impl.source.tree.TreeElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -25,17 +28,74 @@ import org.jetbrains.annotations.Nullable; * Dummy file element for using together with DummyHolder. * See {@link com.intellij.psi.impl.PsiJavaParserFacadeImpl} for details. */ -public class JavaDummyElement extends BasicJavaDummyElement { +public class JavaDummyElement extends FileElement { + private final JavaParserUtil.ParserWrapper myParser; + private final LanguageLevel myLanguageLevel; + private final boolean myConsumeAll; + private Throwable myParserError; + public JavaDummyElement(@Nullable CharSequence text, - BasicJavaParserUtil.@NotNull ParserWrapper parser, + JavaParserUtil.@NotNull ParserWrapper parser, @NotNull LanguageLevel level) { - super(text, parser, level, JavaElementTypeFactory.INSTANCE); + this(text, parser, level, JavaElementTypeFactory.INSTANCE, false); } public JavaDummyElement(@Nullable CharSequence text, - BasicJavaParserUtil.@NotNull ParserWrapper parser, + JavaParserUtil.@NotNull ParserWrapper parser, @NotNull LanguageLevel level, boolean consumeAll) { - super(text, parser, level, JavaElementTypeFactory.INSTANCE, consumeAll); + super(JavaElementTypeFactory.INSTANCE.getContainer().DUMMY_ELEMENT, text); + this.myParser = parser; + this.myLanguageLevel = level; + this.myConsumeAll = consumeAll; + } + + public JavaDummyElement(@Nullable CharSequence text, + @NotNull JavaParserUtil.ParserWrapper parser, + @NotNull LanguageLevel level, + @NotNull AbstractBasicJavaElementTypeFactory javaElementTypeFactory, + boolean consumeAll) { + super(javaElementTypeFactory.getContainer().DUMMY_ELEMENT, text); + myParser = parser; + myLanguageLevel = level; + myConsumeAll = consumeAll; + } + + public @NotNull JavaParserUtil.ParserWrapper getParser() { + return myParser; + } + + public boolean consumeAll() { + return myConsumeAll; + } + + public @NotNull LanguageLevel getLanguageLevel() { + return myLanguageLevel; + } + + @Override + public TreeElement getFirstChildNode() { + try { + return super.getFirstChildNode(); + } + catch (IncompleteFragmentParsingException e) { + myParserError = e; + return null; // masquerade parser errors + } + } + + @Override + public TreeElement getLastChildNode() { + try { + return super.getLastChildNode(); + } + catch (IncompleteFragmentParsingException e) { + myParserError = e; + return null; // masquerade parser errors + } + } + + public @Nullable Throwable getParserError() { + return myParserError; } } \ No newline at end of file diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/OldParserWhiteSpaceAndCommentSetHolder.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/OldParserWhiteSpaceAndCommentSetHolder.java new file mode 100644 index 000000000000..604dc72aa409 --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/OldParserWhiteSpaceAndCommentSetHolder.java @@ -0,0 +1,157 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.psi.impl.source; + +import com.intellij.lang.WhitespacesAndCommentsBinder; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.pom.java.JavaFeature; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.ParentAwareTokenSet; +import com.intellij.psi.tree.TokenSet; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import static com.intellij.psi.impl.source.BasicElementTypes.BASIC_JAVA_PLAIN_COMMENT_BIT_SET; +import static com.intellij.psi.impl.source.BasicJavaDocElementType.BASIC_DOC_COMMENT; +import static com.intellij.psi.tree.ParentAwareTokenSet.create; +import static com.intellij.psi.tree.ParentAwareTokenSet.orSet; + + +@Deprecated +public class OldParserWhiteSpaceAndCommentSetHolder { + public static final OldParserWhiteSpaceAndCommentSetHolder INSTANCE = new OldParserWhiteSpaceAndCommentSetHolder(); + private static final ParentAwareTokenSet PRECEDING_COMMENT_SET = + orSet(create(BasicJavaElementType.BASIC_MODULE, BasicJavaElementType.BASIC_IMPLICIT_CLASS), + BasicElementTypes.BASIC_FULL_MEMBER_BIT_SET); + + private static final ParentAwareTokenSet TRAILING_COMMENT_SET = + orSet(create(BasicJavaElementType.BASIC_PACKAGE_STATEMENT), BasicElementTypes.BASIC_IMPORT_STATEMENT_BASE_BIT_SET, + BasicElementTypes.BASIC_FULL_MEMBER_BIT_SET, BasicElementTypes.BASIC_JAVA_STATEMENT_BIT_SET); + + private OldParserWhiteSpaceAndCommentSetHolder() { + } + + private final WhitespacesAndCommentsBinder PRECEDING_COMMENT_BINDER_WITH_MARKDOWN = + new PrecedingWhitespacesAndCommentsBinder(false, true); + private final WhitespacesAndCommentsBinder SPECIAL_PRECEDING_COMMENT_BINDER_WITH_MARKDOWN = + new PrecedingWhitespacesAndCommentsBinder(true, true); + private final WhitespacesAndCommentsBinder PRECEDING_COMMENT_BINDER_WITHOUT_MARKDOWN = + new PrecedingWhitespacesAndCommentsBinder(false, false); + private final WhitespacesAndCommentsBinder SPECIAL_PRECEDING_COMMENT_BINDER_WITHOUT_MARKDOWN = + new PrecedingWhitespacesAndCommentsBinder(true, false); + private final WhitespacesAndCommentsBinder TRAILING_COMMENT_BINDER = new TrailingWhitespacesAndCommentsBinder(); + + public WhitespacesAndCommentsBinder getPrecedingCommentBinder(@NotNull LanguageLevel myLanguageLevel) { + return JavaFeature.MARKDOWN_COMMENT.isSufficient(myLanguageLevel) + ? PRECEDING_COMMENT_BINDER_WITH_MARKDOWN + : PRECEDING_COMMENT_BINDER_WITHOUT_MARKDOWN; + } + + public WhitespacesAndCommentsBinder getSpecialPrecedingCommentBinder(@NotNull LanguageLevel myLanguageLevel) { + return JavaFeature.MARKDOWN_COMMENT.isSufficient(myLanguageLevel) + ? SPECIAL_PRECEDING_COMMENT_BINDER_WITH_MARKDOWN + : SPECIAL_PRECEDING_COMMENT_BINDER_WITHOUT_MARKDOWN; + } + + public WhitespacesAndCommentsBinder getTrailingCommentBinder() { + return TRAILING_COMMENT_BINDER; + } + + public ParentAwareTokenSet getPrecedingCommentSet() { + return PRECEDING_COMMENT_SET; + } + + public ParentAwareTokenSet getTrailingCommentSet() { + return TRAILING_COMMENT_SET; + } + + + private static class PrecedingWhitespacesAndCommentsBinder implements WhitespacesAndCommentsBinder { + private final boolean myAfterEmptyImport; + private final boolean mySupportMarkdown; + + PrecedingWhitespacesAndCommentsBinder(final boolean afterImport, final boolean supportMarkdown) { + this.myAfterEmptyImport = afterImport; + this.mySupportMarkdown = supportMarkdown; + } + + @Override + public int getEdgePosition(final List tokens, final boolean atStreamEdge, final TokenTextGetter + getter) { + if (tokens.isEmpty()) return 0; + + // 1. bind doc comment + // now there are markdown comments. + if (mySupportMarkdown) { + //collect everything + for (int idx = tokens.size() - 1; idx >= 0; idx--) { + if (BasicJavaAstTreeUtil.is(tokens.get(idx), BASIC_DOC_COMMENT)) return idx; + } + } + else { + // To preserve previous orders, let's try to find the first non-markdown comment (and skip markdown comments). + // If there is no non-markdown, take the first markdown + for (int idx = tokens.size() - 1; idx >= 0; idx--) { + if (BasicJavaAstTreeUtil.is(tokens.get(idx), BASIC_DOC_COMMENT) && !isDocMarkdownComment(idx, getter)) { + return idx; + } + } + + for (int idx = tokens.size() - 1; idx >= 0; idx--) { + if (BasicJavaAstTreeUtil.is(tokens.get(idx), BASIC_DOC_COMMENT)) return idx; + } + } + + // 2. bind plain comments + int result = tokens.size(); + for (int idx = tokens.size() - 1; idx >= 0; idx--) { + final IElementType tokenType = tokens.get(idx); + if (TokenSet.WHITE_SPACE.contains(tokenType)) { + if (StringUtil.getLineBreakCount(getter.get(idx)) > 1) break; + } + else if (BASIC_JAVA_PLAIN_COMMENT_BIT_SET.contains(tokenType)) { + if (atStreamEdge || + (idx == 0 && myAfterEmptyImport) || + (idx > 0 && TokenSet.WHITE_SPACE.contains(tokens.get(idx - 1)) && StringUtil.containsLineBreak(getter.get(idx - 1)))) { + result = idx; + } + } + else { + break; + } + } + + return result; + } + + private static boolean isDocMarkdownComment(int idx, @NotNull TokenTextGetter getter) { + CharSequence sequence = getter.get(idx); + return sequence.length() >= 3 && "///".equals(sequence.subSequence(0, 3).toString()); + } + } + + private static class TrailingWhitespacesAndCommentsBinder implements WhitespacesAndCommentsBinder { + + @Override + public int getEdgePosition(final List tokens, final boolean atStreamEdge, final TokenTextGetter getter) { + if (tokens.isEmpty()) return 0; + + int result = 0; + for (int idx = 0; idx < tokens.size(); idx++) { + final IElementType tokenType = tokens.get(idx); + if (TokenSet.WHITE_SPACE.contains(tokenType)) { + if (StringUtil.containsLineBreak(getter.get(idx))) break; + } + else if (BASIC_JAVA_PLAIN_COMMENT_BIT_SET.contains(tokenType)) { + result = idx + 1; + } + else { + break; + } + } + + return result; + } + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/parser/OldJavaParsingTestConfigurator.java b/java/java-tests/testSrc/com/intellij/java/parser/OldJavaParsingTestConfigurator.java index 3c373b323af1..83957acd6523 100644 --- a/java/java-tests/testSrc/com/intellij/java/parser/OldJavaParsingTestConfigurator.java +++ b/java/java-tests/testSrc/com/intellij/java/parser/OldJavaParsingTestConfigurator.java @@ -8,7 +8,6 @@ import com.intellij.lang.ASTNode; import com.intellij.lang.LanguageASTFactory; import com.intellij.lang.PsiBuilder; import com.intellij.lang.java.JavaLanguage; -import com.intellij.lang.java.parser.BasicJavaParserUtil; import com.intellij.lang.java.parser.JavaParser; import com.intellij.lang.java.parser.JavaParserUtil; import com.intellij.lang.java.syntax.JavaElementTypeConverterExtension; @@ -70,7 +69,7 @@ public class OldJavaParsingTestConfigurator } @Override - public @Nullable SyntaxNode createFileSyntaxNode(@NotNull String text, BasicJavaParserUtil.@Nullable ParserWrapper parserWrapper) { + public @Nullable SyntaxNode createFileSyntaxNode(@NotNull String text, JavaParserUtil.@Nullable ParserWrapper parserWrapper) { return null; }