From aa919a41f0a8151ca90f25c88c706970a68fcbbf Mon Sep 17 00:00:00 2001 From: Junichi Yamamoto Date: Sun, 31 Dec 2023 20:18:01 +0900 Subject: [PATCH] Add the `IncorrectStaticContextHintError` as a new hint #6703 - https://github.com/apache/netbeans/issues/6703 - Show errors when `$this` is used in static context(static methods, static closures, static arrow functions) - Add unit tests Note: Check only $this in TypeDeclarations because Frameworks may use `$this` in the global scope. e.g. CakePHP framework can use `$this`(e.g. `$this->Html->link()`) in the global scope of view files. (see: https://book.cakephp.org/5/en/views.html) Example: ```php class Example { private int $field = 1; public static function staticMethod(): void { $this->field; // error } public function method(): void { $staticClosure = static function() { var_dump($this); // error }; $staticArrow = static fn() => $this; // error echo $this->field; } } ``` --- .../modules/php/editor/CodeUtils.java | 1 + .../modules/php/editor/resources/layer.xml | 1 + .../IncorrectStaticContextHintError.java | 274 ++++++++++++++++++ ...stStaticClosureAndArrowFunctionInClass.php | 156 ++++++++++ ...StaticClosureAndArrowFunctionInClass.hints | 72 +++++ ...estStaticClosureAndArrowFunctionInEnum.php | 115 ++++++++ ...tStaticClosureAndArrowFunctionInEnum.hints | 54 ++++ ...stStaticClosureAndArrowFunctionInTrait.php | 117 ++++++++ ...StaticClosureAndArrowFunctionInTrait.hints | 54 ++++ .../testThisVariables.php | 76 +++++ ...tThisVariables.php.testThisVariables.hints | 15 + .../IncorrectStaticContextHintErrorTest.java | 51 ++++ 12 files changed, 986 insertions(+) create mode 100644 php/php.editor/src/org/netbeans/modules/php/editor/verification/IncorrectStaticContextHintError.java create mode 100644 php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInClass.php create mode 100644 php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInClass.php.testStaticClosureAndArrowFunctionInClass.hints create mode 100644 php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInEnum.php create mode 100644 php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInEnum.php.testStaticClosureAndArrowFunctionInEnum.hints create mode 100644 php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInTrait.php create mode 100644 php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInTrait.php.testStaticClosureAndArrowFunctionInTrait.hints create mode 100644 php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testThisVariables.php create mode 100644 php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testThisVariables.php.testThisVariables.hints create mode 100644 php/php.editor/test/unit/src/org/netbeans/modules/php/editor/verification/IncorrectStaticContextHintErrorTest.java diff --git a/php/php.editor/src/org/netbeans/modules/php/editor/CodeUtils.java b/php/php.editor/src/org/netbeans/modules/php/editor/CodeUtils.java index cfdadb5f43a9..03184cd4361e 100644 --- a/php/php.editor/src/org/netbeans/modules/php/editor/CodeUtils.java +++ b/php/php.editor/src/org/netbeans/modules/php/editor/CodeUtils.java @@ -94,6 +94,7 @@ public final class CodeUtils { public static final String OVERRIDE_ATTRIBUTE = "#[" + OVERRIDE_ATTRIBUTE_FQ_NAME + "]"; // NOI18N public static final String EMPTY_STRING = ""; // NOI18N public static final String NEW_LINE = "\n"; // NOI18N + public static final String THIS_VARIABLE = "$this"; // NOI18N public static final Pattern WHITE_SPACES_PATTERN = Pattern.compile("\\s+"); // NOI18N public static final Pattern SPLIT_TYPES_PATTERN = Pattern.compile("[()|&]+"); // NOI18N diff --git a/php/php.editor/src/org/netbeans/modules/php/editor/resources/layer.xml b/php/php.editor/src/org/netbeans/modules/php/editor/resources/layer.xml index b3aecee6890a..e907da2c5669 100644 --- a/php/php.editor/src/org/netbeans/modules/php/editor/resources/layer.xml +++ b/php/php.editor/src/org/netbeans/modules/php/editor/resources/layer.xml @@ -433,6 +433,7 @@ + diff --git a/php/php.editor/src/org/netbeans/modules/php/editor/verification/IncorrectStaticContextHintError.java b/php/php.editor/src/org/netbeans/modules/php/editor/verification/IncorrectStaticContextHintError.java new file mode 100644 index 000000000000..00409bea05ef --- /dev/null +++ b/php/php.editor/src/org/netbeans/modules/php/editor/verification/IncorrectStaticContextHintError.java @@ -0,0 +1,274 @@ +/* + * 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. + */ +package org.netbeans.modules.php.editor.verification; + +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.netbeans.modules.csl.api.Hint; +import org.netbeans.modules.csl.api.HintFix; +import org.netbeans.modules.csl.api.OffsetRange; +import org.netbeans.modules.csl.spi.support.CancelSupport; +import org.netbeans.modules.php.editor.CodeUtils; +import org.netbeans.modules.php.editor.model.FileScope; +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.BodyDeclaration; +import org.netbeans.modules.php.editor.parser.astnodes.ClassDeclaration; +import org.netbeans.modules.php.editor.parser.astnodes.ClassInstanceCreation; +import org.netbeans.modules.php.editor.parser.astnodes.EnumDeclaration; +import org.netbeans.modules.php.editor.parser.astnodes.LambdaFunctionDeclaration; +import org.netbeans.modules.php.editor.parser.astnodes.MethodDeclaration; +import org.netbeans.modules.php.editor.parser.astnodes.TraitDeclaration; +import org.netbeans.modules.php.editor.parser.astnodes.Variable; +import org.netbeans.modules.php.editor.parser.astnodes.visitors.DefaultVisitor; +import org.openide.filesystems.FileObject; +import org.openide.util.NbBundle; + +/** + * Handle incorrect static context statements. + */ +public class IncorrectStaticContextHintError extends HintErrorRule { + + private static final boolean DISABLED = Boolean.getBoolean("nb.php.editor.disableIncorrectStaticContextHintError"); // NOI18N + private FileObject fileObject; + + @Override + @NbBundle.Messages("IncorrectStaticContextHintError.displayName=Incorrect Statements in Static Context") + public String getDisplayName() { + return Bundle.IncorrectStaticContextHintError_displayName(); + } + + @Override + public void invoke(PHPRuleContext context, List hints) { + if (DISABLED) { + return; + } + PHPParseResult phpParseResult = (PHPParseResult) context.parserResult; + if (phpParseResult.getProgram() == null) { + return; + } + FileScope fileScope = context.fileScope; + fileObject = phpParseResult.getSnapshot().getSource().getFileObject(); + if (fileScope != null && fileObject != null) { + if (CancelSupport.getDefault().isCancelled()) { + return; + } + CheckVisitor checkVisitor = new CheckVisitor(); + phpParseResult.getProgram().accept(checkVisitor); + Set incorrectThisVariables = checkVisitor.getThisVariableInStaticContext(); + addIncorrectThisHints(incorrectThisVariables, hints); + } + } + + @NbBundle.Messages("IncorrectStaticContextHintError.incorrectThis=Cannot use \"$this\" in static context.") + private void addIncorrectThisHints(Set incorrectThisVariables, List hints) { + // e.g. + // public static function staticMethod(): int { + // return $this->field; + // } ^^^^^ + for (Variable thisVariable : incorrectThisVariables) { + if (CancelSupport.getDefault().isCancelled()) { + return; + } + addHint(thisVariable, Bundle.IncorrectStaticContextHintError_incorrectThis(), hints); + } + } + + private void addHint(ASTNode node, String description, List hints) { + addHint(node, description, hints, Collections.emptyList()); + } + + private void addHint(ASTNode node, String description, List hints, List fixes) { + hints.add(new Hint( + this, + description, + fileObject, + new OffsetRange(node.getStartOffset(), node.getEndOffset()), + fixes, + 500 + )); + } + + //~ Inner classes + private static final class CheckVisitor extends DefaultVisitor { + + private boolean isInTypeDeclaration = false; + private boolean isInStaticMethod = false; + private ASTNode firstStaticClosure = null; + private final ArrayDeque firstStaticClosureStack = new ArrayDeque<>(); + private final ArrayDeque isInStaticMethodStack = new ArrayDeque<>(); + private final Set thisVariableInStaticContext = new HashSet<>(); + + @Override + public void visit(MethodDeclaration node) { + if (CancelSupport.getDefault().isCancelled()) { + return; + } + isInStaticMethod = BodyDeclaration.Modifier.isStatic(node.getModifier()); + super.visit(node); + isInStaticMethod = false; + } + + @Override + public void visit(LambdaFunctionDeclaration node) { + if (CancelSupport.getDefault().isCancelled()) { + return; + } + // e.g. + // $closure = function() { + // $this->something; // ok + // $nestedStaticClosure = static function() { + // $this->something; // error + // $nestedClosure = function() { + // $this->something; // error + // }; + // }; + // }; + if (node.isStatic()) { + if (firstStaticClosure == null) { + firstStaticClosure = node; + } + } + super.visit(node); + if (firstStaticClosure == node) { + firstStaticClosure = null; + } + } + + @Override + public void visit(ArrowFunctionDeclaration node) { + if (CancelSupport.getDefault().isCancelled()) { + return; + } + if (node.isStatic()) { + if (firstStaticClosure == null) { + firstStaticClosure = node; + } + } + super.visit(node); + if (firstStaticClosure == node) { + firstStaticClosure = null; + } + } + + @Override + public void visit(ClassDeclaration node) { + if (CancelSupport.getDefault().isCancelled()) { + return; + } + isInTypeDeclaration = true; + super.visit(node); + isInTypeDeclaration = false; + } + + @Override + public void visit(EnumDeclaration node) { + if (CancelSupport.getDefault().isCancelled()) { + return; + } + isInTypeDeclaration = true; + super.visit(node); + isInTypeDeclaration = false; + } + + @Override + public void visit(TraitDeclaration node) { + if (CancelSupport.getDefault().isCancelled()) { + return; + } + isInTypeDeclaration = true; + super.visit(node); + isInTypeDeclaration = false; + } + + @Override + public void visit(ClassInstanceCreation node) { + if (CancelSupport.getDefault().isCancelled()) { + return; + } + // static method may have anonymous classes + // e.g. + // public static function getExample(): int { + // self::staticMethod($this); // error + // $anon = new class() { + // private int $field = 1; + // + // public static function nestedStaticMethod(): void { + // echo $this->field; // error + // } + // + // public function nestedMethod(): void { + // echo $this->field; // ok + // } + // }; + // return $this->test; // error + // } + boolean isInPreviousTypeDeclaration = this.isInTypeDeclaration; + boolean hasFirstStaticClosure = firstStaticClosure != null; + if (node.isAnonymous()) { + isInTypeDeclaration = true; + isInStaticMethodStack.push(isInStaticMethod); + isInStaticMethod = false; + if (hasFirstStaticClosure) { + firstStaticClosureStack.push(firstStaticClosure); + firstStaticClosure = null; + } + } + super.visit(node); + if (node.isAnonymous()) { + isInTypeDeclaration = isInPreviousTypeDeclaration; + isInStaticMethod = isInStaticMethodStack.pop(); + if (hasFirstStaticClosure) { + firstStaticClosure = firstStaticClosureStack.pop(); + } + } + } + + @Override + public void visit(Variable node) { + if (CancelSupport.getDefault().isCancelled()) { + return; + } + if (isInStaticContext()) { + String variableName = CodeUtils.extractVariableName(node); + if (CodeUtils.THIS_VARIABLE.equals(variableName)) { + thisVariableInStaticContext.add(node); + } + } + super.visit(node); + } + + private boolean isInStaticContext() { + // NOTE: + // Check only $this in TypeDeclarations because Frameworks may use `$this` in the global scope + // e.g. CakePHP framework can use `$this` in the global scope of view files + // see: https://book.cakephp.org/5/en/views.html + return isInStaticMethod + || (isInTypeDeclaration && firstStaticClosure != null); + } + + public Set getThisVariableInStaticContext() { + return Collections.unmodifiableSet(thisVariableInStaticContext); + } + } +} diff --git a/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInClass.php b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInClass.php new file mode 100644 index 000000000000..1cff6383a2da --- /dev/null +++ b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInClass.php @@ -0,0 +1,156 @@ +test; // error + }; + $staticClosure = static function() { + echo $this->test; // error + $anon = new class() { + private int $test = 1; + public function method() { + $closure = function() { + $this->test; // ok + }; + $staticClosure = static function() { + $this->test; // error + $closure2 = function() { + $this->test; // error + }; + }; + $staticClosure(); + return $this; // ok + } + public static function staticMethod() { + $this->method(); // error + } + }; + }; + $staticClosure(); + $arrowFunction = fn() => $this; // error + $staticArrowFunction = static fn() => $this; // error + $staticArrowFunction()->test; + } + + public function method(): void { + $this->test; // ok + $closure = function() { + $this->test; // ok + $nestedStaticClosure = static function() { + $this->test; // error + }; + }; + $staticClosure = static function() { + echo $this->test; // error + $closuer2 = function() { + $this->test; // error + }; + $anon = new class() { + private int $test = 1; + public function method() { + $closure = function() { + $this->test; // ok + }; + $staticClosure = static function() { + $this->test; // error + $closure2 = function() { + $this->test; // error + }; + }; + $staticClosure(); + return $this; // ok + } + public static function staticMethod() { + $this->method(); // error + } + }; + $nestedArrow = fn() => $this; // error + }; + $staticClosure(); + $arrowFunction = fn() => $this; // ok + $staticArrowFunction = static fn() => $this; // error + $staticArrowFunction = static fn() => $anon = new class() { + private int $test = 1; + public function method() { + $closure = function () { + $this->test; // ok + }; + $staticClosure = static function () { + $this->test; // error + $closure2 = function () { + $this->test; // error + }; + }; + $staticClosure(); + return $this; // ok + } + + public static function staticMethod() { + $this->method(); // error + } + }; + $staticArrowFunction()->test; + $this->test; // ok + } +} + +// anonymous class +$anon = new class() { + private int $test = 1; + + public static function test(): void { + $closure = function() { + echo $this->test; // error + }; + $staticClosure = static function() { + echo $this->test; // error + }; + $staticClosure(); + $arrowFunction = fn() => $this; // error + $staticArrowFunction = static fn() => $this; // error + $staticArrowFunction()->test; + } + + public function method(): void { + $closure = function() { + echo $this->test; // ok + }; + $this->test(); // ok + $staticClosure = static function() { + echo $this->test; // error + }; + $staticClosure(); + $arrowFunction = fn() => $this; // ok + $staticArrowFunction = static fn() => $this; // error + $staticArrowFunction()->test; + } +}; + +$staticClosure = static function(): void { + var_dump($this); // ok(ignore) +}; +$staticClosure(); +$staticArrow = static fn(): object => var_dump($this); // ok(ignore) +$staticArrow(); diff --git a/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInClass.php.testStaticClosureAndArrowFunctionInClass.hints b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInClass.php.testStaticClosureAndArrowFunctionInClass.hints new file mode 100644 index 000000000000..c3e1b3451fe8 --- /dev/null +++ b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInClass.php.testStaticClosureAndArrowFunctionInClass.hints @@ -0,0 +1,72 @@ + echo $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + echo $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->method(); // error + ----- +HINT:Cannot use "$this" in static context. + $arrowFunction = fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + $staticArrowFunction = static fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + echo $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->method(); // error + ----- +HINT:Cannot use "$this" in static context. + $nestedArrow = fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + $staticArrowFunction = static fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->method(); // error + ----- +HINT:Cannot use "$this" in static context. + echo $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + echo $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $arrowFunction = fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + $staticArrowFunction = static fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + echo $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $staticArrowFunction = static fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. diff --git a/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInEnum.php b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInEnum.php new file mode 100644 index 000000000000..e7144c5443d1 --- /dev/null +++ b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInEnum.php @@ -0,0 +1,115 @@ +name; // error + }; + $staticClosure = static function() { + echo $this->name; // error + $anon = new class() { + private int $test = 1; + public function method() { + $closure = function() { + $this->test; // ok + }; + $staticClosure = static function() { + $this->test; // error + $closure2 = function() { + $this->test; // error + }; + }; + $staticClosure(); + return $this; // ok + } + public static function staticMethod() { + $this->method(); // error + } + }; + }; + $staticClosure(); + $arrowFunction = fn() => $this; // error + $staticArrowFunction = static fn() => $this; // error + $staticArrowFunction()->name; + } + + public function method(): void { + $this->name; // ok + $closure = function() { + $this->name; // ok + $nestedStaticClosure = static function() { + $this->name; // error + }; + }; + $staticClosure = static function() { + echo $this->name; // error + $closuer2 = function() { + $this->name; // error + }; + $anon = new class() { + private int $test = 1; + public function method() { + $closure = function() { + $this->test; // ok + }; + $staticClosure = static function() { + $this->test; // error + $closure2 = function() { + $this->test; // error + }; + }; + $staticClosure(); + return $this; // ok + } + public static function staticMethod() { + $this->method(); // error + } + }; + $nestedArrow = fn() => $this; // error + }; + $staticClosure(); + $arrowFunction = fn() => $this; // ok + $staticArrowFunction = static fn() => $this; // error + $staticArrowFunction = static fn() => $anon = new class() { + private int $test = 1; + public function method() { + $closure = function () { + $this->test; // ok + }; + $staticClosure = static function () { + $this->test; // error + $closure2 = function () { + $this->test; // error + }; + }; + $staticClosure(); + return $this; // ok + } + + public static function staticMethod() { + $this->method(); // error + } + }; + $staticArrowFunction()->name; + $this->name; // ok + } +} diff --git a/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInEnum.php.testStaticClosureAndArrowFunctionInEnum.hints b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInEnum.php.testStaticClosureAndArrowFunctionInEnum.hints new file mode 100644 index 000000000000..e958500948ea --- /dev/null +++ b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInEnum.php.testStaticClosureAndArrowFunctionInEnum.hints @@ -0,0 +1,54 @@ + echo $this->name; // error + ----- +HINT:Cannot use "$this" in static context. + echo $this->name; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->method(); // error + ----- +HINT:Cannot use "$this" in static context. + $arrowFunction = fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + $staticArrowFunction = static fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + $this->name; // error + ----- +HINT:Cannot use "$this" in static context. + echo $this->name; // error + ----- +HINT:Cannot use "$this" in static context. + $this->name; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->method(); // error + ----- +HINT:Cannot use "$this" in static context. + $nestedArrow = fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + $staticArrowFunction = static fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->method(); // error + ----- +HINT:Cannot use "$this" in static context. diff --git a/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInTrait.php b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInTrait.php new file mode 100644 index 000000000000..cd4e752d8357 --- /dev/null +++ b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInTrait.php @@ -0,0 +1,117 @@ +test; // error + }; + $staticClosure = static function() { + echo $this->test; // error + $anon = new class() { + private int $test = 1; + public function method() { + $closure = function() { + $this->test; // ok + }; + $staticClosure = static function() { + $this->test; // error + $closure2 = function() { + $this->test; // error + }; + }; + $staticClosure(); + return $this; // ok + } + public static function staticMethod() { + $this->method(); // error + } + }; + }; + $staticClosure(); + $arrowFunction = fn() => $this; // error + $staticArrowFunction = static fn() => $this; // error + $staticArrowFunction()->test; + } + + public function method(): void { + $this->test; // ok + $closure = function() { + $this->test; // ok + $nestedStaticClosure = static function() { + $this->test; // error + }; + }; + $staticClosure = static function() { + echo $this->test; // error + $closuer2 = function() { + $this->test; // error + }; + $anon = new class() { + private int $test = 1; + public function method() { + $closure = function() { + $this->test; // ok + }; + $staticClosure = static function() { + $this->test; // error + $closure2 = function() { + $this->test; // error + }; + }; + $staticClosure(); + return $this; // ok + } + public static function staticMethod() { + $this->method(); // error + } + }; + $nestedArrow = fn() => $this; // error + }; + $staticClosure(); + $arrowFunction = fn() => $this; // ok + $staticArrowFunction = static fn() => $this; // error + $staticArrowFunction = static fn() => $anon = new class() { + private int $test = 1; + public function method() { + $closure = function () { + $this->test; // ok + }; + $staticClosure = static function () { + $this->test; // error + $closure2 = function () { + $this->test; // error + }; + }; + $staticClosure(); + return $this; // ok + } + + public static function staticMethod() { + $this->method(); // error + } + }; + $staticArrowFunction()->test; + $this->test; // ok + } +} diff --git a/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInTrait.php.testStaticClosureAndArrowFunctionInTrait.hints b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInTrait.php.testStaticClosureAndArrowFunctionInTrait.hints new file mode 100644 index 000000000000..49e91bf48660 --- /dev/null +++ b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testStaticClosureAndArrowFunctionInTrait.php.testStaticClosureAndArrowFunctionInTrait.hints @@ -0,0 +1,54 @@ + echo $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + echo $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->method(); // error + ----- +HINT:Cannot use "$this" in static context. + $arrowFunction = fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + $staticArrowFunction = static fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + echo $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->method(); // error + ----- +HINT:Cannot use "$this" in static context. + $nestedArrow = fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + $staticArrowFunction = static fn() => $this; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + $this->method(); // error + ----- +HINT:Cannot use "$this" in static context. diff --git a/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testThisVariables.php b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testThisVariables.php new file mode 100644 index 000000000000..04a2b4c03657 --- /dev/null +++ b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testThisVariables.php @@ -0,0 +1,76 @@ +field; // ok + } + + public static function nestedStaticMethod(): void { + echo $this->field; // error + } + + }; + return $this->test; // error + } + + + public static function staticMethod(ThisInStaticContext $test): void { + } + + public function method(): void { + $this->test; // ok + $anon = new class() { + private int $field = 1; + + public static function nestedStaticMethod(): void { + echo $this->field; // error + } + + public function nestedMethod(): void { + echo $this->field; // ok + } + }; + } +} +echo ThisInStaticContext::getTest(); + +// anonymous class +$anon = new class() { + private int $test = 1; + + public static function test(): void { + $this->test; // error + } + + public function method(): void { + $this->test(); // ok + } +}; + +$this->HTML->something(); // ok(ignore) diff --git a/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testThisVariables.php.testThisVariables.hints b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testThisVariables.php.testThisVariables.hints new file mode 100644 index 000000000000..64a42b3d3a9a --- /dev/null +++ b/php/php.editor/test/unit/data/testfiles/verification/IncorrectStaticContextHintError/testThisVariables.php.testThisVariables.hints @@ -0,0 +1,15 @@ + self::staticMethod($this); // error + ----- +HINT:Cannot use "$this" in static context. + echo $this->field; // error + ----- +HINT:Cannot use "$this" in static context. + return $this->test; // error + ----- +HINT:Cannot use "$this" in static context. + echo $this->field; // error + ----- +HINT:Cannot use "$this" in static context. + $this->test; // error + ----- +HINT:Cannot use "$this" in static context. diff --git a/php/php.editor/test/unit/src/org/netbeans/modules/php/editor/verification/IncorrectStaticContextHintErrorTest.java b/php/php.editor/test/unit/src/org/netbeans/modules/php/editor/verification/IncorrectStaticContextHintErrorTest.java new file mode 100644 index 000000000000..611300431415 --- /dev/null +++ b/php/php.editor/test/unit/src/org/netbeans/modules/php/editor/verification/IncorrectStaticContextHintErrorTest.java @@ -0,0 +1,51 @@ +/* + * 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. + */ +package org.netbeans.modules.php.editor.verification; + +public class IncorrectStaticContextHintErrorTest extends PHPHintsTestBase { + + public IncorrectStaticContextHintErrorTest(String testName) { + super(testName); + } + + @Override + protected String getTestDirectory() { + return TEST_DIRECTORY + "IncorrectStaticContextHintError/"; + } + + public void testThisVariables() throws Exception { + checkHints("testThisVariables.php"); + } + + public void testStaticClosureAndArrowFunctionInClass() throws Exception { + checkHints("testStaticClosureAndArrowFunctionInClass.php"); + } + + public void testStaticClosureAndArrowFunctionInTrait() throws Exception { + checkHints("testStaticClosureAndArrowFunctionInTrait.php"); + } + + public void testStaticClosureAndArrowFunctionInEnum() throws Exception { + checkHints("testStaticClosureAndArrowFunctionInEnum.php"); + } + + private void checkHints(String fileName) throws Exception { + checkHints(new IncorrectStaticContextHintError(), fileName); + } +}