diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..43176c9c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,168 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is an IntelliJ IDEA/PhpStorm plugin that provides PHP annotation and PHP 8 Attribute support. The plugin extends the IDE to recognize annotation classes marked with `@Annotation`, provides code completion, navigation, inspections, and integrates with Doctrine ORM and Symfony frameworks. + +**Plugin ID**: `de.espend.idea.php.annotation` + +## Build Commands + +### Build the plugin +```bash +./gradlew buildPlugin +``` +The plugin ZIP will be in `build/distributions/`. + +### Run tests +```bash +./gradlew test +``` + +### Run a single test class +```bash +./gradlew test --tests "de.espend.idea.php.annotation.tests.AnnotationStubIndexTest" +``` + +### Run a single test method +```bash +./gradlew test --tests "de.espend.idea.php.annotation.tests.AnnotationStubIndexTest.testThatAnnotationClassIsInIndex" +``` + +### Run the plugin in a test IDE instance +```bash +./gradlew runIde +``` + +### Verify plugin compatibility +```bash +./gradlew verifyPlugin +``` + +### Clean build +```bash +./gradlew clean buildPlugin +``` + +## Architecture + +### Extension Point System + +The plugin's core architecture is based on extension points that allow both internal components and external plugins (like Symfony Support or PHP Toolbox) to extend functionality. + +**Key extension point interfaces** (in `src/main/java/de/espend/idea/php/annotation/extension/`): + +- **PhpAnnotationCompletionProvider**: Provides code completion for annotation property values +- **PhpAnnotationReferenceProvider**: Provides references and navigation for annotation elements +- **PhpAnnotationDocTagGotoHandler**: Handles "Go to Declaration" for annotation tags +- **PhpAnnotationDocTagAnnotator**: Provides custom highlighting/error annotations for doc tags +- **PhpAnnotationGlobalNamespacesLoader**: Loads global annotation namespace mappings +- **PhpAnnotationVirtualProperties**: Provides virtual properties for annotation classes +- **PhpAnnotationUseAlias**: Maps custom class aliases (e.g., "ORM" => "Doctrine\\ORM\\Mapping") + +Extension points are registered in `src/main/resources/META-INF/plugin.xml` and can be used by other plugins. + +### Indexing System + +The plugin uses two main file-based indices for fast lookup: + +- **AnnotationStubIndex**: Indexes all PHP classes marked with `@Annotation` in their doc block +- **AnnotationUsageIndex**: Indexes where annotations are used in the codebase + +These indices power the navigation features (find usages, line markers) and are updated automatically when files change. + +### Module Organization + +- **annotator/**: Provides inline error highlighting and warnings +- **completion/**: Code completion contributors and providers +- **dict/**: Data transfer objects and dictionary classes +- **doctrine/**: Doctrine ORM-specific features (property generators, repository class handling, column type support) +- **extension/**: Extension point interfaces and parameters +- **inspection/**: Code inspections (missing imports, deprecated usage, etc.) +- **navigation/**: Navigation handlers and line marker providers +- **pattern/**: PSI pattern matching for identifying annotation contexts +- **reference/**: Reference contributors for navigation and "Find Usages" +- **symfony/**: Symfony-specific annotation support +- **toolbox/**: PHP Toolbox integration +- **ui/**: Settings forms and configuration UI +- **util/**: Utility classes, particularly `AnnotationUtil` which contains core logic for annotation detection and processing + +### Dual Support: DocBlock Annotations and PHP 8 Attributes + +The plugin supports both: +- **DocBlock annotations**: `/** @Route("/path") */` +- **PHP 8 Attributes**: `#[Route('/path')]` + +Extension points work transparently with both formats, allowing feature implementations to support both simultaneously. + +## Key Concepts + +### Annotation Detection + +Classes are recognized as annotation classes if they have `@Annotation` in their doc block: +```php +/** + * @Annotation + * @Target("METHOD", "CLASS") + */ +class Route { + public $path; +} +``` + +### Target Filtering + +The `@Target` annotation restricts where annotations can be used (METHOD, CLASS, PROPERTY, ALL). The plugin uses this for completion filtering. + +### Property Type Detection + +Annotation properties support type hints via doc comments: +- Simple types: `@var string`, `@var bool` +- Arrays: `@var array` +- Enums: `@Enum({"GET", "POST", "PUT"})` +- Mixed: `@var mixed|string|bool` + +### Use Alias System + +The plugin supports configurable namespace aliases (Settings > PHP > Annotations / Attributes > Use Alias): +- Maps short names to FQCNs: `ORM` => `Doctrine\ORM\Mapping` +- Auto-import suggestions use these mappings +- External plugins can provide their own mappings via `PhpAnnotationUseAlias` extension point + +## Test Structure + +Tests extend `AnnotationLightCodeInsightFixtureTestCase` which provides a test fixture framework. + +Test data files are in `src/test/java/de/espend/idea/php/annotation/tests/fixtures/`. + +Tests use the `myFixture` field to: +- Copy test PHP files: `myFixture.copyFileToProject("classes.php")` +- Check completion: `myFixture.completeBasic()` +- Navigate: `myFixture.getReferenceAtCaretPosition()` +- Assert index contents: `assertIndexContains(AnnotationStubIndex.KEY, "My\\Class")` + +## Configuration + +Build configuration is in `gradle.properties`: +- `platformVersion`: Target IntelliJ/PhpStorm version (currently 2025.2.5) +- `pluginSinceBuild` / `pluginUntilBuild`: Supported IDE version range +- `javaVersion`: Java language level (21) + +The plugin requires these IntelliJ plugins as dependencies: +- `com.jetbrains.php` (PhpStorm PHP support) +- `com.jetbrains.twig` (Twig template support) +- `com.intellij.modules.json` (JSON support) + +Optional integration: +- `de.espend.idea.php.toolbox` (PHP Toolbox) + +## Publishing + +See `MAINTENANCE.md` for the full release process. Key steps: +1. Update `CHANGELOG.md` +2. Commit changes +3. Tag release: `git tag X.Y.Z` +4. Build: `./gradlew clean buildPlugin` +5. Publish: `IJ_TOKEN=yourtoken ./gradlew publishPlugin` diff --git a/README.md b/README.md index be4820fd..0a65a3e8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ IntelliJ IDEA - PhpStorm PHP Annotations / Attributes ========================== [![Build Status](https://github.com/Haehnchen/idea-php-annotation-plugin/actions/workflows/gradle.yml/badge.svg?branch=master)](https://github.com/Haehnchen/idea-php-annotation-plugin/actions/workflows/gradle.yml) +[![zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/Haehnchen/idea-php-annotation-plugin) [![Version](http://phpstorm.espend.de/badge/7320/version)](https://plugins.jetbrains.com/plugin/7320) [![Downloads](http://phpstorm.espend.de/badge/7320/downloads)](https://plugins.jetbrains.com/plugin/7320) [![Downloads last month](http://phpstorm.espend.de/badge/7320/last-month)](https://plugins.jetbrains.com/plugin/7320) diff --git a/build.gradle.kts b/build.gradle.kts index c93eb213..513cf627 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,8 +5,8 @@ fun properties(key: String) = project.findProperty(key).toString() plugins { id("java") - id("org.jetbrains.kotlin.jvm") version "2.0.0" - id("org.jetbrains.intellij.platform") version "2.4.0" + id("org.jetbrains.kotlin.jvm") version "2.2.21" + id("org.jetbrains.intellij.platform") version "2.10.4" id("org.jetbrains.changelog") version "1.3.1" id("org.jetbrains.qodana") version "0.1.13" } @@ -27,27 +27,46 @@ dependencies { intellijPlatform { val version = providers.gradleProperty("platformVersion") val type = providers.gradleProperty("platformType") - create(type, version, useInstaller = false) + create(type, version) { + useInstaller = false + useCache = true + } - bundledPlugins(properties("platformBundledPlugins").split(',').map(String::trim).filter(String::isNotEmpty)) - plugins(properties("platformPlugins").split(',').map(String::trim).filter(String::isNotEmpty)) + bundledPlugins("com.intellij.java", "com.jetbrains.plugins.webDeployment") + compatiblePlugins( + "com.jetbrains.php", + "com.jetbrains.twig", + "com.intellij.modules.json", + "de.espend.idea.php.toolbox" + ) testFramework(TestFrameworkType.Platform) testFramework(TestFrameworkType.Plugin.Java) } testImplementation("junit:junit:4.13.2") - testImplementation("org.junit.jupiter:junit-jupiter:5.8.2") - testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2") + testImplementation("org.junit.jupiter:junit-jupiter:5.11.4") + testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.11.4") + testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.11.4") } // Configure Gradle IntelliJ Plugin - read more: https://github.com/JetBrains/gradle-intellij-plugin intellijPlatform { + val version = providers.gradleProperty("platformVersion") + val type = providers.gradleProperty("platformType") + pluginConfiguration { name = properties("pluginName") } - instrumentCode = false - buildSearchableOptions = false + + pluginVerification { + ides { + create(type, version) { + useInstaller = false + useCache = true + } + } + } } // Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin @@ -72,7 +91,9 @@ tasks { targetCompatibility = it } withType { - kotlinOptions.jvmTarget = it + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(it)) + } } } @@ -85,31 +106,6 @@ tasks { sinceBuild.set(properties("pluginSinceBuild")) untilBuild.set(properties("pluginUntilBuild")) changeNotes.set(file("src/main/resources/META-INF/change-notes.html").readText().replace("", "").replace("", "")) - - // Get the latest available change notes from the changelog file - // changeNotes.set(provider { - // changelog.run { - // getOrNull(properties("pluginVersion")) ?: getLatest() - // }.toHTML() - // }) - } - - // Configure UI tests plugin - // Read more: https://github.com/JetBrains/intellij-ui-test-robot - intellijPlatformTesting.runIde.registering { - task { - jvmArgumentProviders += CommandLineArgumentProvider { - listOf( - "-Drobot-server.port=8082", - "-Dide.mac.message.dialogs.as.sheets=false", - "-Djb.privacy.policy.text=", - "-Djb.consents.confirmation.enabled=false", - ) - } - } - plugins { - robotServerPlugin() - } } signPlugin { @@ -119,18 +115,13 @@ tasks { } publishPlugin { - // dependsOn("patchChangelog") - token.set(System.getenv("PUBLISH_TOKEN")) - // pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3 - // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more: - // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel - // channels.set(listOf(properties("pluginVersion").split('-').getOrElse(1) { "default" }.split('.').listIterator())) + token.set(System.getenv("PUBLISH_TOKEN")) } test { // Support "setUp" like "BasePlatformTestCase::setUp" as valid test structure useJUnitPlatform { - includeEngines("junit-vintage") + includeEngines("junit-vintage", "junit-jupiter") } } } diff --git a/gradle.properties b/gradle.properties index 158bf41c..f953f63f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,25 +9,22 @@ pluginVersion = 12.0.1 # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html # for insight into build numbers and IntelliJ Platform versions. -pluginSinceBuild = 251 +pluginSinceBuild = 252 pluginUntilBuild = 299.* # IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties platformType = IU -platformVersion = 2025.1 +platformVersion = 2025.2.5 -# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html -# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 -platformBundledPlugins = com.intellij.java,com.jetbrains.plugins.webDeployment -platformPlugins = com.jetbrains.php:251.23774.318,com.jetbrains.twig:251.23774.318,de.espend.idea.php.toolbox:6.2.0,com.intellij.modules.json:251.23774.318 - -# Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3 +# Java language level used to compile sources and to generate the files for javaVersion = 21 # Gradle Releases -> https://github.com/gradle/gradle/releases -gradleVersion = 8.13 +gradleVersion = 9.2.1 # Opt-out flag for bundling Kotlin standard library. # See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details. # suppress inspection "UnusedProperty" kotlin.stdlib.default.dependency = false + +org.gradle.configuration-cache=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd491..9bbc975c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f853b1..23449a2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a42..faf93008 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f..9d21a218 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/src/main/java/de/espend/idea/php/annotation/completion/PhpAnnotationCompletionConfidence.java b/src/main/java/de/espend/idea/php/annotation/completion/PhpAnnotationCompletionConfidence.java index 49c0b211..cb88efe6 100644 --- a/src/main/java/de/espend/idea/php/annotation/completion/PhpAnnotationCompletionConfidence.java +++ b/src/main/java/de/espend/idea/php/annotation/completion/PhpAnnotationCompletionConfidence.java @@ -1,6 +1,7 @@ package de.espend.idea.php.annotation.completion; import com.intellij.codeInsight.completion.CompletionConfidence; +import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiWhiteSpace; @@ -21,7 +22,7 @@ public class PhpAnnotationCompletionConfidence extends CompletionConfidence { @NotNull @Override - public ThreeState shouldSkipAutopopup(@NotNull PsiElement contextElement, @NotNull PsiFile psiFile, int offset) { + public ThreeState shouldSkipAutopopup(@NotNull Editor editor, @NotNull PsiElement contextElement, @NotNull PsiFile psiFile, int offset) { if(!(psiFile instanceof PhpFile)) { return ThreeState.UNSURE; diff --git a/src/main/java/de/espend/idea/php/annotation/completion/insert/AnnotationTagInsertHandler.java b/src/main/java/de/espend/idea/php/annotation/completion/insert/AnnotationTagInsertHandler.java index dc83bb2c..a12beafa 100644 --- a/src/main/java/de/espend/idea/php/annotation/completion/insert/AnnotationTagInsertHandler.java +++ b/src/main/java/de/espend/idea/php/annotation/completion/insert/AnnotationTagInsertHandler.java @@ -31,7 +31,7 @@ public class AnnotationTagInsertHandler implements InsertHandler public void handleInsert(@NotNull InsertionContext context, @NotNull LookupElement lookupElement) { // "ORM\Entity" - if (lookupElement instanceof PhpClassAnnotationLookupElement lookupElement1 && ((PhpClassAnnotationLookupElement) lookupElement).getAlias() != null) { + if (lookupElement instanceof PhpClassAnnotationLookupElement lookupElement1 && lookupElement1.getAlias() != null) { PsiElement element = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset()); PhpPsiElement scopeForUseOperator = PhpCodeInsightUtil.findScopeForUseOperator(element); diff --git a/src/main/java/de/espend/idea/php/annotation/completion/insert/AttributeAliasInsertHandler.java b/src/main/java/de/espend/idea/php/annotation/completion/insert/AttributeAliasInsertHandler.java index e2f19b72..01f31d55 100644 --- a/src/main/java/de/espend/idea/php/annotation/completion/insert/AttributeAliasInsertHandler.java +++ b/src/main/java/de/espend/idea/php/annotation/completion/insert/AttributeAliasInsertHandler.java @@ -25,7 +25,7 @@ public class AttributeAliasInsertHandler implements InsertHandler public void handleInsert(@NotNull InsertionContext context, @NotNull LookupElement lookupElement) { // "ORM\Entity" - if (lookupElement instanceof PhpClassAnnotationLookupElement lookupElement1 && ((PhpClassAnnotationLookupElement) lookupElement).getAlias() != null) { + if (lookupElement instanceof PhpClassAnnotationLookupElement lookupElement1 && lookupElement1.getAlias() != null) { PsiElement element = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset()); PhpPsiElement scopeForUseOperator = PhpCodeInsightUtil.findScopeForUseOperator(element); diff --git a/src/main/java/de/espend/idea/php/annotation/ui/SettingsForm.java b/src/main/java/de/espend/idea/php/annotation/ui/SettingsForm.java index 26e20253..943a1177 100644 --- a/src/main/java/de/espend/idea/php/annotation/ui/SettingsForm.java +++ b/src/main/java/de/espend/idea/php/annotation/ui/SettingsForm.java @@ -1,16 +1,12 @@ package de.espend.idea.php.annotation.ui; import com.intellij.openapi.options.Configurable; -import com.intellij.uiDesigner.core.GridConstraints; -import com.intellij.uiDesigner.core.GridLayoutManager; -import com.intellij.uiDesigner.core.Spacer; import de.espend.idea.php.annotation.ApplicationSettings; import de.espend.idea.php.annotation.util.PluginUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -75,49 +71,4 @@ private void updateUIFromSettings() { public void disposeUIResources() { } - - { -// GUI initializer generated by IntelliJ IDEA GUI Designer -// >>> IMPORTANT!! <<< -// DO NOT EDIT OR ADD ANY CODE HERE! - $$$setupUI$$$(); - } - - /** - * Method generated by IntelliJ IDEA GUI Designer - * >>> IMPORTANT!! <<< - * DO NOT edit this method OR call it in your code! - * - * @noinspection ALL - */ - private void $$$setupUI$$$() { - panel = new JPanel(); - panel.setLayout(new GridLayoutManager(4, 1, new Insets(0, 0, 0, 0), -1, -1)); - final JLabel label1 = new JLabel(); - label1.setText("Autocomplete (Annotations)"); - panel.add(label1, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - final Spacer spacer1 = new Spacer(); - panel.add(spacer1, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); - appendRoundBracket = new JCheckBox(); - appendRoundBracket.setText("Insert round bracket after class name"); - panel.add(appendRoundBracket, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - final JPanel panel1 = new JPanel(); - panel1.setLayout(new GridLayoutManager(2, 2, new Insets(0, 0, 0, 0), -1, -1)); - panel.add(panel1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); - final JLabel label2 = new JLabel(); - label2.setText("Actions"); - panel1.add(label2, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - buttonCleanIndex = new JButton(); - buttonCleanIndex.setText("Schedule annotation reindex"); - panel1.add(buttonCleanIndex, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - final Spacer spacer2 = new Spacer(); - panel1.add(spacer2, new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); - } - - /** - * @noinspection ALL - */ - public JComponent $$$getRootComponent$$$() { - return panel; - } } diff --git a/src/main/java/de/espend/idea/php/annotation/ui/UseAliasListForm.java b/src/main/java/de/espend/idea/php/annotation/ui/UseAliasListForm.java index e6b3f06b..e5ea2d11 100644 --- a/src/main/java/de/espend/idea/php/annotation/ui/UseAliasListForm.java +++ b/src/main/java/de/espend/idea/php/annotation/ui/UseAliasListForm.java @@ -3,9 +3,6 @@ import com.intellij.openapi.options.Configurable; import com.intellij.ui.ToolbarDecorator; import com.intellij.ui.table.TableView; -import com.intellij.uiDesigner.core.GridConstraints; -import com.intellij.uiDesigner.core.GridLayoutManager; -import com.intellij.uiDesigner.core.Spacer; import com.intellij.util.ui.ColumnInfo; import com.intellij.util.ui.ElementProducer; import com.intellij.util.ui.ListTableModel; @@ -15,7 +12,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.awt.*; import java.util.ArrayList; /** @@ -151,43 +147,6 @@ public void disposeUIResources() { } - { -// GUI initializer generated by IntelliJ IDEA GUI Designer -// >>> IMPORTANT!! <<< -// DO NOT EDIT OR ADD ANY CODE HERE! - $$$setupUI$$$(); - } - - /** - * Method generated by IntelliJ IDEA GUI Designer - * >>> IMPORTANT!! <<< - * DO NOT edit this method OR call it in your code! - * - * @noinspection ALL - */ - private void $$$setupUI$$$() { - panel = new JPanel(); - panel.setLayout(new GridLayoutManager(2, 3, new Insets(0, 0, 0, 0), -1, -1)); - final JLabel label1 = new JLabel(); - label1.setText("Auto insert use alias for given class scope eg \"Doctrine\\ORM\\Mapping as ORM\""); - panel.add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - panel1 = new JPanel(); - panel1.setLayout(new BorderLayout(0, 0)); - panel.add(panel1, new GridConstraints(1, 0, 1, 3, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); - buttonReset = new JButton(); - buttonReset.setText("Default reset"); - panel.add(buttonReset, new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); - final Spacer spacer1 = new Spacer(); - panel.add(spacer1, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); - } - - /** - * @noinspection ALL - */ - public JComponent $$$getRootComponent$$$() { - return panel; - } - private static class ClassColumn extends ColumnInfo { ClassColumn() { diff --git a/src/main/java/de/espend/idea/php/annotation/util/IdeUtil.java b/src/main/java/de/espend/idea/php/annotation/util/IdeUtil.java index b4edfe49..4cbd8796 100644 --- a/src/main/java/de/espend/idea/php/annotation/util/IdeUtil.java +++ b/src/main/java/de/espend/idea/php/annotation/util/IdeUtil.java @@ -71,7 +71,6 @@ private static PsiFile createFile(@NotNull PsiDirectory directory, @NotNull Stri try { psiFile.getVirtualFile().setBinaryContent(content.getBytes()); } catch (IOException e) { - e.printStackTrace(); } } diff --git a/src/main/resources/META-INF/de.espend.idea.php.annotation-toolbox.xml b/src/main/resources/META-INF/de.espend.idea.php.annotation-toolbox.xml index 2ae17929..eef53f51 100644 --- a/src/main/resources/META-INF/de.espend.idea.php.annotation-toolbox.xml +++ b/src/main/resources/META-INF/de.espend.idea.php.annotation-toolbox.xml @@ -1,4 +1,4 @@ - + diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 687a9883..02c6767a 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -167,14 +167,12 @@ PHP de.espend.idea.php.annotation.doctrine.intention.DoctrineOrmFieldIntention PHP - DoctrineOrmFieldIntention PHP de.espend.idea.php.annotation.doctrine.intention.DoctrineOrmRepositoryIntention PHP - DoctrineOrmRepositoryIntention diff --git a/src/main/resources/inspectionDescriptions/AnnotationDeprecatedInspection.html b/src/main/resources/inspectionDescriptions/AnnotationDeprecatedInspection.html index 5a7c6a21..3d1459a8 100644 --- a/src/main/resources/inspectionDescriptions/AnnotationDeprecatedInspection.html +++ b/src/main/resources/inspectionDescriptions/AnnotationDeprecatedInspection.html @@ -1,8 +1,24 @@ -

