KtBlockExpression as lazy reparseable node (KT-13841)
Changed KtBlockExpression to support LazyReparseable behaviour Added LazyKtBlockExpressionTest Added new BlockWrapper delegations
This commit is contained in:
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin
|
||||||
|
|
||||||
|
import com.intellij.lang.ASTNode
|
||||||
|
import com.intellij.lang.Language
|
||||||
|
import com.intellij.lang.PsiBuilderFactory
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.tree.ICompositeElementType
|
||||||
|
import com.intellij.psi.tree.IErrorCounterReparseableElementType
|
||||||
|
import com.intellij.psi.tree.TokenSet
|
||||||
|
import org.jetbrains.kotlin.KtNodeTypes.BLOCK_CODE_FRAGMENT
|
||||||
|
import org.jetbrains.kotlin.KtNodeTypes.FUNCTION_LITERAL
|
||||||
|
import org.jetbrains.kotlin.KtNodeTypes.SCRIPT
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
|
import org.jetbrains.kotlin.lexer.KotlinLexer
|
||||||
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
import org.jetbrains.kotlin.parsing.KotlinParser
|
||||||
|
import org.jetbrains.kotlin.psi.KtBlockExpression
|
||||||
|
|
||||||
|
class BlockExpressionElementType : IErrorCounterReparseableElementType("BLOCK", KotlinLanguage.INSTANCE), ICompositeElementType {
|
||||||
|
|
||||||
|
override fun createCompositeNode() = KtBlockExpression(null)
|
||||||
|
|
||||||
|
override fun createNode(text: CharSequence?) = KtBlockExpression(text)
|
||||||
|
|
||||||
|
override fun isParsable(parent: ASTNode?, buffer: CharSequence, fileLanguage: Language, project: Project) =
|
||||||
|
fileLanguage == KotlinLanguage.INSTANCE &&
|
||||||
|
BlockExpressionElementType.isAllowedParentNode(parent) &&
|
||||||
|
BlockExpressionElementType.isReparseableBlock(buffer) &&
|
||||||
|
super.isParsable(buffer, fileLanguage, project)
|
||||||
|
|
||||||
|
override fun getErrorsCount(seq: CharSequence, fileLanguage: Language, project: Project) =
|
||||||
|
ElementTypeUtils.getKotlinBlockImbalanceCount(seq)
|
||||||
|
|
||||||
|
override fun parseContents(chameleon: ASTNode): ASTNode {
|
||||||
|
val project = chameleon.psi.project
|
||||||
|
val builder = PsiBuilderFactory.getInstance().createBuilder(
|
||||||
|
project, chameleon, null, KotlinLanguage.INSTANCE, chameleon.chars
|
||||||
|
)
|
||||||
|
|
||||||
|
return KotlinParser.parseBlockExpression(builder).firstChildNode
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private fun isAllowedParentNode(node: ASTNode?) =
|
||||||
|
node != null &&
|
||||||
|
SCRIPT != node.elementType &&
|
||||||
|
FUNCTION_LITERAL != node.elementType &&
|
||||||
|
BLOCK_CODE_FRAGMENT != node.elementType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this text is block but not a lambda, please refer to parsing rules!
|
||||||
|
@see [org.jetbrains.kotlin.parsing.KotlinExpressionParsing.parseFunctionLiteral]
|
||||||
|
*/
|
||||||
|
fun isReparseableBlock(blockText: CharSequence): Boolean {
|
||||||
|
|
||||||
|
fun advanceWhitespacesCheckIsEndOrArrow(lexer: KotlinLexer): Boolean {
|
||||||
|
lexer.advance()
|
||||||
|
while (lexer.tokenType != null && lexer.tokenType != KtTokens.EOF) {
|
||||||
|
if (lexer.tokenType == KtTokens.ARROW) return true
|
||||||
|
if (lexer.tokenType != KtTokens.WHITE_SPACE) return false
|
||||||
|
lexer.advance()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val lexer = KotlinLexer()
|
||||||
|
lexer.start(blockText)
|
||||||
|
|
||||||
|
// Try to parse a simple name list followed by an ARROW
|
||||||
|
// {a -> ...}
|
||||||
|
// {a, b -> ...}
|
||||||
|
// {(a, b) -> ... }
|
||||||
|
if (lexer.tokenType != KtTokens.LBRACE) return false
|
||||||
|
|
||||||
|
if (advanceWhitespacesCheckIsEndOrArrow(lexer)) return false
|
||||||
|
|
||||||
|
if (lexer.tokenType != KtTokens.COLON &&
|
||||||
|
lexer.tokenType != KtTokens.IDENTIFIER &&
|
||||||
|
lexer.tokenType != KtTokens.LPAR
|
||||||
|
) return true
|
||||||
|
|
||||||
|
val searchForRPAR = lexer.tokenType == KtTokens.LPAR
|
||||||
|
|
||||||
|
if (advanceWhitespacesCheckIsEndOrArrow(lexer)) return false
|
||||||
|
|
||||||
|
val preferParamsToExpressions = lexer.tokenType == KtTokens.COMMA || lexer.tokenType == KtTokens.COLON
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
|
||||||
|
if (lexer.tokenType == KtTokens.LBRACE) return true
|
||||||
|
if (lexer.tokenType == KtTokens.RBRACE) return !preferParamsToExpressions
|
||||||
|
|
||||||
|
if (searchForRPAR && lexer.tokenType == KtTokens.RPAR) {
|
||||||
|
return !advanceWhitespacesCheckIsEndOrArrow(lexer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (advanceWhitespacesCheckIsEndOrArrow(lexer)) return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin
|
||||||
|
|
||||||
|
import com.intellij.psi.tree.IErrorCounterReparseableElementType
|
||||||
|
import org.jetbrains.kotlin.lexer.KotlinLexer
|
||||||
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
|
||||||
|
object ElementTypeUtils {
|
||||||
|
@JvmStatic
|
||||||
|
fun getKotlinBlockImbalanceCount(seq: CharSequence): Int {
|
||||||
|
val lexer = KotlinLexer()
|
||||||
|
|
||||||
|
lexer.start(seq)
|
||||||
|
if (lexer.tokenType !== KtTokens.LBRACE) return IErrorCounterReparseableElementType.FATAL_ERROR
|
||||||
|
lexer.advance()
|
||||||
|
var balance = 1
|
||||||
|
while (lexer.tokenType != KtTokens.EOF) {
|
||||||
|
val type = lexer.tokenType ?: break
|
||||||
|
if (balance == 0) {
|
||||||
|
return IErrorCounterReparseableElementType.FATAL_ERROR
|
||||||
|
}
|
||||||
|
if (type === KtTokens.LBRACE) {
|
||||||
|
balance++
|
||||||
|
} else if (type === KtTokens.RBRACE) {
|
||||||
|
balance--
|
||||||
|
}
|
||||||
|
lexer.advance()
|
||||||
|
}
|
||||||
|
return balance
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -112,9 +112,10 @@ public interface KtNodeTypes {
|
|||||||
IElementType DO_WHILE = new KtNodeType("DO_WHILE", KtDoWhileExpression.class);
|
IElementType DO_WHILE = new KtNodeType("DO_WHILE", KtDoWhileExpression.class);
|
||||||
IElementType LOOP_RANGE = new KtNodeType("LOOP_RANGE", KtContainerNode.class);
|
IElementType LOOP_RANGE = new KtNodeType("LOOP_RANGE", KtContainerNode.class);
|
||||||
IElementType BODY = new KtNodeType("BODY", KtContainerNodeForControlStructureBody.class);
|
IElementType BODY = new KtNodeType("BODY", KtContainerNodeForControlStructureBody.class);
|
||||||
IElementType BLOCK = new KtNodeType("BLOCK", KtBlockExpression.class);
|
|
||||||
|
|
||||||
IElementType LAMBDA_EXPRESSION = new LambdaExpressionElementType();
|
IElementType BLOCK = new BlockExpressionElementType();
|
||||||
|
|
||||||
|
IElementType LAMBDA_EXPRESSION = new LambdaExpressionElementType();
|
||||||
|
|
||||||
IElementType FUNCTION_LITERAL = new KtNodeType("FUNCTION_LITERAL", KtFunctionLiteral.class);
|
IElementType FUNCTION_LITERAL = new KtNodeType("FUNCTION_LITERAL", KtFunctionLiteral.class);
|
||||||
IElementType ANNOTATED_EXPRESSION = new KtNodeType("ANNOTATED_EXPRESSION", KtAnnotatedExpression.class);
|
IElementType ANNOTATED_EXPRESSION = new KtNodeType("ANNOTATED_EXPRESSION", KtAnnotatedExpression.class);
|
||||||
|
|||||||
@@ -147,27 +147,7 @@ class LambdaExpressionElementType extends IErrorCounterReparseableElementType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getErrorsCount(CharSequence seq, Language fileLanguage, Project project) {
|
public int getErrorsCount(CharSequence seq, Language fileLanguage, Project project){
|
||||||
Lexer lexer = new KotlinLexer();
|
return ElementTypeUtils.getKotlinBlockImbalanceCount(seq);
|
||||||
|
|
||||||
lexer.start(seq);
|
|
||||||
if (lexer.getTokenType() != KtTokens.LBRACE) return IErrorCounterReparseableElementType.FATAL_ERROR;
|
|
||||||
lexer.advance();
|
|
||||||
int balance = 1;
|
|
||||||
while (true) {
|
|
||||||
IElementType type = lexer.getTokenType();
|
|
||||||
if (type == null) break;
|
|
||||||
if (balance == 0) {
|
|
||||||
return IErrorCounterReparseableElementType.FATAL_ERROR;
|
|
||||||
}
|
|
||||||
if (type == KtTokens.LBRACE) {
|
|
||||||
balance++;
|
|
||||||
}
|
|
||||||
else if (type == KtTokens.RBRACE) {
|
|
||||||
balance--;
|
|
||||||
}
|
|
||||||
lexer.advance();
|
|
||||||
}
|
|
||||||
return balance;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,10 @@
|
|||||||
package org.jetbrains.kotlin.parsing;
|
package org.jetbrains.kotlin.parsing;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.intellij.lang.ASTNode;
|
||||||
|
import com.intellij.lang.Language;
|
||||||
import com.intellij.lang.PsiBuilder;
|
import com.intellij.lang.PsiBuilder;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.psi.tree.IElementType;
|
import com.intellij.psi.tree.IElementType;
|
||||||
import com.intellij.psi.tree.TokenSet;
|
import com.intellij.psi.tree.TokenSet;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -1093,6 +1096,8 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* If it has no ->, it's a block, otherwise a function literal
|
* If it has no ->, it's a block, otherwise a function literal
|
||||||
|
*
|
||||||
|
* Please update {@link org.jetbrains.kotlin.BlockExpressionElementType#isParsable(ASTNode, CharSequence, Language, Project)} if any changes occurs!
|
||||||
*/
|
*/
|
||||||
public void parseFunctionLiteral(boolean preferBlock, boolean collapse) {
|
public void parseFunctionLiteral(boolean preferBlock, boolean collapse) {
|
||||||
assert _at(LBRACE);
|
assert _at(LBRACE);
|
||||||
@@ -1138,7 +1143,7 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (collapse) {
|
if (collapse) {
|
||||||
advanceLambdaBlock();
|
myKotlinParsing.advanceBalancedBlock();
|
||||||
literal.done(FUNCTION_LITERAL);
|
literal.done(FUNCTION_LITERAL);
|
||||||
literalExpression.collapse(LAMBDA_EXPRESSION);
|
literalExpression.collapse(LAMBDA_EXPRESSION);
|
||||||
}
|
}
|
||||||
@@ -1157,24 +1162,6 @@ public class KotlinExpressionParsing extends AbstractKotlinParsing {
|
|||||||
myBuilder.restoreNewlinesState();
|
myBuilder.restoreNewlinesState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void advanceLambdaBlock() {
|
|
||||||
int braceCount = 1;
|
|
||||||
while (!eof()) {
|
|
||||||
if (_at(LBRACE)) {
|
|
||||||
braceCount++;
|
|
||||||
}
|
|
||||||
else if (_at(RBRACE)) {
|
|
||||||
braceCount--;
|
|
||||||
}
|
|
||||||
|
|
||||||
advance();
|
|
||||||
|
|
||||||
if (braceCount == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean rollbackOrDropAt(PsiBuilder.Marker rollbackMarker, IElementType dropAt) {
|
private boolean rollbackOrDropAt(PsiBuilder.Marker rollbackMarker, IElementType dropAt) {
|
||||||
if (at(dropAt)) {
|
if (at(dropAt)) {
|
||||||
advance(); // dropAt
|
advance(); // dropAt
|
||||||
|
|||||||
@@ -80,4 +80,11 @@ public class KotlinParser implements PsiParser {
|
|||||||
ktParsing.parseLambdaExpression();
|
ktParsing.parseLambdaExpression();
|
||||||
return psiBuilder.getTreeBuilt();
|
return psiBuilder.getTreeBuilt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static ASTNode parseBlockExpression(PsiBuilder psiBuilder) {
|
||||||
|
KotlinParsing ktParsing = KotlinParsing.createForTopLevel(new SemanticWhitespaceAwarePsiBuilderImpl(psiBuilder));
|
||||||
|
ktParsing.parseBlockExpression();
|
||||||
|
return psiBuilder.getTreeBuilt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,6 +162,10 @@ public class KotlinParsing extends AbstractKotlinParsing {
|
|||||||
myExpressionParsing.parseFunctionLiteral(/* preferBlock = */ false, /* collapse = */false);
|
myExpressionParsing.parseFunctionLiteral(/* preferBlock = */ false, /* collapse = */false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void parseBlockExpression() {
|
||||||
|
parseBlock(/* collapse = */ false);
|
||||||
|
}
|
||||||
|
|
||||||
void parseScript() {
|
void parseScript() {
|
||||||
PsiBuilder.Marker fileMarker = mark();
|
PsiBuilder.Marker fileMarker = mark();
|
||||||
|
|
||||||
@@ -1725,17 +1729,50 @@ public class KotlinParsing extends AbstractKotlinParsing {
|
|||||||
* ;
|
* ;
|
||||||
*/
|
*/
|
||||||
void parseBlock() {
|
void parseBlock() {
|
||||||
PsiBuilder.Marker block = mark();
|
parseBlock(/*collapse*/ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseBlock(boolean collapse) {
|
||||||
|
|
||||||
|
PsiBuilder.Marker lazyBlock = mark();
|
||||||
|
|
||||||
myBuilder.enableNewlines();
|
myBuilder.enableNewlines();
|
||||||
|
|
||||||
expect(LBRACE, "Expecting '{' to open a block");
|
expect(LBRACE, "Expecting '{' to open a block");
|
||||||
|
|
||||||
myExpressionParsing.parseStatements();
|
if(collapse){
|
||||||
|
advanceBalancedBlock();
|
||||||
|
}else{
|
||||||
|
myExpressionParsing.parseStatements();
|
||||||
|
expect(RBRACE, "Expecting '}'");
|
||||||
|
}
|
||||||
|
|
||||||
expect(RBRACE, "Expecting '}'");
|
|
||||||
myBuilder.restoreNewlinesState();
|
myBuilder.restoreNewlinesState();
|
||||||
|
|
||||||
block.done(BLOCK);
|
if(collapse){
|
||||||
|
lazyBlock.collapse(BLOCK);
|
||||||
|
}else{
|
||||||
|
lazyBlock.done(BLOCK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void advanceBalancedBlock() {
|
||||||
|
|
||||||
|
int braceCount = 1;
|
||||||
|
while (!eof()) {
|
||||||
|
if (_at(LBRACE)) {
|
||||||
|
braceCount++;
|
||||||
|
}
|
||||||
|
else if (_at(RBRACE)) {
|
||||||
|
braceCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance();
|
||||||
|
|
||||||
|
if (braceCount == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -16,21 +16,32 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.psi;
|
package org.jetbrains.kotlin.psi;
|
||||||
|
|
||||||
import com.intellij.lang.ASTNode;
|
import com.intellij.lang.Language;
|
||||||
import com.intellij.openapi.util.TextRange;
|
import com.intellij.openapi.util.TextRange;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.*;
|
||||||
import com.intellij.psi.PsiModifiableCodeBlock;
|
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
|
||||||
|
import com.intellij.psi.impl.source.tree.CompositeElement;
|
||||||
|
import com.intellij.psi.impl.source.tree.LazyParseablePsiElement;
|
||||||
|
import com.intellij.psi.util.PsiUtilCore;
|
||||||
|
import com.intellij.util.IncorrectOperationException;
|
||||||
import kotlin.annotations.jvm.ReadOnly;
|
import kotlin.annotations.jvm.ReadOnly;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinLanguage;
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens;
|
import org.jetbrains.kotlin.lexer.KtTokens;
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class KtBlockExpression extends KtExpressionImpl implements KtStatementExpression, PsiModifiableCodeBlock {
|
import static org.jetbrains.kotlin.KtNodeTypes.BLOCK;
|
||||||
public KtBlockExpression(@NotNull ASTNode node) {
|
|
||||||
super(node);
|
public class KtBlockExpression extends LazyParseablePsiElement implements KtElement, KtExpression, KtStatementExpression, PsiModifiableCodeBlock {
|
||||||
|
|
||||||
|
public KtBlockExpression(@Nullable CharSequence text) {
|
||||||
|
super(BLOCK, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -39,11 +50,92 @@ public class KtBlockExpression extends KtExpressionImpl implements KtStatementEx
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Language getLanguage() {
|
||||||
|
return KotlinLanguage.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getNode().getElementType().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public KtFile getContainingKtFile() {
|
||||||
|
return PsiUtilsKt.getContainingKtFile(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <D> void acceptChildren(@NotNull KtVisitor<Void, D> visitor, D data) {
|
||||||
|
KtPsiUtil.visitChildren(this, visitor, data);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <R, D> R accept(@NotNull KtVisitor<R, D> visitor, D data) {
|
public <R, D> R accept(@NotNull KtVisitor<R, D> visitor, D data) {
|
||||||
return visitor.visitBlockExpression(this, data);
|
return visitor.visitBlockExpression(this, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public final void accept(@NotNull PsiElementVisitor visitor) {
|
||||||
|
if (visitor instanceof KtVisitor) {
|
||||||
|
accept((KtVisitor) visitor, null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
visitor.visitElement(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete() throws IncorrectOperationException {
|
||||||
|
KtElementUtilsKt.deleteSemicolon(this);
|
||||||
|
super.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public PsiReference getReference() {
|
||||||
|
PsiReference[] references = getReferences();
|
||||||
|
if (references.length == 1) return references[0];
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public PsiElement[] getChildren() {
|
||||||
|
PsiElement psiChild = getFirstChild();
|
||||||
|
|
||||||
|
List<PsiElement> result = null;
|
||||||
|
while (psiChild != null) {
|
||||||
|
if (psiChild.getNode() instanceof CompositeElement) {
|
||||||
|
if(result == null) result = new ArrayList<>();
|
||||||
|
result.add(psiChild);
|
||||||
|
}
|
||||||
|
psiChild = psiChild.getNextSibling();
|
||||||
|
}
|
||||||
|
return result == null ? PsiElement.EMPTY_ARRAY : PsiUtilCore.toPsiElementArray(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public PsiReference[] getReferences() {
|
||||||
|
return ReferenceProvidersRegistry.getReferencesFromProviders(this, PsiReferenceService.Hints.NO_HINTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public KtElement getPsiOrParent() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PsiElement getParent() {
|
||||||
|
PsiElement substitute = KtPsiUtilKt.getParentSubstitute(this);
|
||||||
|
return substitute != null ? substitute : super.getParent();
|
||||||
|
}
|
||||||
|
|
||||||
@ReadOnly
|
@ReadOnly
|
||||||
@NotNull
|
@NotNull
|
||||||
public List<KtExpression> getStatements() {
|
public List<KtExpression> getStatements() {
|
||||||
@@ -58,11 +150,11 @@ public class KtBlockExpression extends KtExpressionImpl implements KtStatementEx
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public PsiElement getRBrace() {
|
public PsiElement getRBrace() {
|
||||||
return findChildByType(KtTokens.RBRACE);
|
return findPsiChildByType(KtTokens.RBRACE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public PsiElement getLBrace() {
|
public PsiElement getLBrace() {
|
||||||
return findChildByType(KtTokens.LBRACE);
|
return findPsiChildByType(KtTokens.LBRACE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.kotlin.KtNodeTypes;
|
import org.jetbrains.kotlin.KtNodeTypes;
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens;
|
import org.jetbrains.kotlin.lexer.KtTokens;
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -76,13 +77,7 @@ public class KtLambdaExpression extends LazyParseablePsiElement implements KtExp
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public KtFile getContainingKtFile() {
|
public KtFile getContainingKtFile() {
|
||||||
PsiFile file = getContainingFile();
|
return PsiUtilsKt.getContainingKtFile(this);
|
||||||
if(!(file instanceof KtFile)) {
|
|
||||||
String fileString = (file != null && file.isValid()) ? file.getText() : "";
|
|
||||||
throw new IllegalStateException("KtElement not inside KtFile: " + file + fileString +
|
|
||||||
"for element " + this + " of type " + this.getClass() + " node = " + getNode());
|
|
||||||
}
|
|
||||||
return (KtFile) file;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.intellij.openapi.project.Project
|
|||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.psi.PsiComment
|
import com.intellij.psi.PsiComment
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiFile
|
||||||
import com.intellij.psi.PsiFileFactory
|
import com.intellij.psi.PsiFileFactory
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
import com.intellij.util.LocalTimeCounter
|
import com.intellij.util.LocalTimeCounter
|
||||||
@@ -853,7 +854,8 @@ class KtPsiFactory @JvmOverloads constructor(private val project: Project, val m
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class BlockWrapper(fakeBlockExpression: KtBlockExpression, private val expression: KtExpression) :
|
private class BlockWrapper(fakeBlockExpression: KtBlockExpression, private val expression: KtExpression) :
|
||||||
KtBlockExpression(fakeBlockExpression.node), KtPsiUtil.KtExpressionWrapper {
|
KtBlockExpression(fakeBlockExpression.text), KtPsiUtil.KtExpressionWrapper {
|
||||||
|
|
||||||
override fun getStatements(): List<KtExpression> {
|
override fun getStatements(): List<KtExpression> {
|
||||||
return listOf(expression)
|
return listOf(expression)
|
||||||
}
|
}
|
||||||
@@ -861,5 +863,13 @@ class KtPsiFactory @JvmOverloads constructor(private val project: Project, val m
|
|||||||
override fun getBaseExpression(): KtExpression {
|
override fun getBaseExpression(): KtExpression {
|
||||||
return expression
|
return expression
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getParent(): PsiElement = expression.parent
|
||||||
|
|
||||||
|
override fun getPsiOrParent(): KtElement = expression.psiOrParent
|
||||||
|
|
||||||
|
override fun getContainingKtFile() = expression.containingKtFile
|
||||||
|
|
||||||
|
override fun getContainingFile(): PsiFile = expression.containingFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.intellij.injected.editor.VirtualFileWindow
|
|||||||
import com.intellij.lang.ASTNode
|
import com.intellij.lang.ASTNode
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.psi.*
|
import com.intellij.psi.*
|
||||||
|
import com.intellij.psi.impl.source.tree.LazyParseablePsiElement
|
||||||
import com.intellij.psi.impl.source.tree.TreeUtil
|
import com.intellij.psi.impl.source.tree.TreeUtil
|
||||||
import com.intellij.psi.search.PsiSearchScopeUtil
|
import com.intellij.psi.search.PsiSearchScopeUtil
|
||||||
import com.intellij.psi.search.SearchScope
|
import com.intellij.psi.search.SearchScope
|
||||||
@@ -380,7 +381,7 @@ fun PsiElement.getElementTextWithContext(): String {
|
|||||||
|
|
||||||
// Find parent for element among file children
|
// Find parent for element among file children
|
||||||
val topLevelElement = PsiTreeUtil.findFirstParent(this, { it.parent is PsiFile })
|
val topLevelElement = PsiTreeUtil.findFirstParent(this, { it.parent is PsiFile })
|
||||||
?: throw AssertionError("For non-file element we should always be able to find parent in file children")
|
?: throw AssertionError("For non-file element we should always be able to find parent in file children")
|
||||||
|
|
||||||
val startContextOffset = topLevelElement.startOffset
|
val startContextOffset = topLevelElement.startOffset
|
||||||
val elementContextOffset = textRange.startOffset
|
val elementContextOffset = textRange.startOffset
|
||||||
@@ -466,3 +467,13 @@ fun ASTNode.closestPsiElement(): PsiElement? {
|
|||||||
}
|
}
|
||||||
return node.psi
|
return node.psi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun LazyParseablePsiElement.getContainingKtFile(): KtFile {
|
||||||
|
|
||||||
|
val file = this.containingFile
|
||||||
|
|
||||||
|
if (file is KtFile) return file
|
||||||
|
|
||||||
|
val fileString = if (file != null && file.isValid) file.text else ""
|
||||||
|
throw IllegalStateException("KtElement not inside KtFile: $file with text \"$fileString\" for element $this of type ${this::class.java} node = ${this.node}")
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -108,7 +108,7 @@ class CodeFragmentAnalyzer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is KtSecondaryConstructor -> {
|
is KtSecondaryConstructor -> {
|
||||||
val expression = context.bodyExpression ?: context.getDelegationCall().calleeExpression
|
val expression = (context.bodyExpression ?: context.getDelegationCall().calleeExpression) as? KtExpression
|
||||||
if (expression != null) {
|
if (expression != null) {
|
||||||
bindingContext = resolutionFactory(expression)
|
bindingContext = resolutionFactory(expression)
|
||||||
scope = bindingContext[BindingContext.LEXICAL_SCOPE, expression]
|
scope = bindingContext[BindingContext.LEXICAL_SCOPE, expression]
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
|
||||||
* that can be found in the license/LICENSE.txt file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.editor
|
|
||||||
|
|
||||||
import com.intellij.psi.PsiDocumentManager
|
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
|
||||||
import com.intellij.testFramework.EditorTestUtil
|
|
||||||
import com.intellij.testFramework.EditorTestUtil.*
|
|
||||||
import com.intellij.testFramework.LightPlatformTestCase
|
|
||||||
import com.intellij.testFramework.LightProjectDescriptor
|
|
||||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCaseBase
|
|
||||||
import org.jetbrains.kotlin.psi.KtLambdaExpression
|
|
||||||
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
|
|
||||||
import org.junit.Assert
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
@RunWith(JUnit3WithIdeaConfigurationRunner::class)
|
|
||||||
class LazyElementTypeTest : KotlinLightCodeInsightFixtureTestCaseBase() {
|
|
||||||
fun testSplitArrow() = reparse("val t = { a: Int -<caret>> }", ' ')
|
|
||||||
fun testDeleteArrow() = reparse("val t = { a: Int -><caret> }", BACKSPACE_FAKE_CHAR)
|
|
||||||
|
|
||||||
fun testReformatNearArrow() = noReparse("val t = { a: Int<caret>-> }", ' ')
|
|
||||||
fun testChangeAfterArrow() = noReparse("val t = { a: Int -> <caret> }", 'a')
|
|
||||||
fun testDeleteIrrelevantArrow() = noReparse("val t = { a: Int -> (1..3).filter { b -><caret> b > 2 } }", BACKSPACE_FAKE_CHAR)
|
|
||||||
fun testReformatNearLambdaStart() = noReparse("val t = {<caret>a: Int -> }", ' ')
|
|
||||||
fun testNoArrow() = noReparse("val t = { <caret> }", 'a')
|
|
||||||
|
|
||||||
fun testAfterRemovingParameterComma() = reparse(inIf("{t,<caret>}"), BACKSPACE_FAKE_CHAR)
|
|
||||||
fun testAfterRemovingNoParameterComma() = noReparse(inIf("{,<caret>}"), BACKSPACE_FAKE_CHAR)
|
|
||||||
fun testAfterRemovingNotLastParameterComma() = noReparse(inIf("{a, b,<caret>}"), BACKSPACE_FAKE_CHAR)
|
|
||||||
fun testAfterRemovingSecondParameter() = noReparse(inIf("{a,b<caret>}"), BACKSPACE_FAKE_CHAR)
|
|
||||||
fun testAfterFirstParameterRenamed() = noReparse(inIf("{a<caret>,}"), 'b')
|
|
||||||
|
|
||||||
fun testAfterRemovingFirstParameterWithOther() = reparse(inIf("{a<caret>,b}"), BACKSPACE_FAKE_CHAR)
|
|
||||||
fun testAfterRemovingFirstParameter() = reparse(inIf("{a<caret>,}"), BACKSPACE_FAKE_CHAR)
|
|
||||||
fun testAfterTypeComma() = reparse(inIf("{a<caret>}"), ',')
|
|
||||||
|
|
||||||
fun reparse(text: String, char: Char): Unit = doTest(text, char, true)
|
|
||||||
fun noReparse(text: String, char: Char): Unit = doTest(text, char, false)
|
|
||||||
|
|
||||||
fun doTest(text: String, char: Char, reparse: Boolean) {
|
|
||||||
val file = myFixture.configureByText("a.kt", text.trimMargin())
|
|
||||||
|
|
||||||
val lambdaExpressionBefore = PsiTreeUtil.findChildOfType(file, KtLambdaExpression::class.java)
|
|
||||||
|
|
||||||
performTypingAction(myFixture.editor, char)
|
|
||||||
PsiDocumentManager.getInstance(LightPlatformTestCase.getProject()).commitDocument(myFixture.getDocument(file))
|
|
||||||
|
|
||||||
val lambdaExpressionAfter = PsiTreeUtil.findChildOfType(file, KtLambdaExpression::class.java)
|
|
||||||
|
|
||||||
val actualReparse = lambdaExpressionAfter != lambdaExpressionBefore
|
|
||||||
|
|
||||||
Assert.assertEquals("Lazy element behaviour was unexpected", reparse, actualReparse)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun inIf(lambda: String) = "val t: Unit = if (true) $lambda else {}"
|
|
||||||
|
|
||||||
override fun getProjectDescriptor() = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.editor
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiDocumentManager
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import com.intellij.testFramework.EditorTestUtil.*
|
||||||
|
import com.intellij.testFramework.LightPlatformTestCase
|
||||||
|
import com.intellij.testFramework.LightProjectDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCaseBase
|
||||||
|
import org.junit.Assert
|
||||||
|
|
||||||
|
abstract class LazyElementTypeTestBase<T>(private val lazyElementClass: Class<T>) :
|
||||||
|
KotlinLightCodeInsightFixtureTestCaseBase() where T : PsiElement {
|
||||||
|
|
||||||
|
protected fun reparse(text: String, char: Char): Unit = doTest(text, char, true)
|
||||||
|
protected fun noReparse(text: String, char: Char): Unit = doTest(text, char, false)
|
||||||
|
|
||||||
|
fun doTest(text: String, char: Char, reparse: Boolean) {
|
||||||
|
val file = myFixture.configureByText("a.kt", text.trimMargin())
|
||||||
|
|
||||||
|
val expressionBefore = PsiTreeUtil.findChildOfType(file, lazyElementClass)
|
||||||
|
|
||||||
|
performTypingAction(myFixture.editor, char)
|
||||||
|
PsiDocumentManager.getInstance(LightPlatformTestCase.getProject()).commitDocument(myFixture.getDocument(file))
|
||||||
|
|
||||||
|
val expressionAfter = PsiTreeUtil.findChildOfType(file, lazyElementClass)
|
||||||
|
|
||||||
|
val actualReparse = expressionAfter != expressionBefore
|
||||||
|
|
||||||
|
Assert.assertEquals("Lazy element behaviour was unexpected", reparse, actualReparse)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun inIf(then: String) = "val t: Unit = if (true) $then else {}"
|
||||||
|
|
||||||
|
override fun getProjectDescriptor() = LightProjectDescriptor.EMPTY_PROJECT_DESCRIPTOR
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.editor
|
||||||
|
|
||||||
|
import com.intellij.testFramework.EditorTestUtil
|
||||||
|
import org.jetbrains.kotlin.psi.KtBlockExpression
|
||||||
|
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(JUnit3WithIdeaConfigurationRunner::class)
|
||||||
|
class LazyKtBlockExpressionTest : LazyElementTypeTestBase<KtBlockExpression>(KtBlockExpression::class.java) {
|
||||||
|
|
||||||
|
fun testSimpleReparse() = noReparse(inIf(" { a<caret>b }"), 'c')
|
||||||
|
|
||||||
|
fun testSimpleNotReparse() = reparse(inIf(" { a-<caret>b }"), '>')
|
||||||
|
|
||||||
|
fun testSplitArrow() = reparse(inIf("{ a: Int -<caret>> }"), ' ')
|
||||||
|
|
||||||
|
fun testDeleteArrow() = reparse(inIf("{ a: Int -<caret>> }"), EditorTestUtil.BACKSPACE_FAKE_CHAR)
|
||||||
|
|
||||||
|
fun testImbalance1() = noReparse(inIf(" { {}<caret> }"), EditorTestUtil.BACKSPACE_FAKE_CHAR)
|
||||||
|
|
||||||
|
fun testImbalance2() = noReparse(inIf(" { }<caret>"), EditorTestUtil.BACKSPACE_FAKE_CHAR)
|
||||||
|
|
||||||
|
fun testBlockWithArrowInside() = noReparse(inIf(" { { a: Int -<caret> a } }"), '>')
|
||||||
|
|
||||||
|
fun testBlockWithArrowInside2() = noReparse(inIf(" { } a: Int -<caret> a } }"), '>')
|
||||||
|
|
||||||
|
fun testBlockWithCommaAsLambdaArgument() = reparse(inIf(" { a<caret> }"), ',')
|
||||||
|
|
||||||
|
fun testBlockWithCommaAsBlockContent() = noReparse(inIf(" { a.b<caret> c.d = 3 }"), ',')
|
||||||
|
|
||||||
|
fun testBlockWithCommaAsBlockContent2() = noReparse(inIf(" { a(b<caret> c) }"), ',')
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.editor
|
||||||
|
|
||||||
|
import com.intellij.testFramework.EditorTestUtil
|
||||||
|
import org.jetbrains.kotlin.psi.KtLambdaExpression
|
||||||
|
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(JUnit3WithIdeaConfigurationRunner::class)
|
||||||
|
class LazyKtLambdaExpressionTest : LazyElementTypeTestBase<KtLambdaExpression>(KtLambdaExpression::class.java) {
|
||||||
|
fun testSplitArrow() = reparse("val t = { a: Int -<caret>> }", ' ')
|
||||||
|
fun testDeleteArrow() = reparse("val t = { a: Int -><caret> }", EditorTestUtil.BACKSPACE_FAKE_CHAR)
|
||||||
|
|
||||||
|
fun testReformatNearArrow() = noReparse("val t = { a: Int<caret>-> }", ' ')
|
||||||
|
fun testChangeAfterArrow() = noReparse("val t = { a: Int -> <caret> }", 'a')
|
||||||
|
fun testDeleteIrrelevantArrow() = noReparse(
|
||||||
|
"val t = { a: Int -> (1..3).filter { b -><caret> b > 2 } }",
|
||||||
|
EditorTestUtil.BACKSPACE_FAKE_CHAR
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testReformatNearLambdaStart() = noReparse("val t = {<caret>a: Int -> }", ' ')
|
||||||
|
fun testNoArrow() = noReparse("val t = { <caret> }", 'a')
|
||||||
|
|
||||||
|
fun testAfterRemovingParameterComma() = reparse(inIf("{t,<caret>}"), EditorTestUtil.BACKSPACE_FAKE_CHAR)
|
||||||
|
fun testAfterRemovingNoParameterComma() = noReparse(inIf("{,<caret>}"), EditorTestUtil.BACKSPACE_FAKE_CHAR)
|
||||||
|
fun testAfterRemovingNotLastParameterComma() = noReparse(inIf("{a, b,<caret>}"), EditorTestUtil.BACKSPACE_FAKE_CHAR)
|
||||||
|
fun testAfterRemovingSecondParameter() = noReparse(inIf("{a,b<caret>}"), EditorTestUtil.BACKSPACE_FAKE_CHAR)
|
||||||
|
fun testAfterFirstParameterRenamed() = noReparse(inIf("{a<caret>,}"), 'b')
|
||||||
|
|
||||||
|
fun testAfterRemovingFirstParameterWithOther() = reparse(inIf("{a<caret>,b}"), EditorTestUtil.BACKSPACE_FAKE_CHAR)
|
||||||
|
fun testAfterRemovingFirstParameter() = reparse(inIf("{a<caret>,}"), EditorTestUtil.BACKSPACE_FAKE_CHAR)
|
||||||
|
fun testAfterTypeComma() = reparse(inIf("{a<caret>}"), ',')
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user