Skip to content

Commit

Permalink
feat(detekt): add detekt for static analysis and fix issues (#145)
Browse files Browse the repository at this point in the history
  • Loading branch information
thelooter authored Nov 13, 2024
1 parent 271521c commit 6564023
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 57 deletions.
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

0 comments on commit 6564023

Please sign in to comment.