Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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<string>`
- 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`
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
75 changes: 33 additions & 42 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand All @@ -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
Expand All @@ -72,7 +91,9 @@ tasks {
targetCompatibility = it
}
withType<KotlinCompile> {
kotlinOptions.jvmTarget = it
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(it))
}
}
}

Expand All @@ -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("<html>", "").replace("</html>", ""))

// 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=<!--999.999-->",
"-Djb.consents.confirmation.enabled=false",
)
}
}
plugins {
robotServerPlugin()
}
}

signPlugin {
Expand All @@ -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")
}
}
}
15 changes: 6 additions & 9 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 5 additions & 3 deletions gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading