Skip to content

Commit baf05ae

Browse files
committed
PHP 8.4 Support: Property hooks (Part 6)
- apache#8035 - https://wiki.php.net/rfc#php_84 - https://wiki.php.net/rfc/property-hooks - Fix the indentation that is inserted when a new line is added - Fix an existing bug for a lambda function - Add unit tests Existing bug: ```php // example test( function(): void {^ ); ``` ```php // before test( function(): void { ^ } ); // after test( function(): void { ^ } ); ```
1 parent 3abcc5e commit baf05ae

File tree

212 files changed

+3110
-174
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

212 files changed

+3110
-174
lines changed

php/php.editor/src/org/netbeans/modules/php/editor/indent/IndentUtils.java

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919

2020
package org.netbeans.modules.php.editor.indent;
2121

22+
import java.util.Collection;
23+
import java.util.Set;
2224
import org.netbeans.api.lexer.Token;
2325
import org.netbeans.api.lexer.TokenSequence;
24-
import org.netbeans.api.lexer.TokenUtilities;
2526
import org.netbeans.editor.BaseDocument;
2627
import org.netbeans.lib.editor.util.ArrayUtilities;
2728
import org.netbeans.modules.php.editor.lexer.LexUtilities;
2829
import org.netbeans.modules.php.editor.lexer.PHPTokenId;
30+
import org.netbeans.modules.php.editor.lexer.utils.LexerUtils;
2931