Doc block annotation class found, but it is deprecated

+Reports deprecated annotation classes in DocBlocks. -
-You should migrate to a current alternative annotation +

+This inspection detects when an annotation class itself is marked as @deprecated. +

+

+Example: +

+
+/**
+ * @Template()
+ */
+public function indexAction()
+{
+}
+
+

+If the @Template annotation class is deprecated, +the inspection will highlight the annotation name with a strikethrough style. +

diff --git a/src/main/resources/inspectionDescriptions/AnnotationDocBlockClassConstantNotFound.html b/src/main/resources/inspectionDescriptions/AnnotationDocBlockClassConstantNotFound.html index 7d2d9c8f..0ac46c18 100644 --- a/src/main/resources/inspectionDescriptions/AnnotationDocBlockClassConstantNotFound.html +++ b/src/main/resources/inspectionDescriptions/AnnotationDocBlockClassConstantNotFound.html @@ -1,6 +1,21 @@ -Class in class constant of DocBlock context not found +Reports unresolved class references in annotation class constants. +

+This inspection detects when a class referenced using the ::class notation within an annotation +does not exist or cannot be resolved. +

+

+Example: +

+
+/**
+ * @Route(name="home", defaults={"_controller"=NonExistent::class})
+ */
+
+

+The inspection will highlight ::class if the NonExistent class cannot be found. +

