Skip to content

Commit

Permalink
PHP 8.3 Support: Dynamic class constant fetch (Part 4)
Browse files Browse the repository at this point in the history
- apache#6701
- https://wiki.php.net/rfc/dynamic_class_constant_fetch
- Fix the code completion feature
- Add unit tests for the code completion and brace matching features
  • Loading branch information
junichi11 committed Nov 27, 2023
1 parent 45b6dda commit a491904
Show file tree
Hide file tree
Showing 28 changed files with 719 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ final class CompletionContextFinder {
private static final String MULTI_CATCH_EXCEPTION_TOKENS = "MULTI_CATCH_EXCEPTION_TOKENS"; //NOI18N
private static final String COMBINED_USE_STATEMENT_TOKENS = "COMBINED_USE_STATEMENT_TOKENS"; //NOI18N
private static final String CONST_STATEMENT_TOKENS = "CONST_STATEMENT_TOKENS"; //NOI18N
private static final String ENUM_CASE_STATEMENT_TOKENS = "ENUM_CASE_STATEMENT_TOKENS"; //NOI18N
private static final String FIELD_UNION_OR_INTERSECTION_TYPE_TOKENS = "FIELD_UNION_TYPE_TOKENS"; //NOI18N
private static final String FIELD_MODIFIERS_TOKENS = "FIELD_MODIFIERS_TOKENS"; //NOI18N
private static final String OBJECT_OPERATOR_TOKEN = "OBJECT_OPERATOR_TOKEN"; //NOI18N
Expand Down Expand Up @@ -204,14 +205,18 @@ final class CompletionContextFinder {
new Object[]{PHPTokenId.PHP_CONST, PHPTokenId.WHITESPACE, PHPTokenId.PHP_STRING, PHPTokenId.WHITESPACE, CONST_STATEMENT_TOKENS},
new Object[]{PHPTokenId.PHP_CONST, PHPTokenId.WHITESPACE, PHPTokenId.PHP_STRING, CONST_STATEMENT_TOKENS}
);
private static final List<Object[]> ENUM_CASE_TOKENCHAINS = Arrays.asList(
new Object[]{PHPTokenId.PHP_CASE, PHPTokenId.WHITESPACE, PHPTokenId.PHP_STRING, PHPTokenId.WHITESPACE, CONST_STATEMENT_TOKENS},
new Object[]{PHPTokenId.PHP_CASE, PHPTokenId.WHITESPACE, PHPTokenId.PHP_STRING, CONST_STATEMENT_TOKENS}
);
private static final List<Object[]> SERVER_ARRAY_TOKENCHAINS = Collections.singletonList(
new Object[]{PHPTokenId.PHP_VARIABLE, PHPTokenId.PHP_TOKEN});
private static final List<String> SERVER_ARRAY_TOKENTEXTS =
Arrays.asList(new String[]{"$_SERVER", "["}); //NOI18N

public static enum CompletionContext {

EXPRESSION, GLOBAL_CONST_EXPRESSION, CLASS_CONST_EXPRESSION, MATCH_EXPRESSION,
EXPRESSION, GLOBAL_CONST_EXPRESSION, CLASS_CONST_EXPRESSION, MATCH_EXPRESSION, ENUM_CASE_EXPRESSION,
HTML, CLASS_NAME, INTERFACE_NAME, BACKING_TYPE,
TYPE_NAME, RETURN_TYPE_NAME, RETURN_UNION_OR_INTERSECTION_TYPE_NAME, FIELD_TYPE_NAME, VISIBILITY_MODIFIER_OR_TYPE_NAME, STRING,
CLASS_MEMBER, STATIC_CLASS_MEMBER, PHPDOC, INHERITANCE, EXTENDS, IMPLEMENTS, METHOD_NAME,
Expand Down Expand Up @@ -328,6 +333,8 @@ static CompletionContext findCompletionContext(ParserResult info, int caretOffse
return CompletionContext.FIELD_TYPE_NAME;
} else if (acceptTokenChains(tokenSequence, CONST_TOKENCHAINS, moveNextSucces)) {
return CompletionContext.CLASS_CONST_EXPRESSION;
} else if (acceptTokenChains(tokenSequence, ENUM_CASE_TOKENCHAINS, moveNextSucces)) {
return CompletionContext.ENUM_CASE_EXPRESSION;
} else if (acceptTokenChains(tokenSequence, CLASS_CONTEXT_KEYWORDS_TOKENCHAINS, moveNextSucces)) {
return CompletionContext.CLASS_CONTEXT_KEYWORDS;
}
Expand Down Expand Up @@ -540,6 +547,11 @@ private static boolean acceptTokenChain(TokenSequence tokenSequence, Object[] to
accept = false;
break;
}
} else if (tokenID == ENUM_CASE_STATEMENT_TOKENS) {
if (!consumeUntilEnumCaseEqual(tokenSequence)) {
accept = false;
break;
}
} else if (tokenID == OBJECT_OPERATOR_TOKEN) {
if (!consumeObjectOperator(tokenSequence)) {
accept = false;
Expand Down Expand Up @@ -749,9 +761,25 @@ private static boolean consumeUntilTypeKeyword(TokenSequence tokenSequence) {
private static boolean consumeUntilConstEqual(TokenSequence tokenSequence) {
boolean hasEqual = false;
do {
if (tokenSequence.token().id() == PHPTokenId.PHP_SEMICOLON
|| tokenSequence.token().id() == PHPTokenId.PHP_CURLY_OPEN
|| tokenSequence.token().id() == PHPTokenId.PHP_CURLY_CLOSE) {
if (tokenSequence.token().id() == PHPTokenId.PHP_CONST
|| tokenSequence.token().id() == PHPTokenId.PHP_SEMICOLON) {
break;
}
if (isEqualSign(tokenSequence.token())) {
hasEqual = true;
tokenSequence.movePrevious();
break;
}
} while (tokenSequence.movePrevious());

return hasEqual;
}

private static boolean consumeUntilEnumCaseEqual(TokenSequence tokenSequence) {
boolean hasEqual = false;
do {
if (tokenSequence.token().id() == PHPTokenId.PHP_CASE
|| tokenSequence.token().id() == PHPTokenId.PHP_SEMICOLON) {
break;
}
if (isEqualSign(tokenSequence.token())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,8 @@ public CodeCompletionResult complete(CodeCompletionContext completionContext) {
autoCompleteConstants(completionResult, request);
autoCompleteKeywords(completionResult, request, PHP_GLOBAL_CONST_KEYWORDS);
break;
case CLASS_CONST_EXPRESSION:
case CLASS_CONST_EXPRESSION: // no break
case ENUM_CASE_EXPRESSION:
autoCompleteNamespaces(completionResult, request);
autoCompleteTypeNames(completionResult, request, null, true);
autoCompleteConstants(completionResult, request);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

class Test {

public const TEST = 'test';
public const TES = 'TES';
public const T = 'T';
public const TEST1 = self::{'TEST'};
public const TEST2 = self::{'TES' . 'T'};
public const TEST3 = self::{self::TES . self::T};

public static function method(): void {

}
}

enum EnumTest: string {

case TEST = 'test';
case TES = 'TES';
case T = 'T';
case TEST1 = self::{'TEST'};
case TEST2 = self::{'TES' . 'T'};
case TEST3 = self::{self::TES . self::T};
case TEST4 = EnumTest::{self::{self::TES} . self::T};
}

function test(string $param = "test"): void {

}

$variable = 'TEST';
$e = 'e';
$test = "Test";

Test::{$variable};
$test::{$variable . $e};

Test::{Test::method()}::{test($variable)};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Code completion result for source line:
public const TEST3 = self::{self::T|ES . self::T};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
CONSTANT T 'T' [PUBLIC] Test
CONSTANT TES 'TES' [PUBLIC] Test
CONSTANT TEST 'test' [PUBLIC] Test
CONSTANT TEST1 ? [PUBLIC] Test
CONSTANT TEST2 ? [PUBLIC] Test
CONSTANT TEST3 ? [PUBLIC] Test
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Code completion result for source line:
Test::{$|variable};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
VARIABLE string $e [PUBLIC] dynamicClassConstantFetch.php
VARIABLE string $test [PUBLIC] dynamicClassConstantFetch.php
VARIABLE string $variable [PUBLIC] dynamicClassConstantFetch.php
------------------------------------
VARIABLE $GLOBALS PHP Platform
VARIABLE $HTTP_RAW_POST_DATA PHP Platform
VARIABLE $_COOKIE PHP Platform
VARIABLE $_ENV PHP Platform
VARIABLE $_FILES PHP Platform
VARIABLE $_GET PHP Platform
VARIABLE $_POST PHP Platform
VARIABLE $_REQUEST PHP Platform
VARIABLE $_SERVER PHP Platform
VARIABLE $_SESSION PHP Platform
VARIABLE $argc PHP Platform
VARIABLE $argv PHP Platform
VARIABLE $http_response_header PHP Platform
VARIABLE $php_errormsg PHP Platform
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Code completion result for source line:
Test::{$var|iable};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
VARIABLE string $variable [PUBLIC] dynamicClassConstantFetch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Code completion result for source line:
$test::{$variable . $|e};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
VARIABLE string $e [PUBLIC] dynamicClassConstantFetch.php
VARIABLE string $test [PUBLIC] dynamicClassConstantFetch.php
VARIABLE string $variable [PUBLIC] dynamicClassConstantFetch.php
------------------------------------
VARIABLE $GLOBALS PHP Platform
VARIABLE $HTTP_RAW_POST_DATA PHP Platform
VARIABLE $_COOKIE PHP Platform
VARIABLE $_ENV PHP Platform
VARIABLE $_FILES PHP Platform
VARIABLE $_GET PHP Platform
VARIABLE $_POST PHP Platform
VARIABLE $_REQUEST PHP Platform
VARIABLE $_SERVER PHP Platform
VARIABLE $_SESSION PHP Platform
VARIABLE $argc PHP Platform
VARIABLE $argv PHP Platform
VARIABLE $http_response_header PHP Platform
VARIABLE $php_errormsg PHP Platform
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Code completion result for source line:
$test::{$variable . $e|};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
VARIABLE string $e [PUBLIC] dynamicClassConstantFetch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Code completion result for source line:
Test::{Tes|t::method()}::{test($variable)};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
CLASS Test [PUBLIC] dynamicClassConstantFetch.php
METHOD test() [PUBLIC] dynamicClassConstantFetch.php
METHOD test(string $param = "test") [PUBLIC] dynamicClassConstantFetch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Code completion result for source line:
Test::{Test::met|hod()}::{test($variable)};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
METHOD method() [STATIC] Test
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Code completion result for source line:
Test::{Test::method()}::{te|st($variable)};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
CLASS Test [PUBLIC] dynamicClassConstantFetch.php
METHOD test() [PUBLIC] dynamicClassConstantFetch.php
METHOD test(string $param = "test") [PUBLIC] dynamicClassConstantFetch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Code completion result for source line:
Test::{Test::method()}::{test($var|iable)};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
VARIABLE string $variable [PUBLIC] dynamicClassConstantFetch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Code completion result for source line:
case TEST3 = self::|{self::TES . self::T};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
CONSTANT T 'T' [PUBLIC] EnumTest
CONSTANT TES 'TES' [PUBLIC] EnumTest
CONSTANT TEST 'test' [PUBLIC] EnumTest
CONSTANT TEST1 ? [PUBLIC] EnumTest
CONSTANT TEST2 ? [PUBLIC] EnumTest
CONSTANT TEST3 ? [PUBLIC] EnumTest
CONSTANT TEST4 ? [PUBLIC] EnumTest
CONSTANT class \EnumTest [PUBLIC] Magic Constant
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Code completion result for source line:
case TEST3 = self::{sel|f::TES . self::T};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
KEYWORD self:: null
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Code completion result for source line:
case TEST3 = self::{self::TE|S . self::T};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
CONSTANT TES 'TES' [PUBLIC] EnumTest
CONSTANT TEST 'test' [PUBLIC] EnumTest
CONSTANT TEST1 ? [PUBLIC] EnumTest
CONSTANT TEST2 ? [PUBLIC] EnumTest
CONSTANT TEST3 ? [PUBLIC] EnumTest
CONSTANT TEST4 ? [PUBLIC] EnumTest
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Code completion result for source line:
case TEST4 = EnumTe|st::{self::{self::TES} . self::T};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
CLASS EnumTest [PUBLIC] dynamicClassConstantFetch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Code completion result for source line:
case TEST4 = EnumTest::{se|lf::{self::TES} . self::T};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
KEYWORD self:: null
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Code completion result for source line:
case TEST4 = EnumTest::{self::{sel|f::TES} . self::T};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
KEYWORD self:: null
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Code completion result for source line:
case TEST4 = EnumTest::{self::{self::TE|S} . self::T};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
CONSTANT TES 'TES' [PUBLIC] EnumTest
CONSTANT TEST 'test' [PUBLIC] EnumTest
CONSTANT TEST1 ? [PUBLIC] EnumTest
CONSTANT TEST2 ? [PUBLIC] EnumTest
CONSTANT TEST3 ? [PUBLIC] EnumTest
CONSTANT TEST4 ? [PUBLIC] EnumTest
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Code completion result for source line:
case TEST4 = EnumTest::{self::{self::TES} . self::T|};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
CONSTANT T 'T' [PUBLIC] EnumTest
CONSTANT TES 'TES' [PUBLIC] EnumTest
CONSTANT TEST 'test' [PUBLIC] EnumTest
CONSTANT TEST1 ? [PUBLIC] EnumTest
CONSTANT TEST2 ? [PUBLIC] EnumTest
CONSTANT TEST3 ? [PUBLIC] EnumTest
CONSTANT TEST4 ? [PUBLIC] EnumTest
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

class Test {
public const TEST = "Test";
public const TEST1 = self::{};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Code completion result for source line:
public const TEST1 = self::{|};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
KEYWORD parent:: null
KEYWORD self:: null
------------------------------------
KEYWORD array null
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

enum Test: string {
case TEST = "Test";
case TEST1 = self::{};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Code completion result for source line:
case TEST1 = self::{|};
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
KEYWORD parent:: null
KEYWORD self:: null
------------------------------------
KEYWORD array null

0 comments on commit a491904

Please sign in to comment.