3032
/**
3133
* This class will be unnecessary when issue #192289 is fixed.
@@ -40,7 +42,24 @@ public final class IndentUtils {
4042
}
4143

4244
private static final int MAX_CACHED_TAB_SIZE = 8; // Should mostly be <= 8
43-
45+
private static final Collection<PHPTokenId> BRACE_PLACEMENT_START_TOKENS = Set.of(
46+
PHPTokenId.PHP_CLASS,
47+
PHPTokenId.PHP_FUNCTION,
48+
PHPTokenId.PHP_IF,
49+
PHPTokenId.PHP_ELSE,
50+
PHPTokenId.PHP_ELSEIF,
51+
PHPTokenId.PHP_FOR,
52+
PHPTokenId.PHP_FOREACH,
53+
PHPTokenId.PHP_WHILE,
54+
PHPTokenId.PHP_DO,
55+
PHPTokenId.PHP_SWITCH,
56+
PHPTokenId.PHP_PUBLIC,
57+
PHPTokenId.PHP_PROTECTED,
58+
PHPTokenId.PHP_PRIVATE,
59+
PHPTokenId.PHP_PUBLIC_SET,
60+
PHPTokenId.PHP_PROTECTED_SET,
61+
PHPTokenId.PHP_PRIVATE_SET
62+
);
4463
/**
4564
* Cached indentation string containing tabs.
4665
* <br/>
@@ -132,24 +151,13 @@ public static int countIndent(BaseDocument doc, int offset, int previousIndent)
132151
Token<? extends PHPTokenId> token = ts.token();
133152
while (token.id() != PHPTokenId.PHP_CURLY_OPEN
134153
&& token.id() != PHPTokenId.PHP_SEMICOLON
135-
&& !(token.id() == PHPTokenId.PHP_TOKEN
136-
&& (TokenUtilities.textEquals(token.text(), "(") // NOI18N
137-
|| TokenUtilities.textEquals(token.text(), "["))) // NOI18N
154+
&& !LexerUtils.isOpenParen(token)
155+
&& !LexerUtils.isOpenBracket(token)
138156
&& ts.movePrevious()) {
139157
token = ts.token();
140158
}
141159
if (token.id() == PHPTokenId.PHP_CURLY_OPEN) {
142-
while (token.id() != PHPTokenId.PHP_CLASS
143-
&& token.id() != PHPTokenId.PHP_FUNCTION
144-
&& token.id() != PHPTokenId.PHP_IF
145-
&& token.id() != PHPTokenId.PHP_ELSE
146-
&& token.id() != PHPTokenId.PHP_ELSEIF
147-
&& token.id() != PHPTokenId.PHP_FOR
148-
&& token.id() != PHPTokenId.PHP_FOREACH
149-
&& token.id() != PHPTokenId.PHP_WHILE
150-
&& token.id() != PHPTokenId.PHP_DO
151-
&& token.id() != PHPTokenId.PHP_SWITCH
152-
&& ts.movePrevious()) {
160+
while (!BRACE_PLACEMENT_START_TOKENS.contains(token.id()) && ts.movePrevious()) {
153161
token = ts.token();
154162
}
155163
CodeStyle codeStyle = CodeStyle.get(doc);
@@ -166,6 +174,8 @@ public static int countIndent(BaseDocument doc, int offset, int previousIndent)
166174
bracePlacement = codeStyle.getWhileBracePlacement();
167175
} else if (token.id() == PHPTokenId.PHP_SWITCH) {
168176
bracePlacement = codeStyle.getSwitchBracePlacement();
177+
} else if (LexerUtils.isGetOrSetVisibilityToken(token)) {
178+
bracePlacement = codeStyle.getFieldDeclBracePlacement();
169179
}
170180
value = bracePlacement == CodeStyle.BracePlacement.NEW_LINE_INDENTED ? previousIndent + codeStyle.getIndentSize() : previousIndent;
171181
}

php/php.editor/src/org/netbeans/modules/php/editor/indent/IndentationCounter.java

Lines changed: 325 additions & 110 deletions
Large diffs are not rendered by default.

php/php.editor/src/org/netbeans/modules/php/editor/lexer/LexUtilities.java

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -505,11 +505,15 @@ public static int findStartTokenOfExpression(TokenSequence ts) {
505505
int start = -1;
506506
int origOffset = ts.offset();
507507

508-
Token<? extends PHPTokenId> token;
508+
Token<? extends PHPTokenId> token = null;
509+
Token<? extends PHPTokenId> lastTokenWithoutWSAndComments = null;
509510
int balance = 0;
510511
int curlyBalance = 0;
511512
boolean isInQuotes = false; // GH-6731 for checking a variable in string
512513
do {
514+
if (!LexerUtils.isWhitespaceOrCommentToken(token)) {
515+
lastTokenWithoutWSAndComments = token;
516+
}
513517
token = ts.token();
514518
if (token.id() == PHPTokenId.PHP_TOKEN && !LexerUtils.isDollarCurlyOpen(token)) {
515519
switch (token.text().charAt(0)) {
@@ -530,14 +534,10 @@ public static int findStartTokenOfExpression(TokenSequence ts) {
530534
} else if ((token.id() == PHPTokenId.PHP_SEMICOLON || token.id() == PHPTokenId.PHP_OPENTAG)
531535
&& ts.moveNext()) {
532536
// we found previous end of expression => find begin of the current.
533-
LexUtilities.findNext(ts, Arrays.asList(
534-
PHPTokenId.WHITESPACE,
535-
PHPTokenId.PHPDOC_COMMENT, PHPTokenId.PHPDOC_COMMENT_END, PHPTokenId.PHPDOC_COMMENT_START,
536-
PHPTokenId.PHP_COMMENT, PHPTokenId.PHP_COMMENT_END, PHPTokenId.PHP_COMMENT_START,
537-
PHPTokenId.PHP_LINE_COMMENT));
537+
LexUtilities.findNext(ts, LexerUtils.getWSCommentTokens());
538538
start = ts.offset();
539539
break;
540-
} else if (token.id() == PHPTokenId.PHP_IF) {
540+
} else if (token.id() == PHPTokenId.PHP_IF && curlyBalance == 0) {
541541
// we are at a beginning of if .... withouth curly?
542542
// need to find end of the condition.
543543
int offsetIf = ts.offset(); // remember the if offset
@@ -569,11 +569,7 @@ public static int findStartTokenOfExpression(TokenSequence ts) {
569569
}
570570
}
571571
if (parentBalance == 1 && ts.movePrevious()) {
572-
LexUtilities.findPrevious(ts, Arrays.asList(
573-
PHPTokenId.WHITESPACE,
574-
PHPTokenId.PHPDOC_COMMENT, PHPTokenId.PHPDOC_COMMENT_END, PHPTokenId.PHPDOC_COMMENT_START,
575-
PHPTokenId.PHP_COMMENT, PHPTokenId.PHP_COMMENT_END, PHPTokenId.PHP_COMMENT_START,
576-
PHPTokenId.PHP_LINE_COMMENT));
572+
LexUtilities.findPrevious(ts, LexerUtils.getWSCommentTokens());
577573
start = ts.offset();
578574
}
579575
break;
@@ -586,41 +582,51 @@ public static int findStartTokenOfExpression(TokenSequence ts) {
586582
ts.move(offsetIf);
587583
ts.movePrevious();
588584
}
589-
} else if (token.id() == PHPTokenId.PHP_CASE || token.id() == PHPTokenId.PHP_DEFAULT) {
585+
} else if ((token.id() == PHPTokenId.PHP_CASE || token.id() == PHPTokenId.PHP_DEFAULT) && curlyBalance == 0) {
590586
start = ts.offset();
591587
break;
592588
} else if (token.id() == PHPTokenId.PHP_CURLY_CLOSE) {
593589
curlyBalance--;
594-
if (!isInQuotes && curlyBalance == -1 && ts.moveNext()) {
595-
// we are after previous blog close
596-
LexUtilities.findNext(ts, Arrays.asList(
597-
PHPTokenId.WHITESPACE,
598-
PHPTokenId.PHPDOC_COMMENT, PHPTokenId.PHPDOC_COMMENT_END, PHPTokenId.PHPDOC_COMMENT_START,
599-
PHPTokenId.PHP_COMMENT, PHPTokenId.PHP_COMMENT_END, PHPTokenId.PHP_COMMENT_START,
600-
PHPTokenId.PHP_LINE_COMMENT));
601-
if (ts.offset() <= origOffset) {
602-
start = ts.offset();
603-
} else {
604-
start = origOffset;
590+
if (lastTokenWithoutWSAndComments == null || !LexerUtils.isComma(lastTokenWithoutWSAndComments)) {
591+
// check },
592+
// e.g. hooked property (CPP), lambda functions
593+
// public __construct(
594+
// public int $prop {get {} set {}},
595+
// ) {}
596+
// myFunc(
597+
// function() {},
598+
// )
599+
if (!isInQuotes && curlyBalance == -1 && ts.moveNext()) {
600+
// we are after previous block close
601+
LexUtilities.findNext(ts, LexerUtils.getWSCommentTokens());
602+
if (ts.offset() <= origOffset) {
603+
start = ts.offset();
604+
} else {
605+
start = origOffset;
606+
}
607+
break;
605608
}
606-
break;
607609
}
608610
} else if (LexerUtils.hasCurlyOpen(token)) {
609611
curlyBalance++;
610612
if (!isInQuotes && curlyBalance == 1 && ts.moveNext()) {
611-
// we are at the begining of a blog
612-
LexUtilities.findNext(ts, List.of(
613-
PHPTokenId.WHITESPACE,
614-
PHPTokenId.PHPDOC_COMMENT, PHPTokenId.PHPDOC_COMMENT_END, PHPTokenId.PHPDOC_COMMENT_START,
615-
PHPTokenId.PHP_COMMENT, PHPTokenId.PHP_COMMENT_END, PHPTokenId.PHP_COMMENT_START,
616-
PHPTokenId.PHP_LINE_COMMENT));
613+
// we are at the begining of a block
614+
LexUtilities.findNext(ts, LexerUtils.getWSCommentTokens());
617615
if (ts.offset() <= origOffset) {
618616
start = ts.offset();
619617
} else {
620618
start = origOffset;
621619
}
622620
break;
623621
}
622+
} else if (curlyBalance == 1
623+
&& (LexerUtils.isGetOrSetVisibilityToken(token) || token.id() == PHPTokenId.PHP_FUNCTION)) {
624+
// e.g. CPP, lambda function
625+
// func(
626+
// function() {^
627+
// )
628+
start = ts.offset();
629+
break;
624630
} else if (balance == 1 && token.id() == PHPTokenId.PHP_STRING) {
625631
// probably there is a function call insede the expression
626632
start = ts.offset();

php/php.editor/src/org/netbeans/modules/php/editor/lexer/utils/LexerUtils.java

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,41 @@
1818
*/
1919
package org.netbeans.modules.php.editor.lexer.utils;
2020