\ No newline at end of file diff --git a/src/main/resources/inspectionDescriptions/AnnotationDocBlockConstantDeprecated.html b/src/main/resources/inspectionDescriptions/AnnotationDocBlockConstantDeprecated.html index e7a1803f..67536fe4 100644 --- a/src/main/resources/inspectionDescriptions/AnnotationDocBlockConstantDeprecated.html +++ b/src/main/resources/inspectionDescriptions/AnnotationDocBlockConstantDeprecated.html @@ -1,8 +1,19 @@ -

Constant in class found, but it is deprecated

+Reports deprecated class constants and class references in annotations. -
-You should migrate to a current alternative annotation +

+This inspection detects when a class constant (including ::class) referenced within +an annotation property is marked as deprecated. +

+

+Examples: +

+
+/**
+ * @SomeAnnotation(targetClass=DeprecatedClass::class)
+ * @OtherAnnotation(version=SomeClass::VERSION)
+ */
+
diff --git a/src/main/resources/inspectionDescriptions/AnnotationMissingUseInspection.html b/src/main/resources/inspectionDescriptions/AnnotationMissingUseInspection.html index e45bb57c..8a223a62 100644 --- a/src/main/resources/inspectionDescriptions/AnnotationMissingUseInspection.html +++ b/src/main/resources/inspectionDescriptions/AnnotationMissingUseInspection.html @@ -1,8 +1,31 @@ -Doc block annotation class found, but its missing in use statement +Reports annotations that are missing a use import statement. -
-You should import the class as use statement +

