diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 6581eb0e4a5b..d18547769322 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -654,7 +654,7 @@ PARSE.expected.colon.or.rbracket=':' or ']' expected PARSE.expected.comma.or.rpar=',' or ')' expected PARSE.expected.else='else' expected -PARSE.expected.func.name=function name expected +PARSE.expected.identifier=Identifier expected PARSE.expected.comma.lpar.rpar=',' or '(' or ')' expected PARSE.expected.statement.break=Statement break expected PARSE.expected.@.or.def='@' or 'def' expected diff --git a/python/src/com/jetbrains/python/parsing/FunctionParsing.java b/python/src/com/jetbrains/python/parsing/FunctionParsing.java index cd61f6ea85a3..3fb67a872123 100644 --- a/python/src/com/jetbrains/python/parsing/FunctionParsing.java +++ b/python/src/com/jetbrains/python/parsing/FunctionParsing.java @@ -45,7 +45,7 @@ public class FunctionParsing extends Parsing { protected void parseFunctionInnards(PsiBuilder.Marker functionMarker) { myBuilder.advanceLexer(); - checkMatchesOrSkip(PyTokenTypes.IDENTIFIER, message("PARSE.expected.func.name")); + parseIdentifierOrSkip(); parseParameterList(); parseReturnTypeAnnotation(); checkMatches(PyTokenTypes.COLON, message("PARSE.expected.colon")); diff --git a/python/src/com/jetbrains/python/parsing/Parsing.java b/python/src/com/jetbrains/python/parsing/Parsing.java index 99eab6f8a97f..5550589af132 100644 --- a/python/src/com/jetbrains/python/parsing/Parsing.java +++ b/python/src/com/jetbrains/python/parsing/Parsing.java @@ -18,7 +18,9 @@ package com.jetbrains.python.parsing; import com.intellij.lang.PsiBuilder; import com.intellij.openapi.diagnostic.Logger; import com.intellij.psi.tree.IElementType; +import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyElementTypes; +import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.psi.PyElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -61,15 +63,19 @@ public class Parsing { return false; } - protected boolean checkMatchesOrSkip(final IElementType token, final String message) { - if (myBuilder.getTokenType() == token) { + protected boolean parseIdentifierOrSkip() { + if (myBuilder.getTokenType() == PyTokenTypes.IDENTIFIER) { myBuilder.advanceLexer(); return true; } - PsiBuilder.Marker mark = myBuilder.mark(); - myBuilder.advanceLexer(); - mark.error(message); - return false; + else { + final PsiBuilder.Marker nameExpected = myBuilder.mark(); + if (myBuilder.getTokenType() != PyTokenTypes.STATEMENT_BREAK) { + myBuilder.advanceLexer(); + } + nameExpected.error(PyBundle.message("PARSE.expected.identifier")); + return false; + } } protected void assertCurrentToken(final PyElementType tokenType) { @@ -95,7 +101,7 @@ public class Parsing { protected boolean matchToken(final IElementType tokenType) { if (myBuilder.getTokenType() == tokenType) { myBuilder.advanceLexer(); - return true; + return true; } return false; } diff --git a/python/src/com/jetbrains/python/parsing/StatementParsing.java b/python/src/com/jetbrains/python/parsing/StatementParsing.java index ae0972340a2c..9c5a9d828022 100644 --- a/python/src/com/jetbrains/python/parsing/StatementParsing.java +++ b/python/src/com/jetbrains/python/parsing/StatementParsing.java @@ -792,7 +792,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { public void parseClassDeclaration(PsiBuilder.Marker classMarker, ParsingScope scope) { assertCurrentToken(PyTokenTypes.CLASS_KEYWORD); myBuilder.advanceLexer(); - checkMatchesOrSkip(PyTokenTypes.IDENTIFIER, IDENTIFIER_EXPECTED); + parseIdentifierOrSkip(); if (myBuilder.getTokenType() == PyTokenTypes.LPAR) { getExpressionParser().parseArgumentList(); } diff --git a/python/testData/psi/KeywordAsFunctionName.txt b/python/testData/psi/KeywordAsFunctionName.txt index 473ba30c4cd1..c995f929c32c 100644 --- a/python/testData/psi/KeywordAsFunctionName.txt +++ b/python/testData/psi/KeywordAsFunctionName.txt @@ -2,7 +2,7 @@ PyFile:KeywordAsFunctionName.py PyFunction('null') PsiElement(Py:DEF_KEYWORD)('def') PsiWhiteSpace(' ') - PsiErrorElement:function name expected + PsiErrorElement:Identifier expected PsiElement(Py:FROM_KEYWORD)('from') PyParameterList PsiElement(Py:LPAR)('(') diff --git a/python/testData/psi/SingleClassBeforeFunction.py b/python/testData/psi/SingleClassBeforeFunction.py new file mode 100644 index 000000000000..b487baf62e31 --- /dev/null +++ b/python/testData/psi/SingleClassBeforeFunction.py @@ -0,0 +1,5 @@ +class + + +def foo(): + pass diff --git a/python/testData/psi/SingleClassBeforeFunction.txt b/python/testData/psi/SingleClassBeforeFunction.txt new file mode 100644 index 000000000000..2fa5b30231ca --- /dev/null +++ b/python/testData/psi/SingleClassBeforeFunction.txt @@ -0,0 +1,25 @@ +PyFile:SingleClassBeforeFunction.py + PyClass: null + PsiElement(Py:CLASS_KEYWORD)('class') + PsiErrorElement:Identifier expected + + PyArgumentList + + PsiErrorElement:Colon expected + + PsiWhiteSpace('\n\n\n') + PyStatementList + PsiErrorElement:Indent expected + + PyFunction('foo') + PsiElement(Py:DEF_KEYWORD)('def') + PsiWhiteSpace(' ') + PsiElement(Py:IDENTIFIER)('foo') + PyParameterList + PsiElement(Py:LPAR)('(') + PsiElement(Py:RPAR)(')') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') diff --git a/python/testData/psi/SingleDefBeforeFunction.py b/python/testData/psi/SingleDefBeforeFunction.py new file mode 100644 index 000000000000..407918fd90ae --- /dev/null +++ b/python/testData/psi/SingleDefBeforeFunction.py @@ -0,0 +1,5 @@ +def + + +def bar(): + x = 1 diff --git a/python/testData/psi/SingleDefBeforeFunction.txt b/python/testData/psi/SingleDefBeforeFunction.txt new file mode 100644 index 000000000000..5d4dfb0fca34 --- /dev/null +++ b/python/testData/psi/SingleDefBeforeFunction.txt @@ -0,0 +1,31 @@ +PyFile:SingleDefBeforeFunction.py + PyFunction('null') + PsiElement(Py:DEF_KEYWORD)('def') + PsiErrorElement:Identifier expected + + PsiErrorElement:'(' expected + + PyParameterList + + PsiWhiteSpace('\n\n\n') + PyStatementList + PsiErrorElement:Indent expected + + PyFunction('bar') + PsiElement(Py:DEF_KEYWORD)('def') + PsiWhiteSpace(' ') + PsiElement(Py:IDENTIFIER)('bar') + PyParameterList + PsiElement(Py:LPAR)('(') + PsiElement(Py:RPAR)(')') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyAssignmentStatement + PyTargetExpression: x + PsiElement(Py:IDENTIFIER)('x') + PsiWhiteSpace(' ') + PsiElement(Py:EQ)('=') + PsiWhiteSpace(' ') + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('1') \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PythonParsingTest.java b/python/testSrc/com/jetbrains/python/PythonParsingTest.java index 737d54dbde51..d253db77f7c0 100644 --- a/python/testSrc/com/jetbrains/python/PythonParsingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonParsingTest.java @@ -455,6 +455,14 @@ public class PythonParsingTest extends ParsingTestCase { doTest(); } + public void testSingleDefBeforeFunction() { + doTest(); + } + + public void testSingleClassBeforeFunction() { + doTest(); + } + public void doTest(LanguageLevel languageLevel) { LanguageLevel prev = myLanguageLevel; myLanguageLevel = languageLevel;