Detection of semicolon between enum entries together with the correct delimiter quick fix for this case.

Error message reported directly on semicolon.
This commit is contained in:
Mikhail Glukhikh
2015-05-12 15:31:04 +03:00
parent edd269f5ff
commit 983339e1c9
19 changed files with 387 additions and 8 deletions
@@ -622,7 +622,7 @@ public class JetParsing extends AbstractJetParsing {
public enum NameParsingMode {
REQUIRED,
ALLOWED,
PROHIBITED;
PROHIBITED
}
/*
@@ -771,8 +771,29 @@ public class JetParsing extends AbstractJetParsing {
if (!parseEnumEntry()) {
break;
}
// TODO: syntax without COMMA is deprecated (only last entry is an exception), KT-7605
consumeIf(COMMA);
// TODO: syntax with SEMICOLON between enum entries is deprecated, KT-7605
if (at(SEMICOLON)) {
// Semicolon can be legally here only if member follows
PsiBuilder.Marker temp = mark();
advance(); // SEMICOLON
ModifierDetector detector = new ModifierDetector();
parseModifierList(detector, ONLY_ESCAPED_REGULAR_ANNOTATIONS);
if (!atSet(SOFT_KEYWORDS_AT_MEMBER_START) && at(IDENTIFIER)) {
// Otherwise it's old syntax that's not supported
temp.rollbackTo();
// Despite of the error, try to restore and parse next enum entry
temp = mark();
advance(); // SEMICOLON
temp.error("Expecting ','");
}
else {
temp.rollbackTo();
}
}
else {
// TODO: syntax without COMMA is deprecated (only last entry is an exception), KT-7605
consumeIf(COMMA);
}
}
}
@@ -903,10 +924,7 @@ public class JetParsing extends AbstractJetParsing {
private IElementType parseMemberDeclarationRest(boolean isEnum, boolean isDefault) {
IElementType keywordToken = tt();
IElementType declType = null;
if (keywordToken == CLASS_KEYWORD) {
declType = parseClass(isEnum);
}
else if (keywordToken == TRAIT_KEYWORD || keywordToken == INTERFACE_KEYWORD) {
if (keywordToken == CLASS_KEYWORD || keywordToken == TRAIT_KEYWORD || keywordToken == INTERFACE_KEYWORD) {
declType = parseClass(isEnum);
}
else if (keywordToken == FUN_KEYWORD) {
@@ -0,0 +1,10 @@
enum class MyEnum {
// Should be comma instead of semicolon
<!ENUM_ENTRY_USES_DEPRECATED_OR_NO_DELIMITER!>A<!><!SYNTAX!>;<!>
B,
<!ENUM_ENTRY_USES_DEPRECATED_OR_NO_DELIMITER!>C<!><!SYNTAX!>;<!>
// Semicolon missed, comma is optional
<!ENUM_ENTRY_USES_DEPRECATED_OR_NO_DELIMITER!>D<!>,
fun foo() = 0
}
@@ -0,0 +1,60 @@
package
internal final enum class MyEnum : kotlin.Enum<MyEnum> {
public enum entry A : MyEnum {
private constructor A()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
internal final override /*1*/ /*fake_override*/ fun foo(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry B : MyEnum {
private constructor B()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
internal final override /*1*/ /*fake_override*/ fun foo(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry C : MyEnum {
private constructor C()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
internal final override /*1*/ /*fake_override*/ fun foo(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry D : MyEnum {
private constructor D()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
internal final override /*1*/ /*fake_override*/ fun foo(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
private constructor MyEnum()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
internal final fun foo(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
// Static members
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): MyEnum
public final /*synthesized*/ fun values(): kotlin.Array<MyEnum>
}
@@ -0,0 +1,8 @@
enum class MyEnum {
<!ENUM_ENTRY_USES_DEPRECATED_OR_NO_DELIMITER!>A<!><!SYNTAX!>;<!>
<!ENUM_ENTRY_USES_DEPRECATED_OR_NO_DELIMITER!>B<!><!SYNTAX!>;<!>
<!ENUM_ENTRY_USES_DEPRECATED_OR_NO_DELIMITER!>C<!><!SYNTAX!>;<!>
<!ENUM_ENTRY_USES_DEPRECATED_OR_NO_DELIMITER!>D<!>
fun foo() = 0
}
@@ -0,0 +1,60 @@
package
internal final enum class MyEnum : kotlin.Enum<MyEnum> {
public enum entry A : MyEnum {
private constructor A()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
internal final override /*1*/ /*fake_override*/ fun foo(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry B : MyEnum {
private constructor B()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
internal final override /*1*/ /*fake_override*/ fun foo(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry C : MyEnum {
private constructor C()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
internal final override /*1*/ /*fake_override*/ fun foo(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public enum entry D : MyEnum {
private constructor D()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
internal final override /*1*/ /*fake_override*/ fun foo(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
private constructor MyEnum()
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: MyEnum): kotlin.Int
public final override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
internal final fun foo(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
// Static members
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): MyEnum
public final /*synthesized*/ fun values(): kotlin.Array<MyEnum>
}
@@ -0,0 +1,6 @@
enum class Color {
NORTH;
SOUTH;
WEST;
EAST
}
@@ -0,0 +1,37 @@
JetFile: EnumSemicolonBetween.kt
PACKAGE_DIRECTIVE
<empty list>
CLASS
MODIFIER_LIST
PsiElement(enum)('enum')
PsiWhiteSpace(' ')
PsiElement(class)('class')
PsiWhiteSpace(' ')
PsiElement(IDENTIFIER)('Color')
PsiWhiteSpace(' ')
CLASS_BODY
PsiElement(LBRACE)('{')
PsiWhiteSpace('\n ')
ENUM_ENTRY
OBJECT_DECLARATION_NAME
PsiElement(IDENTIFIER)('NORTH')
PsiErrorElement:Expecting ','
PsiElement(SEMICOLON)(';')
PsiWhiteSpace('\n ')
ENUM_ENTRY
OBJECT_DECLARATION_NAME
PsiElement(IDENTIFIER)('SOUTH')
PsiErrorElement:Expecting ','
PsiElement(SEMICOLON)(';')
PsiWhiteSpace('\n ')
ENUM_ENTRY
OBJECT_DECLARATION_NAME
PsiElement(IDENTIFIER)('WEST')
PsiErrorElement:Expecting ','
PsiElement(SEMICOLON)(';')
PsiWhiteSpace('\n ')
ENUM_ENTRY
OBJECT_DECLARATION_NAME
PsiElement(IDENTIFIER)('EAST')
PsiWhiteSpace('\n')
PsiElement(RBRACE)('}')
@@ -0,0 +1,10 @@
enum class Color {
NORTH;
fun foo() = 1
SOUTH;
companion object {
fun bar() = 2
}
WEST;
EAST
}
@@ -0,0 +1,87 @@
JetFile: EnumSemicolonBetweenWithMembers.kt
PACKAGE_DIRECTIVE
<empty list>
CLASS
MODIFIER_LIST
PsiElement(enum)('enum')
PsiWhiteSpace(' ')
PsiElement(class)('class')
PsiWhiteSpace(' ')
PsiElement(IDENTIFIER)('Color')
PsiWhiteSpace(' ')
CLASS_BODY
PsiElement(LBRACE)('{')
PsiWhiteSpace('\n ')
ENUM_ENTRY
OBJECT_DECLARATION_NAME
PsiElement(IDENTIFIER)('NORTH')
PsiElement(SEMICOLON)(';')
PsiWhiteSpace('\n ')
FUN
PsiElement(fun)('fun')
PsiWhiteSpace(' ')
PsiElement(IDENTIFIER)('foo')
VALUE_PARAMETER_LIST
PsiElement(LPAR)('(')
PsiElement(RPAR)(')')
PsiWhiteSpace(' ')
PsiElement(EQ)('=')
PsiWhiteSpace(' ')
INTEGER_CONSTANT
PsiElement(INTEGER_LITERAL)('1')
PsiWhiteSpace('\n ')
MODIFIER_LIST
ANNOTATION_ENTRY
CONSTRUCTOR_CALLEE
TYPE_REFERENCE
USER_TYPE
REFERENCE_EXPRESSION
PsiElement(IDENTIFIER)('SOUTH')
PsiErrorElement:Expecting member declaration
PsiElement(SEMICOLON)(';')
PsiWhiteSpace('\n ')
OBJECT_DECLARATION
MODIFIER_LIST
PsiElement(companion)('companion')
PsiWhiteSpace(' ')
PsiElement(object)('object')
PsiWhiteSpace(' ')
CLASS_BODY
PsiElement(LBRACE)('{')
PsiWhiteSpace('\n ')
FUN
PsiElement(fun)('fun')
PsiWhiteSpace(' ')
PsiElement(IDENTIFIER)('bar')
VALUE_PARAMETER_LIST
PsiElement(LPAR)('(')
PsiElement(RPAR)(')')
PsiWhiteSpace(' ')
PsiElement(EQ)('=')
PsiWhiteSpace(' ')
INTEGER_CONSTANT
PsiElement(INTEGER_LITERAL)('2')
PsiWhiteSpace('\n ')
PsiElement(RBRACE)('}')
PsiWhiteSpace('\n ')
MODIFIER_LIST
ANNOTATION_ENTRY
CONSTRUCTOR_CALLEE
TYPE_REFERENCE
USER_TYPE
REFERENCE_EXPRESSION
PsiElement(IDENTIFIER)('WEST')
PsiErrorElement:Expecting member declaration
PsiElement(SEMICOLON)(';')
PsiWhiteSpace('\n ')
MODIFIER_LIST
ANNOTATION_ENTRY
CONSTRUCTOR_CALLEE
TYPE_REFERENCE
USER_TYPE
REFERENCE_EXPRESSION
PsiElement(IDENTIFIER)('EAST')
PsiErrorElement:Expecting member declaration
<empty list>
PsiWhiteSpace('\n')
PsiElement(RBRACE)('}')
@@ -4182,6 +4182,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("enumCommaSemicolonBetween.kt")
public void testEnumCommaSemicolonBetween() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/enum/enumCommaSemicolonBetween.kt");
doTest(fileName);
}
@TestMetadata("enumEntryCannotHaveClassObject.kt")
public void testEnumEntryCannotHaveClassObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/enum/enumEntryCannotHaveClassObject.kt");
@@ -4260,6 +4266,12 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("enumSemicolonBetween.kt")
public void testEnumSemicolonBetween() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/enum/enumSemicolonBetween.kt");
doTest(fileName);
}
@TestMetadata("enumStarImport.kt")
public void testEnumStarImport() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/enum/enumStarImport.kt");
@@ -223,6 +223,18 @@ public class JetParsingTestGenerated extends AbstractJetParsingTest {
doParsingTest(fileName);
}
@TestMetadata("EnumSemicolonBetween.kt")
public void testEnumSemicolonBetween() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/psi/EnumSemicolonBetween.kt");
doParsingTest(fileName);
}
@TestMetadata("EnumSemicolonBetweenWithMembers.kt")
public void testEnumSemicolonBetweenWithMembers() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/psi/EnumSemicolonBetweenWithMembers.kt");
doParsingTest(fileName);
}
@TestMetadata("EnumShortCommas.kt")
public void testEnumShortCommas() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/psi/EnumShortCommas.kt");
@@ -20,6 +20,7 @@ import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiErrorElement
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.createIntentionFactory
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.createIntentionForFirstParentOfType
@@ -62,7 +63,11 @@ class DeprecatedEnumEntryDelimiterSyntaxFix(element: JetEnumEntry): JetIntention
var next = entry.getNextSiblingIgnoringWhitespaceAndComments()
var nextType = next?.getNode()?.getElementType()
if (entryIndex < entries.size() - 1) {
if (nextType != JetTokens.COMMA) {
if (next is PsiErrorElement && next.getFirstChild()?.getNode()?.getElementType() == JetTokens.SEMICOLON) {
// Fix for syntax error like ENUM_ENTRY1; ENUM_ENTRY2; ENUM_ENTRY3
next.replace(psiFactory.createComma())
}
else if (nextType != JetTokens.COMMA) {
// Classic case like ENUM_ENTRY1 ENUM_ENTRY2
body.addAfter(psiFactory.createComma(), entry)
}
@@ -0,0 +1,6 @@
// "Insert lacking comma(s) / semicolon(s)" "true"
enum class MyEnum {
A, B; C<caret> D,
fun foo() = 42
}
@@ -0,0 +1,6 @@
// "Insert lacking comma(s) / semicolon(s)" "true"
enum class MyEnum {
A, B, C<caret>, D;
fun foo() = 42
}
@@ -0,0 +1,6 @@
// "Insert lacking comma(s) / semicolon(s)" "true"
enum class MyEnum {
A<caret>; B; C; D;
fun foo() = 42
}
@@ -0,0 +1,6 @@
// "Insert lacking comma(s) / semicolon(s)" "true"
enum class MyEnum {
A<caret>, B, C, D;
fun foo() = 42
}
@@ -0,0 +1,6 @@
// "Insert lacking comma(s) / semicolon(s)" "true"
enum class MyEnum {
A; B; C<caret>; D
fun foo() = 42
}
@@ -0,0 +1,6 @@
// "Insert lacking comma(s) / semicolon(s)" "true"
enum class MyEnum {
A, B, C<caret>, D;
fun foo() = 42
}
@@ -3096,6 +3096,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName);
}
@TestMetadata("commaSemicolonDelimiter.kt")
public void testCommaSemicolonDelimiter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/enumDelimiter/commaSemicolonDelimiter.kt");
doTest(fileName);
}
@TestMetadata("missedCommas.kt")
public void testMissedCommas() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/enumDelimiter/missedCommas.kt");
@@ -3150,6 +3156,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName);
}
@TestMetadata("semicolonDelimiter.kt")
public void testSemicolonDelimiter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/enumDelimiter/semicolonDelimiter.kt");
doTest(fileName);
}
@TestMetadata("semicolonDelimiterExceptLast.kt")
public void testSemicolonDelimiterExceptLast() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/enumDelimiter/semicolonDelimiterExceptLast.kt");
doTest(fileName);
}
@TestMetadata("wholeProject.kt")
public void testWholeProject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/migration/enumDelimiter/wholeProject.kt");