+This inspection detects when an annotation class is used in a DocBlock but has not been imported +with a use statement at the top of the file. +

+

+Example problem: +

+
+/**
+ * @Route("/api/users")
+ */
+class UserController
+{
+}
+
+

+The inspection will highlight @Route and offer a quick fix to add: +

+
+use Symfony\Component\Routing\Annotation\Route;
+
+

+This helps ensure proper namespace resolution and follows PHP best practices. +If multiple possible import candidates exist, the quick fix will present all options. +

\ No newline at end of file diff --git a/src/main/resources/inspectionDescriptions/DoctrineTypeDeprecatedInspection.html b/src/main/resources/inspectionDescriptions/DoctrineTypeDeprecatedInspection.html index 99301bca..d4d76168 100644 --- a/src/main/resources/inspectionDescriptions/DoctrineTypeDeprecatedInspection.html +++ b/src/main/resources/inspectionDescriptions/DoctrineTypeDeprecatedInspection.html @@ -1,7 +1,34 @@ -

Reports deprecations of the type Doctrine\ORM\Mapping\Column::type based on the underlying type class

+Reports deprecated Doctrine column types in #[ORM\Column] annotations. -

[Annotation]Reports deprecations of Doctrine\ORM\Mapping\Column::type

+

+This inspection detects when a Doctrine column type specified in the type parameter +of an @ORM\Column annotation or attribute is deprecated. +

