Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Classes with same name in different namespaces can be confused.
(Disabled by default)
<p>Option: Ignore vendor classes. When enabled, class name collisions with classes defined in the project's <code>vendor</code> directory are ignored.</p>
<pre>
namespace App {
class User {}; // <- Class name collision with \Cli\User
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,42 @@ package com.funivan.idea.phpClean.inspections.classNameCollision

import com.funivan.idea.phpClean.spl.PhpCleanInspection
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.jetbrains.php.PhpIndex
import com.jetbrains.php.lang.psi.elements.PhpClass
import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor
import javax.swing.JComponent

class ClassNameCollisionInspection : PhpCleanInspection() {
companion object {
private const val VENDOR_DIR_NAME = "vendor"
}

@JvmField
var ignoreVendorClasses = false

override fun getShortName() = "ClassNameCollisionInspection"

override fun createOptionsPanel(): JComponent {
return SingleCheckboxOptionsPanel(
"Ignore vendor classes",
this,
"ignoreVendorClasses"
)
}

override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
val vendorDir = if (ignoreVendorClasses) findVendorDir(holder.project) else null
return object : PhpElementVisitor() {
override fun visitPhpClass(phpClass: PhpClass) {
phpClass.nameIdentifier?.let { name ->
find(phpClass, name)?.let { clazz ->
find(phpClass, name, vendorDir)?.let { clazz ->
holder.registerProblem(
name,
"Class name collision with ${clazz.fqn}"
Expand All @@ -27,8 +50,32 @@ class ClassNameCollisionInspection : PhpCleanInspection() {

private fun find(
origin: PhpClass,
name: PsiElement
) = PhpIndex.getInstance(origin.project)
.getClassesByName(name.text)
.firstOrNull { it.fqn != origin.fqn }
name: PsiElement,
vendorDir: VirtualFile?
): PhpClass? {
val collisions = PhpIndex.getInstance(origin.project)
.getClassesByName(name.text)
.filter { it.fqn != origin.fqn }
val filtered = if (vendorDir == null) {
collisions
} else {
collisions.filterNot { isVendorClass(it, vendorDir) }
}
return filtered.firstOrNull()
}

private fun findVendorDir(project: Project): VirtualFile? {
val basePath = project.basePath
?: return null
val projectDir = LocalFileSystem.getInstance().findFileByPath(basePath)
?: return null
return projectDir.findChild(VENDOR_DIR_NAME)
}

private fun isVendorClass(phpClass: PhpClass, vendorDir: VirtualFile): Boolean {
val file = phpClass.containingFile.virtualFile
?: return false

return VfsUtilCore.isAncestor(vendorDir, file, true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,46 @@ class ClassNameCollisionInspectionTest : BaseInspectionTest() {
"""
)
}

@Test
fun testVendorCollisionIgnoredWhenOptionEnabled() {
myFixture.addFileToProject(
"vendor/lib/User.php",
"""<?php
namespace Vendor {
class User {};
}
"""
)
val inspection = ClassNameCollisionInspection()
inspection.ignoreVendorClasses = true
assert(
inspection,
"""<?php
namespace App {
class User {};
}
"""
)
}

@Test
fun testVendorCollisionReportedWhenOptionDisabled() {
myFixture.addFileToProject(
"vendor/lib/User.php",
"""<?php
namespace Vendor {
class User {};
}
"""
)
assert(
ClassNameCollisionInspection(),
"""<?php
namespace App {
class <warning descr="Class name collision with \Vendor\User">User</warning> {};
}
"""
)
}
}
Loading