Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(detekt): add detekt for static analysis and fix issues #145

Merged
merged 4 commits into from
Nov 13, 2024
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
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ trim_trailing_whitespace = false
# windows shell scripts
[*.{cmd,bat,ps1}]
end_of_line = crlf

[*.{kt,kts}]
ij_kotlin_name_count_to_use_star_import = 2147483647
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ jobs:
- name: Check Formatting
run: ./gradlew checkFormatting

- name: Run Static Analysis
run: ./gradlew runStaticAnalysis
continue-on-error: true

- name: Test & Build Plugin
run: ./gradlew buildPlugin

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Changed

- Add code formatting with ktfmt
- Add static analysis with detekt
- Add tests

### Deprecated
Expand Down
30 changes: 30 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import com.ncorti.ktfmt.gradle.tasks.KtfmtCheckTask
import io.gitlab.arturbosch.detekt.Detekt
import org.jetbrains.changelog.Changelog
import org.jetbrains.changelog.date
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
Expand All @@ -21,6 +22,8 @@ plugins {
// Code Quality
// ktfmt
id("com.ncorti.ktfmt.gradle") version "0.21.0"
//detekt
id("io.gitlab.arturbosch.detekt").version("1.23.7")
}

group = properties("pluginGroup")
Expand Down Expand Up @@ -123,6 +126,8 @@ tasks {
tasks.buildSearchableOptions { enabled = false }

// Code quality settings

// Formatting settings
ktfmt { googleStyle() }

// This is used over the "ktfmtCheck" task to exclude the autogenerated file Icons.kt.
Expand All @@ -131,3 +136,28 @@ tasks.register<KtfmtCheckTask>("checkFormatting") {
include("**/*.kt")
exclude("src/main/kotlin/com/github/catppuccin/jetbrains_icons/Icons.kt")
}

// Static Analysis config
detekt {
parallel = true
config.setFrom("detekt.yaml")
buildUponDefaultConfig = true
}

// This is used over the "detekt" task to exclude the autogenerated file Icons.kt.
tasks.withType<Detekt>().configureEach {
source = project.fileTree(rootDir)
include("**/*.kt")
exclude("src/main/kotlin/com/github/catppuccin/jetbrains_icons/Icons.kt")
}

tasks.register<Detekt>("runStaticAnalysis") {

parallel = true
config.setFrom("detekt.yaml")
buildUponDefaultConfig = true

source = project.fileTree(rootDir)
include("**/*.kt")
exclude("src/main/kotlin/com/github/catppuccin/jetbrains_icons/Icons.kt")
}
6 changes: 6 additions & 0 deletions detekt.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
config:
validation: true

naming:
PackageNaming:
active: false
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package com.github.catppuccin.jetbrains_icons

import com.github.catppuccin.jetbrains_icons.IconPack.icons
import com.intellij.ide.IconProvider
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.util.PsiUtilCore
import javax.swing.Icon
Expand All @@ -23,6 +25,14 @@ class IconProvider : IconProvider() {
/** File extensions that are handled by more specific providers (not this class). */
private val fileTypesByProviders = listOf(".java")

/**
* Returns an icon for the given [PsiElement].
*
* @param element the [PsiElement] to get an icon for.
* @param flags additional flags for icon retrieval (not used in this implementation). It is only
* used because the method signature requires it.
* @return the [Icon] corresponding to the file type
*/
override fun getIcon(element: PsiElement, flags: Int): Icon? {
val virtualFile = PsiUtilCore.getVirtualFile(element)
val file = virtualFile?.let { PsiManager.getInstance(element.project).findFile(it) }
Expand All @@ -33,39 +43,68 @@ class IconProvider : IconProvider() {
return null
}

// Check if the name of the file is overridden by anything, if so return that icon.
if (iconOverrides.containsKey(file?.fileType?.name?.lowercase())) {
return iconOverrides[file?.fileType?.name?.lowercase()]
}
return findIcon(virtualFile, file)
}

/**
* Finds an appropriate icon for the given virtual file and [PsiFile].
*
* @param virtualFile the [VirtualFile] associated with the element.
* @param file the [PsiFile] associated with the element.
* @return the icon for the file, or a default icon if no specific icon is found.
*/
private fun findIcon(virtualFile: VirtualFile?, file: PsiFile?): Icon? {
val fileTypeName = file?.fileType?.name?.lowercase()

// Folders
if (virtualFile?.isDirectory == true) {
return icons.FOLDER_TO_ICONS[virtualFile.name.lowercase()] ?: icons._folder
return when {
// Check if the name of the file is overridden by anything, if so return that icon.
iconOverrides.containsKey(fileTypeName) -> iconOverrides[fileTypeName]
virtualFile?.isDirectory == true ->
icons.FOLDER_TO_ICONS[virtualFile.name.lowercase()] ?: icons._folder

else -> findFileIcon(virtualFile) ?: icons._file
}
}

/**
* Finds an icon specifically for a file (not a directory).
*
* @param virtualFile the [VirtualFile] to find an icon for.
* @return the icon for the file, or null if no specific icon is found.
*/
private fun findFileIcon(virtualFile: VirtualFile?): Icon? {
val fileName = virtualFile?.name?.lowercase()

// Files
val icon = icons.FILE_TO_ICONS[virtualFile?.name?.lowercase()]
if (icon != null) {
return icon
}
return icons.FILE_TO_ICONS[fileName]
?: findExtensionIcon(fileName)
?: if (virtualFile?.fileType?.isBinary == true) icons.binary else null
}

/**
* Finds an icon based on the file extension.
*
* @param fileName the name of the file to find an icon for.
* @return the icon for the file extension, or null if no matching icon is found.
*/
private fun findExtensionIcon(fileName: String?): Icon? {
// Extensions
// if the file is abc.test.tsx, try abc.test.tsx, then test.tsx, then tsx
val parts = virtualFile?.name?.split(".")
if (parts != null) {
for (i in parts.indices) {
val path = parts.subList(i, parts.size).joinToString(".")
val icon = icons.EXT_TO_ICONS[path]
if (icon != null) {
return icon
return when {
// Return null if filename is null since we can't process it
fileName == null -> null
else -> {
val parts = fileName.split(".")
for (i in parts.indices) {
val path = parts.subList(i, parts.size).joinToString(".")
// Return the first matching icon we find, starting from the longest possible extension
icons.EXT_TO_ICONS[path]?.let {
return it
}
}
// No matching extension was found, so return null, falling back to default icon
null
}
}

if (virtualFile?.fileType?.isBinary == true) {
return icons.binary
}

return icons._file
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,46 +16,91 @@ import com.intellij.ui.RowIcon
import javax.swing.Icon
import org.jetbrains.annotations.NotNull

/** Provides icons for Java classes */
class JavaIconProvider : IconProvider() {
override fun getIcon(@NotNull element: PsiElement, @IconFlags flags: Int): Icon? {
if (!PluginSettingsState.instance.javaSupport) return icons.java

if (element !is PsiClass) return null

val virtualFile = PsiUtilCore.getVirtualFile(element) ?: return null
if (!virtualFile.name.endsWith(".java")) return null
/**
* Returns an icon for the given [PsiElement] if it's a Java class.
*
* @param element The [PsiElement] to get an icon for.
* @param flags Additional flags for icon retrieval.
* @return The icon for the element, or null if no suitable icon is found.
*/
override fun getIcon(@NotNull element: PsiElement, @IconFlags flags: Int): Icon? =
when {
!PluginSettingsState.instance.javaSupport -> icons.java
element !is PsiClass -> null
PsiUtilCore.getVirtualFile(element)?.name?.endsWith(".java") != true -> null
else -> getJavaClassIcon(element)
}

/**
* Gets the appropriate icon for a Java class, including static and visibility markers.
*
* @param element The [PsiClass] to get an icon for.
* @return The icon for the Java class.
*/
private fun getJavaClassIcon(element: PsiClass): Icon {
val baseIcon = getJavaIcon(element)
val staticMark = getStaticMark(element)
val iconWithStaticMark = addStaticMarkIfNeeded(element, baseIcon)
return addVisibilityIconIfNeeded(element, iconWithStaticMark)
}

val icon =
when {
staticMark != null -> {
LayeredIcon(2).apply {
setIcon(baseIcon, 0)
setIcon(staticMark, 1)
}
}
else -> baseIcon
/**
* Adds a static mark to the icon if the class is static.
*
* @param element The [PsiClass] to check for static modifier.
* @param baseIcon The base icon to add the static mark to.
* @return The icon with static mark added if needed.
*/
private fun addStaticMarkIfNeeded(element: PsiClass, baseIcon: Icon): Icon {
val staticMark = getStaticMark(element)
return if (staticMark != null) {
LayeredIcon(2).apply {
setIcon(baseIcon, 0)
setIcon(staticMark, 1)
}
} else {
baseIcon
}
}

/**
* Adds a visibility icon to the class icon if visibility icons are enabled.
*
* @param element The [PsiClass] to get the visibility for.
* @param icon The icon to add the visibility icon to.
* @return The icon with visibility icon added if needed.
*/
private fun addVisibilityIconIfNeeded(element: PsiClass, icon: Icon): Icon {
val visibilityIconsEnabled =
ProjectView.getInstance(element.project)?.isShowVisibilityIcons("ProjectPane") == true

return when {
visibilityIconsEnabled -> {
RowIcon(2).apply {
setIcon(icon, 0)
getVisibilityIcon(element)?.let { setIcon(it, 1) }
}
return if (visibilityIconsEnabled) {
RowIcon(2).apply {
setIcon(icon, 0)
getVisibilityIcon(element)?.let { setIcon(it, 1) }
}
else -> icon
} else {
icon
}
}

/**
* Gets the static mark icon if the class is static. This is a small icon that is added to the
* class icon to indicate that the class is static.
*
* @param element The [PsiClass] to check for static modifier.
* @return The static mark icon if the class is static, null otherwise.
* @see AllIcons.Nodes.StaticMark
*/
private fun getStaticMark(element: PsiClass): Icon? =
if (PsiClassUtils.isStatic(element)) AllIcons.Nodes.StaticMark else null

/**
* Gets the visibility icon for the given [PsiClass].
*
* @param psiElement The [PsiClass] to get the visibility icon for.
* @return The visibility icon based on the class's modifier, or null if not applicable.
*/
private fun getVisibilityIcon(psiElement: PsiClass): Icon? =
when {
psiElement.hasModifierProperty(PsiModifier.PUBLIC) -> AllIcons.Nodes.Public
Expand All @@ -65,6 +110,12 @@ class JavaIconProvider : IconProvider() {
else -> null
}

/**
* Gets the appropriate Java icon based on the class type.
*
* @param aClass The [PsiClass] to get the icon for.
* @return The icon representing the Java class type.
*/
private fun getJavaIcon(aClass: PsiClass): Icon =
when {
aClass.isAnnotationType -> icons.java_annotation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.github.catppuccin.jetbrains_icons.settings.views

import com.github.catppuccin.jetbrains_icons.settings.PluginSettingsState
import com.intellij.ide.plugins.PluginManager.isPluginInstalled
import com.intellij.openapi.extensions.PluginId.*
import com.intellij.openapi.extensions.PluginId.findId
import com.intellij.ui.components.JBCheckBox
import com.intellij.util.ui.FormBuilder
import java.awt.FlowLayout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,35 @@ import javax.swing.JLabel
import javax.swing.JPanel

class SettingsHeaderView : JPanel() {

init {
drawLogo()
add(Box.createRigidArea(Dimension(4, 0)))

// Draw spacer between Logo and Title
add(Box.createRigidArea(Dimension(SPACER_WIDTH, SPACER_HEIGHT)))

drawTitle()
}

private fun drawLogo() {
val url = javaClass.getResource("/pluginIcon.png")
var image = ImageIcon(url)
image = ImageIcon(image.image.getScaledInstance(60, 60, Image.SCALE_SMOOTH))
image = ImageIcon(image.image.getScaledInstance(LOGO_SIZE, LOGO_SIZE, Image.SCALE_SMOOTH))

val field = JLabel(image)
add(field)
}

private fun drawTitle() {
val label = JLabel("Catppuccin Icons")
label.font = label.font.deriveFont(24.0f)
label.font = label.font.deriveFont(FONT_SIZE)
add(label)
}

companion object {
private const val SPACER_WIDTH = 4
private const val SPACER_HEIGHT = 8
private const val LOGO_SIZE = 60
private const val FONT_SIZE = 24.0f
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ object PsiClassUtils {

/** Returns true if the [psiClass] is an exception (inherits from an exception). */
fun isException(psiClass: PsiClass): Boolean {
val className = psiClass.name
if (className.isNullOrEmpty()) return false
if (!psiClass.isValid) return false
return extendsException(psiClass)
if (psiClass.name.isNullOrEmpty()) return false
return psiClass.isValid && extendsException(psiClass)
}

/** Returns true if the [psiClass] has package-private visibility. */
Expand Down