+

+Example: +

+
+/**
+ * @ORM\Column(type="json_array")
+ */
+private $data;
+
+

+The inspection will highlight deprecated type values such as: +

+
    +
  • "json_array" (deprecated in favor of "json")
  • +
  • "simple_array" (deprecated)
  • +
  • Other types whose underlying implementation classes are marked as deprecated
  • +
+

+This helps you migrate to the current recommended Doctrine column types and avoid using +deprecated functionality that may be removed in future versions. +

+

+Works with both @ORM\Column annotations and #[ORM\Column] attributes. +

\ No newline at end of file diff --git a/src/main/resources/inspectionDescriptions/RepositoryClass.html b/src/main/resources/inspectionDescriptions/RepositoryClass.html deleted file mode 100644 index c7fc2595..00000000 --- a/src/main/resources/inspectionDescriptions/RepositoryClass.html +++ /dev/null @@ -1,5 +0,0 @@ - - -Repository class not found - - \ No newline at end of file diff --git a/src/main/resources/inspectionDescriptions/RepositoryClassInspection.html b/src/main/resources/inspectionDescriptions/RepositoryClassInspection.html index ddaee444..d7b08fc4 100644 --- a/src/main/resources/inspectionDescriptions/RepositoryClassInspection.html +++ b/src/main/resources/inspectionDescriptions/RepositoryClassInspection.html @@ -1,5 +1,37 @@ -No repository class was found. Please create a PHP class see for more details +Reports missing Doctrine repository classes referenced in @ORM\Entity annotations. + +

