From 450cb2075a80d441dde6b67777918c8c16fe0ce9 Mon Sep 17 00:00:00 2001 From: Junichi Yamamoto Date: Tue, 5 Dec 2023 10:29:50 +0900 Subject: [PATCH] PHP 8.3 Support: Typed class constants (Part 6) - https://github.com/apache/netbeans/issues/6701 - https://wiki.php.net/rfc/typed_class_constants - Fix the formatter (don't add spaces within parens of DNF types) - Fix the `UnusableTypeHintError` - Add unit tests --- .../php/editor/indent/FormatToken.java | 1 + .../php/editor/indent/FormatVisitor.java | 21 ++- .../php/editor/indent/TokenFormatter.java | 4 + .../verification/UnusableTypesHintError.java | 25 ++- .../php82/dnfTypes_01.php.formatted | 2 +- .../php83/typedClassConstants_01.php | 81 +++++++++ ...1.php.testTypedClassConstants_01.formatted | 93 ++++++++++ .../php83/typedClassConstants_02.php | 93 ++++++++++ ...2.php.testTypedClassConstants_02.formatted | 93 ++++++++++ .../testConstantTypes_01.php | 99 +++++++++++ ...antTypes_01.php.testConstantTypes_01.hints | 168 ++++++++++++++++++ .../php/editor/indent/PHPFormatterTest.java | 11 ++ .../UnusableTypesHintErrorTest.java | 4 + 13 files changed, 685 insertions(+), 10 deletions(-) create mode 100644 php/php.editor/test/unit/data/testfiles/formatting/php83/typedClassConstants_01.php create mode 100644 php/php.editor/test/unit/data/testfiles/formatting/php83/typedClassConstants_01.php.testTypedClassConstants_01.formatted create mode 100644 php/php.editor/test/unit/data/testfiles/formatting/php83/typedClassConstants_02.php create mode 100644 php/php.editor/test/unit/data/testfiles/formatting/php83/typedClassConstants_02.php.testTypedClassConstants_02.formatted create mode 100644 php/php.editor/test/unit/data/testfiles/verification/UnusableTypesHintError/testConstantTypes_01.php create mode 100644 php/php.editor/test/unit/data/testfiles/verification/UnusableTypesHintError/testConstantTypes_01.php.testConstantTypes_01.hints diff --git a/php/php.editor/src/org/netbeans/modules/php/editor/indent/FormatToken.java b/php/php.editor/src/org/netbeans/modules/php/editor/indent/FormatToken.java index 6e812f71d6bb..727f8f968661 100644 --- a/php/php.editor/src/org/netbeans/modules/php/editor/indent/FormatToken.java +++ b/php/php.editor/src/org/netbeans/modules/php/editor/indent/FormatToken.java @@ -126,6 +126,7 @@ public enum Kind { WHITESPACE_WITHIN_ATTRIBUTE_BRACKETS, WHITESPACE_WITHIN_ATTRIBUTE_DECL_PARENS, WHITESPACE_WITHIN_TYPE_CAST_PARENS, + WHITESPACE_WITHIN_DNF_TYPE_PARENS, // (A&B)|C WHITESPACE_WITHIN_DYNAMIC_NAME_BRACES, // {$example} WHITESPACE_BEFORE_COMMA, WHITESPACE_AFTER_COMMA, diff --git a/php/php.editor/src/org/netbeans/modules/php/editor/indent/FormatVisitor.java b/php/php.editor/src/org/netbeans/modules/php/editor/indent/FormatVisitor.java index eddae94c393a..11058e1136c1 100644 --- a/php/php.editor/src/org/netbeans/modules/php/editor/indent/FormatVisitor.java +++ b/php/php.editor/src/org/netbeans/modules/php/editor/indent/FormatVisitor.java @@ -1080,13 +1080,14 @@ public void visit(ConstantDeclaration node) { } } scan(node.getAttributes()); - while (ts.moveNext() && ts.token().id() != PHPTokenId.PHP_STRING) { + while (ts.moveNext() && !isConstTypeToken(ts.token())) { addFormatToken(formatTokens); } + ts.movePrevious(); FormatToken lastWhitespace = formatTokens.remove(formatTokens.size() - 1); formatTokens.add(new FormatToken(FormatToken.Kind.WHITESPACE_AFTER_MODIFIERS, lastWhitespace.getOffset(), lastWhitespace.getOldText())); - addFormatToken(formatTokens); formatTokens.add(new FormatToken.IndentToken(node.getStartOffset(), options.continualIndentSize)); + scan(node.getConstType()); scan(node.getNames()); if (node.getNames().size() == 1) { while (ts.moveNext() @@ -2499,6 +2500,8 @@ public void visit(DeclareStatement node) { @Override public void visit(UnionType node) { processUnionOrIntersectionType(node.getTypes()); + // add ")" if it exists e.g. (A&B)|(B&C) + addAllUntilOffset(node.getEndOffset()); } @Override @@ -2729,6 +2732,9 @@ private void addFormatToken(List tokens) { tokens.add(new FormatToken(FormatToken.Kind.WHITESPACE_BEFORE_ARRAY_DECL_PAREN, ts.offset())); tokens.add(new FormatToken(FormatToken.Kind.TEXT, ts.offset(), ts.token().text().toString())); tokens.add(new FormatToken(FormatToken.Kind.WHITESPACE_AFTER_ARRAY_DECL_LEFT_PAREN, ts.offset() + ts.token().length())); + } else if (parent instanceof UnionType) { + tokens.add(new FormatToken(FormatToken.Kind.TEXT, ts.offset(), ts.token().text().toString())); + tokens.add(new FormatToken(FormatToken.Kind.WHITESPACE_WITHIN_DNF_TYPE_PARENS, ts.offset() + ts.token().length())); } else { tokens.add(new FormatToken(FormatToken.Kind.TEXT, ts.offset(), ts.token().text().toString())); } @@ -2767,6 +2773,9 @@ private void addFormatToken(List tokens) { } else if (parent instanceof ArrayCreation) { tokens.add(new FormatToken(FormatToken.Kind.WHITESPACE_BEFORE_ARRAY_DECL_RIGHT_PAREN, ts.offset())); tokens.add(new FormatToken(FormatToken.Kind.TEXT, ts.offset(), ts.token().text().toString())); + } else if (parent instanceof UnionType) { + tokens.add(new FormatToken(FormatToken.Kind.WHITESPACE_WITHIN_DNF_TYPE_PARENS, ts.offset())); + tokens.add(new FormatToken(FormatToken.Kind.TEXT, ts.offset(), ts.token().text().toString())); } else { tokens.add(new FormatToken(FormatToken.Kind.TEXT, ts.offset(), ts.token().text().toString())); } @@ -3349,7 +3358,11 @@ private boolean isFirstUseTraitStatementInBlock(ASTNode parentNode, UseTraitStat private boolean isFieldTypeOrVariableToken(Token token) { return PHPTokenId.PHP_VARIABLE == token.id() - || PHPTokenId.PHP_STRING == token.id() + || isConstTypeToken(token); + } + + private boolean isConstTypeToken(Token token) { + return PHPTokenId.PHP_STRING == token.id() || PHPTokenId.PHP_ARRAY == token.id() || PHPTokenId.PHP_ITERABLE == token.id() || PHPTokenId.PHP_PARENT == token.id() @@ -3362,9 +3375,11 @@ private boolean isFieldTypeOrVariableToken(Token token) { || PHPTokenId.PHP_NULL == token.id() || PHPTokenId.PHP_FALSE == token.id() || PHPTokenId.PHP_NS_SEPARATOR == token.id() // \ + || (PHPTokenId.PHP_TOKEN == token.id() && TokenUtilities.textEquals(token.text(), "(")) // NOI18N || (PHPTokenId.PHP_TOKEN == token.id() && TokenUtilities.textEquals(token.text(), "?")) // NOI18N || PHPTokenId.PHP_TYPE_VOID == token.id() // not supported type but just check it || PHPTokenId.PHP_CALLABLE == token.id() // not supported type but just check it + || PHPTokenId.PHP_TYPE_NEVER == token.id() // not supported type but just check it ; } diff --git a/php/php.editor/src/org/netbeans/modules/php/editor/indent/TokenFormatter.java b/php/php.editor/src/org/netbeans/modules/php/editor/indent/TokenFormatter.java index 30821471cea3..476431657557 100644 --- a/php/php.editor/src/org/netbeans/modules/php/editor/indent/TokenFormatter.java +++ b/php/php.editor/src/org/netbeans/modules/php/editor/indent/TokenFormatter.java @@ -1555,6 +1555,10 @@ && countOfNewLines(formatTokens.get(index + 1).getOldText()) > 0) { case WHITESPACE_WITHIN_TYPE_CAST_PARENS: countSpaces = docOptions.spaceWithinTypeCastParens ? 1 : 0; break; + case WHITESPACE_WITHIN_DNF_TYPE_PARENS: + // change here if we add the option for it + countSpaces = 0; + break; case WHITESPACE_WITHIN_DYNAMIC_NAME_BRACES: // change here if we add the option for it countSpaces = 0; diff --git a/php/php.editor/src/org/netbeans/modules/php/editor/verification/UnusableTypesHintError.java b/php/php.editor/src/org/netbeans/modules/php/editor/verification/UnusableTypesHintError.java index 227b7efe8229..a1948e2737df 100644 --- a/php/php.editor/src/org/netbeans/modules/php/editor/verification/UnusableTypesHintError.java +++ b/php/php.editor/src/org/netbeans/modules/php/editor/verification/UnusableTypesHintError.java @@ -40,6 +40,7 @@ import org.netbeans.modules.php.editor.parser.PHPParseResult; import org.netbeans.modules.php.editor.parser.astnodes.ASTNode; import org.netbeans.modules.php.editor.parser.astnodes.ArrowFunctionDeclaration; +import org.netbeans.modules.php.editor.parser.astnodes.ConstantDeclaration; import org.netbeans.modules.php.editor.parser.astnodes.Expression; import org.netbeans.modules.php.editor.parser.astnodes.FieldsDeclaration; import org.netbeans.modules.php.editor.parser.astnodes.FormalParameter; @@ -128,7 +129,19 @@ public void visit(FieldsDeclaration node) { } Expression fieldType = node.getFieldType(); if (fieldType != null) { - checkFieldType(fieldType, false); + checkFieldAndConstType(fieldType, false); + } + super.visit(node); + } + + @Override + public void visit(ConstantDeclaration node) { + if (CancelSupport.getDefault().isCancelled()) { + return; + } + Expression constType = node.getConstType(); + if (constType != null) { + checkFieldAndConstType(constType, false); } super.visit(node); } @@ -239,11 +252,11 @@ public void visit(NullableType nullableType) { super.visit(nullableType); } - private void checkFieldType(@NullAllowed Expression fieldType, boolean isInUnionType) { + private void checkFieldAndConstType(@NullAllowed Expression declaredType, boolean isInUnionType) { // unusable types: void and callable PHP 7.4 - Expression type = fieldType; - if (fieldType instanceof NullableType) { - type = ((NullableType) fieldType).getType(); + Expression type = declaredType; + if (declaredType instanceof NullableType) { + type = ((NullableType) declaredType).getType(); } if (type == null) { return; @@ -262,7 +275,7 @@ private void checkFieldType(@NullAllowed Expression fieldType, boolean isInUnion checkTrueAndFalseAndNullTypes((NamespaceName) type); } } else if (type instanceof UnionType) { - ((UnionType) type).getTypes().forEach(unionType -> checkFieldType(unionType, true)); + ((UnionType) type).getTypes().forEach(unionType -> checkFieldAndConstType(unionType, true)); } } diff --git a/php/php.editor/test/unit/data/testfiles/formatting/php82/dnfTypes_01.php.formatted b/php/php.editor/test/unit/data/testfiles/formatting/php82/dnfTypes_01.php.formatted index 933d833d0e4c..d7bd04d77666 100644 --- a/php/php.editor/test/unit/data/testfiles/formatting/php82/dnfTypes_01.php.formatted +++ b/php/php.editor/test/unit/data/testfiles/formatting/php82/dnfTypes_01.php.formatted @@ -69,7 +69,7 @@ class ClassY { private (ClassX&ClassZ)|(ClassY&ClassZ)|ClassX $privateYField; protected ClassX|(ClassY&ClassZ)|ClassY $protectedYField; public static (ClassY&ClassZ)|ClassX $publicStaticYField; - private static ( ClassY&ClassZ)|ClassY $privateStaticYField; + private static (ClassY&ClassZ)|ClassY $privateStaticYField; protected static (ClassY&ClassZ)|ClassX|ClassY $protectedStaticYField; public function publicYMethod((ClassY&ClassZ)|ClassX $param1, (ClassY&ClassZ)|ClassX|(ClassX&ClassZ) $param2): ClassX|(ClassY&ClassZ) { diff --git a/php/php.editor/test/unit/data/testfiles/formatting/php83/typedClassConstants_01.php b/php/php.editor/test/unit/data/testfiles/formatting/php83/typedClassConstants_01.php new file mode 100644 index 000000000000..cf366b26fd06 --- /dev/null +++ b/php/php.editor/test/unit/data/testfiles/formatting/php83/typedClassConstants_01.php @@ -0,0 +1,81 @@ + options = new HashMap<>(FmtOptions.getDefaults()); + reformatFileContents("testfiles/formatting/php83/typedClassConstants_01.php", options, false, true); + } + + public void testTypedClassConstants_02() throws Exception { + HashMap options = new HashMap<>(FmtOptions.getDefaults()); + reformatFileContents("testfiles/formatting/php83/typedClassConstants_02.php", options, false, true); + } + } diff --git a/php/php.editor/test/unit/src/org/netbeans/modules/php/editor/verification/UnusableTypesHintErrorTest.java b/php/php.editor/test/unit/src/org/netbeans/modules/php/editor/verification/UnusableTypesHintErrorTest.java index 81b375a6e250..0a488330bccf 100644 --- a/php/php.editor/test/unit/src/org/netbeans/modules/php/editor/verification/UnusableTypesHintErrorTest.java +++ b/php/php.editor/test/unit/src/org/netbeans/modules/php/editor/verification/UnusableTypesHintErrorTest.java @@ -75,6 +75,10 @@ public void testDuplicateTypes_01() throws Exception { checkHints(new UnusableTypesHintError(), "testDuplicateTypes_01.php"); } + public void testConstantTypes_01() throws Exception { + checkHints(new UnusableTypesHintError(), "testConstantTypes_01.php"); + } + @Override protected String getTestDirectory() { return TEST_DIRECTORY + "UnusableTypesHintError/";