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 1 commit
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 @@ -23,6 +23,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.
thelooter marked this conversation as resolved.
Show resolved Hide resolved
*
* @param element The PsiElement to get an icon for.
* @param flags Additional flags for icon retrieval (not used in this implementation).
thelooter marked this conversation as resolved.
Show resolved Hide resolved
* @return The icon for the element, or null if no suitable icon is found or if the file type is
thelooter marked this conversation as resolved.
Show resolved Hide resolved
* handled by another provider.
*/
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 +41,70 @@ 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)
}

// Folders
if (virtualFile?.isDirectory == true) {
return icons.FOLDER_TO_ICONS[virtualFile.name.lowercase()] ?: icons._folder
/**
* Finds an appropriate icon for the given virtual file and PsiFile.
thelooter marked this conversation as resolved.
Show resolved Hide resolved
*
* @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: com.intellij.openapi.vfs.VirtualFile?,
file: com.intellij.psi.PsiFile?,
): Icon? {
val fileTypeName = file?.fileType?.name?.lowercase()

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: com.intellij.openapi.vfs.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 to fall back to default icon
thelooter marked this conversation as resolved.
Show resolved Hide resolved
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,90 @@ import com.intellij.ui.RowIcon
import javax.swing.Icon
import org.jetbrains.annotations.NotNull

/** Provides icons for Java classes in the IDE. */
thelooter marked this conversation as resolved.
Show resolved Hide resolved
class JavaIconProvider : IconProvider() {
/**
* Returns an icon for the given PsiElement if it's a Java class.
thelooter marked this conversation as resolved.
Show resolved Hide resolved
*
* @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? {
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
return when {
thelooter marked this conversation as resolved.
Show resolved Hide resolved
!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 in the project view.
thelooter marked this conversation as resolved.
Show resolved Hide resolved
*
* @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.
thelooter marked this conversation as resolved.
Show resolved Hide resolved
*
* @param element The PsiClass to check for static modifier.
* @return The static mark icon if the class is static, null otherwise.
*/
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 +109,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() {

companion object {
thelooter marked this conversation as resolved.
Show resolved Hide resolved
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
}

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)
}
}
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