+This inspection detects when a repositoryClass parameter in an @ORM\Entity +annotation or attribute references a repository class that does not exist. +

+

+Example problem: +

+
+/**
+ * @ORM\Entity(repositoryClass=ProductRepository::class)
+ */
+class Product
+{
+}
+
+

+If ProductRepository does not exist, the inspection will highlight the repository class reference +and provide a quick fix to automatically create the repository class. +

+

+Quick fix actions: +

+
    +
  • Creates a new repository class in the appropriate namespace
  • +
  • Extends ServiceEntityRepository (Symfony) or EntityRepository (standalone Doctrine)
  • +
  • Adds proper constructor and entity references
  • +
+

+For more information about Doctrine repositories, see +Symfony documentation. +

\ No newline at end of file diff --git a/src/main/resources/intentionDescriptions/DoctrineOrmFieldIntention/after.php.template b/src/main/resources/intentionDescriptions/DoctrineOrmFieldIntention/after.php.template new file mode 100644 index 00000000..3fdd56b6 --- /dev/null +++ b/src/main/resources/intentionDescriptions/DoctrineOrmFieldIntention/after.php.template @@ -0,0 +1,16 @@ +use Doctrine\ORM\Mapping as ORM; + +/** + * @ORM\Entity + */ +class Product +{ + private $id; + + /** + * @ORM\Column(type="string") + */ + private $name; + + private $price; +} diff --git a/src/main/resources/intentionDescriptions/DoctrineOrmFieldIntention/before.php.template b/src/main/resources/intentionDescriptions/DoctrineOrmFieldIntention/before.php.template new file mode 100644 index 00000000..b2b86b5d --- /dev/null +++ b/src/main/resources/intentionDescriptions/DoctrineOrmFieldIntention/before.php.template @@ -0,0 +1,13 @@ +use Doctrine\ORM\Mapping as ORM; + +/** + * @ORM\Entity + */ +class Product +{ + private $id; + + private $name; + + private $price; +} diff --git a/src/main/resources/intentionDescriptions/DoctrineOrmFieldIntention/description.html b/src/main/resources/intentionDescriptions/DoctrineOrmFieldIntention/description.html index 919df5a8..9583a419 100644 --- a/src/main/resources/intentionDescriptions/DoctrineOrmFieldIntention/description.html +++ b/src/main/resources/intentionDescriptions/DoctrineOrmFieldIntention/description.html @@ -1,7 +1,18 @@ -Create @Column annotation. +Adds a Doctrine ORM #[ORM\Column] annotation to a class property. -Add annotation for class property field for Doctrine Orm Entitiy +

