diff --git a/CHANGELOG.md b/CHANGELOG.md index 79ff056..a23a9fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - BEAR and Ray gutter icons now use transparent backgrounds. - Incoming resource relation gutters moved from the resource class name to the target resource method (`#[Embed]` always maps to `onGet`; `#[Link]` maps from its `method` argument and defaults to `onGet`). - Replaced the deprecated `Project#getBaseDir()` with `ProjectUtil.guessProjectDir()` (with null guards) across the resource / router / SQL / JSON Schema goto handlers and the resource index +- Replaced the remaining deprecated/internal API usages reported by the plugin verifier against IntelliJ IDEA 2026.2.1: `FilenameIndex.getFilesByName()` → `FilenameIndex.getVirtualFilesByName()` and internal `PhpType.from()` → public `new PhpType().add()` in the resource method type provider - Refreshed README and plugin description metadata, including current feature wording and Marketplace links - Removed stale README TODOs and legacy Php Annotations Plugin references from public documentation - Updated the MIT license notice to cover 2015-2026 Shingo Kumagai and contributors diff --git a/src/main/java/idea/bear/sunday/body/BodyTypes.java b/src/main/java/idea/bear/sunday/body/BodyTypes.java index 81bc9e8..f0d6f9c 100644 --- a/src/main/java/idea/bear/sunday/body/BodyTypes.java +++ b/src/main/java/idea/bear/sunday/body/BodyTypes.java @@ -102,7 +102,7 @@ static List shapeFields(BodyType type) { return null; } - static List unionTypes(BodyType type) { + public static List unionTypes(BodyType type) { if (type instanceof UnionType unionType) { return unionType.types(); } diff --git a/src/main/java/idea/bear/sunday/resource/ResourceMethodTypeProvider.java b/src/main/java/idea/bear/sunday/resource/ResourceMethodTypeProvider.java index ec93695..af02cb7 100644 --- a/src/main/java/idea/bear/sunday/resource/ResourceMethodTypeProvider.java +++ b/src/main/java/idea/bear/sunday/resource/ResourceMethodTypeProvider.java @@ -25,8 +25,10 @@ import com.jetbrains.php.lang.psi.elements.Variable; import com.jetbrains.php.lang.psi.resolve.types.PhpType; import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider4; +import idea.bear.sunday.body.BodyType; import idea.bear.sunday.body.BodyTypeCollector; import idea.bear.sunday.body.BodyTypeDeclaration; +import idea.bear.sunday.body.BodyTypes; import idea.bear.sunday.util.UriUtil; import org.jetbrains.annotations.Nullable; @@ -109,18 +111,34 @@ public Collection getBySignature(String expression, S private @Nullable PhpType completeRequest(SignedResourceRequest request, Project project) { if (SIGNATURE_RESOURCE.equals(request.kind())) { return resolveResourceClass(project, request.uri()) - .map(phpClass -> PhpType.from(phpClass.getFQN())) + .map(phpClass -> new PhpType().add(phpClass.getFQN())) .orElse(null); } if (SIGNATURE_BODY.equals(request.kind())) { return resolveBodyType(project, request) - .map(declaration -> PhpType.from(declaration.bodyType().render(), PhpType._NULL)) + .map(declaration -> bodyPhpType(declaration.bodyType())) .orElse(null); } return null; } + private static PhpType bodyPhpType(BodyType bodyType) { + // PhpType.add(String) stores the argument as one opaque type, so a rendered union + // ("array{…}|array{…}") must be added component by component to stay a real union. + PhpType phpType = new PhpType(); + List unionParts = BodyTypes.unionTypes(bodyType); + if (unionParts == null) { + phpType.add(bodyType.render()); + } else { + for (BodyType part : unionParts) { + phpType.add(part.render()); + } + } + + return phpType.add(PhpType._NULL); + } + private Optional resolveBodyType(Project project, SignedResourceRequest request) { return resolveResourceClass(project, request.uri()) .flatMap(bodyTypeCollector::collect) @@ -305,9 +323,14 @@ private static Optional resolveResourceClassFromFilenameIndex(Project String fileName = className + ".php"; String expectedSuffix = "/" + relPath; try { - for (PsiFile psiFile : FilenameIndex.getFilesByName(project, fileName, GlobalSearchScope.allScope(project))) { - VirtualFile virtualFile = psiFile.getVirtualFile(); - if (virtualFile == null || !virtualFile.getPath().replace('\\', '/').endsWith(expectedSuffix)) { + PsiManager psiManager = PsiManager.getInstance(project); + for (VirtualFile virtualFile : FilenameIndex.getVirtualFilesByName(fileName, GlobalSearchScope.allScope(project))) { + if (!virtualFile.getPath().replace('\\', '/').endsWith(expectedSuffix)) { + continue; + } + + PsiFile psiFile = psiManager.findFile(virtualFile); + if (psiFile == null) { continue; } diff --git a/src/test/java/idea/bear/sunday/resource/ResourceMethodTypeProviderFixtureTest.java b/src/test/java/idea/bear/sunday/resource/ResourceMethodTypeProviderFixtureTest.java index 5bd9042..8850cd2 100644 --- a/src/test/java/idea/bear/sunday/resource/ResourceMethodTypeProviderFixtureTest.java +++ b/src/test/java/idea/bear/sunday/resource/ResourceMethodTypeProviderFixtureTest.java @@ -302,6 +302,48 @@ public function onPut(): void assertTrue(completedType.isNullable(), completedType::toString); } + @Test + void addsUnionBodyTypeComponentsIndividually() { + addPhysicalPhpFile("src/Resource/App/Article.php", """ + body = ['id' => 1]; + + return $this; + } + + $this->body = ['error' => 'not found']; + + return $this; + } + } + """); + PsiFile caller = fixture.addFileToProject("src/Resource/App/Caller.php", """ + resource->get('app://self/article')->body; + } + } + """); + + PhpType completedType = completedBodyType(caller); + + assertTrue(completedType.getTypes().contains("array{id: int}"), completedType::toString); + assertTrue(completedType.getTypes().contains("array{error: string}"), completedType::toString); + assertTrue(completedType.getTypes().stream().noneMatch(type -> type.contains("|")), completedType::toString); + } + @Test void narrowsDirectResourceBodyAccess() { addPhysicalPhpFile("src/Resource/App/Article.php", """