21+
import java.util.Collection;
22+
import java.util.List;
23+
import java.util.Set;
24+
import org.netbeans.api.annotations.common.NullAllowed;
2125
import org.netbeans.api.lexer.Token;
2226
import org.netbeans.api.lexer.TokenUtilities;
2327
import org.netbeans.modules.php.editor.lexer.PHPTokenId;
2428

2529
public final class LexerUtils {
2630

31+
private static final Collection<PHPTokenId> WS_COMMENT_TOKENS = Set.of(
32+
PHPTokenId.WHITESPACE,
33+
PHPTokenId.PHPDOC_COMMENT, PHPTokenId.PHPDOC_COMMENT_END, PHPTokenId.PHPDOC_COMMENT_START,
34+
PHPTokenId.PHP_COMMENT, PHPTokenId.PHP_COMMENT_END, PHPTokenId.PHP_COMMENT_START,
35+
PHPTokenId.PHP_LINE_COMMENT
36+
);
37+
private static final Collection<PHPTokenId> VISIBILITY_TOKENS = Set.of(
38+
PHPTokenId.PHP_PUBLIC,
39+
PHPTokenId.PHP_PROTECTED,
40+
PHPTokenId.PHP_PRIVATE
41+
);
42+
private static final Collection<PHPTokenId> SET_VISIBILITY_TOKENS = Set.of(
43+
PHPTokenId.PHP_PUBLIC_SET,
44+
PHPTokenId.PHP_PROTECTED_SET,
45+
PHPTokenId.PHP_PRIVATE_SET
46+
);
47+
private static final Collection<PHPTokenId> ALL_VISIBILITY_TOKENS = Set.of(
48+
PHPTokenId.PHP_PUBLIC,
49+
PHPTokenId.PHP_PROTECTED,
50+
PHPTokenId.PHP_PRIVATE,
51+
PHPTokenId.PHP_PUBLIC_SET,
52+
PHPTokenId.PHP_PROTECTED_SET,
53+
PHPTokenId.PHP_PRIVATE_SET
54+
);
55+
2756
private LexerUtils() {
2857
}
2958

@@ -46,4 +75,149 @@ public static boolean hasCurlyOpen(Token<? extends PHPTokenId> token) {
4675
public static boolean isDollarCurlyOpen(Token<? extends PHPTokenId> token) {
4776
return token.id() == PHPTokenId.PHP_TOKEN && TokenUtilities.textEquals(token.text(), "${"); // NOI18N
4877
}
78+
79+
/**
80+
* Check whether a token is the open parenthesis ("(").
81+
*
82+
* @param token a token
83+
* @return {@code true} if a token is "(", {@code false} otherwise
84+
*/
85+
public static boolean isOpenParen(Token<? extends PHPTokenId> token) {
86+
return token.id() == PHPTokenId.PHP_TOKEN && TokenUtilities.textEquals(token.text(), "("); // NOI18N
87+
}
88+
89+
/**
90+
* Check whether a token is the close parenthesis (")").
91+
*
92+
* @param token a token
93+
* @return {@code true} if a token is ")", {@code false} otherwise
94+
*/
95+
public static boolean isCloseParen(Token<? extends PHPTokenId> token) {
96+
return token.id() == PHPTokenId.PHP_TOKEN && TokenUtilities.textEquals(token.text(), ")"); // NOI18N
97+
}
98+
99+
/**
100+
* Check whether a token is the open bracket ("[").
101+
*
102+
* @param token a token
103+
* @return {@code true} if a token is "[", {@code false} otherwise
104+
*/
105+
public static boolean isOpenBracket(Token<? extends PHPTokenId> token) {
106+
return token.id() == PHPTokenId.PHP_TOKEN && TokenUtilities.textEquals(token.text(), "["); // NOI18N
107+
}
108+
109+
/**
110+
* Check whether a token is the close bracket ("]").
111+
*
112+
* @param token a token
113+
* @return {@code true} if a token is "]", {@code false} otherwise
114+
*/
115+
public static boolean isCloseBracket(Token<? extends PHPTokenId> token) {
116+
return token.id() == PHPTokenId.PHP_TOKEN && TokenUtilities.textEquals(token.text(), "]"); // NOI18N
117+
}
118+
119+
/**
120+
* Check whether a token is the comma (",").
121+
*
122+
* @param token a token
123+
* @return {@code true} if a token is ",", {@code false} otherwise
124+
*/
125+
public static boolean isComma(Token<? extends PHPTokenId> token) {
126+
return token.id() == PHPTokenId.PHP_TOKEN && TokenUtilities.textEquals(token.text(), ","); // NOI18N
127+
}
128+
129+
/**
130+
* Check whether a token is the colon (":").
131+
*
132+
* @param token a token
133+
* @return {@code true} if a token is ":", {@code false} otherwise
134+
*/
135+
public static boolean isColon(Token<? extends PHPTokenId> token) {
136+
return token.id() == PHPTokenId.PHP_TOKEN && TokenUtilities.textEquals(token.text(), ":"); // NOI18N
137+
}
138+
139+
/**
140+
* Check whether a token is the colon (":").
141+
*
142+
* @param token a token
143+
* @return {@code true} if a token is ":", {@code false} otherwise
144+
*/
145+
public static boolean isEqual(Token<? extends PHPTokenId> token) {
146+
return token.id() == PHPTokenId.PHP_OPERATOR && TokenUtilities.textEquals(token.text(), "="); // NOI18N
147+
}
148+
149+
/**
150+
* Check whether a token is the double arrow operator ("=>").
151+
*
152+
* @param token a token
153+
* @return {@code true} if a token is "=>", {@code false} otherwise
154+
*/
155+
public static boolean isDoubleArrow(Token<? extends PHPTokenId> token) {
156+
return token.id() == PHPTokenId.PHP_OPERATOR && TokenUtilities.textEquals(token.text(), "=>"); // NOI18N
157+
}
158+
159+
/**
160+
* Check whether a token is a whitespace or a comments.
161+
*
162+
* @param token a token
163+
* @return {@code true} if a token is a whitespace or a comment,
164+
* {@code false} otherwise
165+
*/
166+
public static boolean isWhitespaceOrCommentToken(Token<? extends PHPTokenId> token) {
167+
return token == null ? false : WS_COMMENT_TOKENS.contains(token.id());
168+
}
169+
170+
/**
171+
* Check whether a token is a visibility token ({@code public},
172+
* {@code protected}, {@code private}).
173+
*
174+
* @param token a token can be {@code null}
175+
* @return {@code true} if it is a visibility token, {@code false} otherwise
176+
*/
177+
public static boolean isVisibilityToken(@NullAllowed Token<? extends PHPTokenId> token) {
178+
return token == null ? false : VISIBILITY_TOKENS.contains(token.id());
179+
}
180+
181+
/**
182+
* Check whether a token is a set visibility token ({@code public(set)},
183+
* {@code protected(set)}, {@code private(set)}).
184+
*
185+
* @param token a token can be {@code null}
186+
* @return {@code true} if it is a set visibility token, {@code false}
187+
* otherwise
188+
*/
189+
public static boolean isSetVisibilityToken(@NullAllowed Token<? extends PHPTokenId> token) {
190+
return token == null ? false : SET_VISIBILITY_TOKENS.contains(token.id());
191+
}
192+
193+
/**
194+
* Check whether a token is one of all visibility tokens ({@code public},
195+
* {@code protected}, {@code private}), ({@code public(set)},
196+
* {@code protected(set)}, {@code private(set)}).
197+
*
198+
* @param token a token can be {@code null}
199+
* @return {@code true} if it is one of all visibility tokens, {@code false}
200+
* otherwise
201+
*/
202+
public static boolean isGetOrSetVisibilityToken(@NullAllowed Token<? extends PHPTokenId> token) {
203+
return token == null ? false : ALL_VISIBILITY_TOKENS.contains(token.id());
204+
}
205+
206+
/**
207+
* Get whitespace and comment token ids.
208+
*
209+
* @return whitespace and comment token ids.
210+
*/
211+
public static List<PHPTokenId> getWSCommentTokens() {
212+
return List.copyOf(WS_COMMENT_TOKENS);
213+
}
214+
215+
/**
216+
* Get all visibility token ids.
217+
*
218+
* @return all visibility token ids
219+
*/
220+
public static List<PHPTokenId> getAllVisibilityTokens() {
221+
return List.copyOf(ALL_VISIBILITY_TOKENS);
222+
}
49223
}

0 commit comments

Comments
 (0)