+This intention action adds a @ORM\Column annotation to a class property in a Doctrine entity, +making it a persistent database column. The annotation will be added with a default type that can be customized. +

+

+Works with both DocBlock annotations (@ORM\Column) and PHP 8 attributes (#[ORM\Column]). +

+

+Example:
+Before: private $name;
+After: /** @ORM\Column(type="string") */ private $name; +

\ No newline at end of file diff --git a/src/main/resources/intentionDescriptions/DoctrineOrmRepositoryIntention/after.php.template b/src/main/resources/intentionDescriptions/DoctrineOrmRepositoryIntention/after.php.template new file mode 100644 index 00000000..c88b0ce1 --- /dev/null +++ b/src/main/resources/intentionDescriptions/DoctrineOrmRepositoryIntention/after.php.template @@ -0,0 +1,14 @@ +use App\Repository\ProductRepository; +use Doctrine\ORM\Mapping as ORM; + +/** + * @ORM\Entity(repositoryClass=ProductRepository::class) + */ +class Product +{ + /** + * @ORM\Id + * @ORM\Column(type="integer") + */ + private $id; +} diff --git a/src/main/resources/intentionDescriptions/DoctrineOrmRepositoryIntention/before.php.template b/src/main/resources/intentionDescriptions/DoctrineOrmRepositoryIntention/before.php.template new file mode 100644 index 00000000..0575d111 --- /dev/null +++ b/src/main/resources/intentionDescriptions/DoctrineOrmRepositoryIntention/before.php.template @@ -0,0 +1,13 @@ +use Doctrine\ORM\Mapping as ORM; + +/** + * @ORM\Entity + */ +class Product +{ + /** + * @ORM\Id + * @ORM\Column(type="integer") + */ + private $id; +} diff --git a/src/main/resources/intentionDescriptions/DoctrineOrmRepositoryIntention/description.html b/src/main/resources/intentionDescriptions/DoctrineOrmRepositoryIntention/description.html index 8afb89e0..444eb9ec 100644 --- a/src/main/resources/intentionDescriptions/DoctrineOrmRepositoryIntention/description.html +++ b/src/main/resources/intentionDescriptions/DoctrineOrmRepositoryIntention/description.html @@ -1,7 +1,24 @@ -Create EntityRepository tied to the entity +Creates a Doctrine repository class and links it to the entity. -Creates an entity repository based on the current entity. +

+This intention action creates a new repository class for the current Doctrine entity and automatically +adds the repositoryClass parameter to the @ORM\Entity annotation or attribute. +

+

+The repository class will be created in an appropriate namespace based on your project structure: +

+
    +
  • Foo\Entity\ProductFoo\Repository\ProductRepository
  • +
  • App\Entity\User\ProfileApp\Repository\User\ProfileRepository
  • +
+

+The repository will extend either ServiceEntityRepository (Symfony) or EntityRepository +(standalone Doctrine) depending on your project dependencies. +

+

+Works with both DocBlock annotations and PHP 8 attributes. +