diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 0000000..6460ca9 --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,33 @@ +name: PR Check + +on: + pull_request: + branches: [ main, master ] + +jobs: + check: + name: ktlint and unit tests + runs-on: ubuntu-latest + steps: + - name: Fetch Sources + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '21' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + with: + cache-read-only: false + + - name: Run Ktlint Check + run: ./gradlew ktlintCheck + + - name: Run Unit Tests + run: ./gradlew test + + - name: Verify Plugin Build + run: ./gradlew buildPlugin verifyPlugin diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..9599c3a --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,40 @@ +name: Publish Plugin + +on: + push: + branches: + - main + release: + types: [created] + workflow_dispatch: # Allows manual trigger + +jobs: + publish: + name: Build and Publish Plugin + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'zulu' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Build Plugin + run: ./gradlew buildPlugin + + - name: Verify Plugin + run: ./gradlew verifyPlugin + + - name: Publish Plugin + env: + CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} + PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} + PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} + run: ./gradlew publishPlugin \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..597ec59 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,81 @@ +# Changelog + +All notable changes to the "Log Injector" plugin will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.0.1] - 2026-02-19 + +### Fixed +- Fixed incorrect removal of entire scope function blocks (`apply`, `let`, `run`) when removing a single log line inside them + +### Improved +- Added Napier logging framework support for Kotlin Multiplatform projects +- Minor stability improvements + +### Planned Features +- Support for additional logging frameworks (Log4j, SLF4J) +- Custom log templates +- Smart log placement (avoid duplicates) +- Log level configuration (DEBUG, INFO, WARN, ERROR) +- Bulk operations across multiple files +- Import optimization for logging frameworks + +## [1.0.0] - 2025-02-17 + +### Added +- πŸŽ‰ Initial release of Log Injector +- ✨ Automatic log insertion for Java and Kotlin methods +- ✨ Automatic log removal functionality +- βš™οΈ Support for System.out.println logging +- βš™οΈ Support for Timber logging framework (Android) +- πŸ“Š Method execution tracking +- πŸ“Š Variable assignment tracking +- 🎨 Configurable log tags/prefixes +- 🎨 Tool window for easy configuration +- πŸ”§ Context menu integration (right-click actions) +- βœ… Full Kotlin K2 support +- βœ… IntelliJ IDEA 2024.3+ compatibility +- πŸ“ Comprehensive documentation and README +- πŸ§ͺ Unit tests for core functionality + +### Technical Details +- Built with Kotlin 2.1.0 +- Uses IntelliJ Platform SDK 2024.3 +- Code style enforced with ktlint +- Gradle-based build system +- GitHub Actions CI/CD integration + +### Compatibility +- IntelliJ IDEA 2024.3 (build 243) to 2025.3 (build 253.*) +- Supports Java and Kotlin files +- Requires JDK 21 or higher + +--- + +## Release Notes Format for Marketplace + +### Version 1.0.0 - First Release πŸŽ‰ + +**New Features:** +- One-click log insertion and removal for Java and Kotlin +- Support for System.out.println and Timber frameworks +- Configurable log tags and tracking options +- Tool window for easy configuration +- Context menu integration + +**Technical:** +- Full Kotlin K2 compiler support +- Compatible with IntelliJ IDEA 2024.3+ +- Built with modern IntelliJ Platform SDK + +**Get Started:** +1. Right-click in any Java/Kotlin file +2. Choose "Insert Logs" to add debug statements +3. Use "Remove Logs" when done debugging +4. Configure options in the LoggingOptions tool window + +Thank you for trying Log Injector! Report issues at: https://github.com/Kiolk/Log-Injector/issues \ No newline at end of file diff --git a/MARKETPLACE.md b/MARKETPLACE.md new file mode 100644 index 0000000..5a40351 --- /dev/null +++ b/MARKETPLACE.md @@ -0,0 +1,299 @@ +# Log Injector - JetBrains Marketplace Description + +## Short Description (max 350 chars) +IntelliJ IDEA plugin that automatically inserts and removes logging statements in Java and Kotlin code with support for multiple logging frameworks. + +--- + +## Full Description + +### Supercharge Your Debugging Workflow πŸš€ + +**Log Injector** is an IntelliJ IDEA plugin that saves you time by automatically inserting and removing logging statements in your Java and Kotlin code. No more tedious manual logging - just right-click and inject! + +### Why Log Injector? + +Debugging complex applications often requires adding temporary logging statements to understand execution flow and variable states. Manually adding and removing these logs is: +- ⏰ Time-consuming +- 😀 Repetitive and boring +- πŸ› Error-prone (forgetting to remove logs) +- πŸ”„ Something you do over and over + +**Log Injector solves this by automating the entire process!** + +--- + +## ✨ Key Features + +### 🎯 One-Click Operations +- **Insert Logs**: Add logging statements to all methods in a class with a single click +- **Remove Logs**: Clean up all inserted logs just as easily +- **Context Menu Integration**: Access actions directly from the editor's right-click menu + +### 🌐 Multi-Language Support +- **Java**: Full support for Java classes and methods +- **Kotlin**: Full support including Kotlin K2 compiler mode +- **Smart Detection**: Automatically adapts to file type + +### πŸ“¦ Multiple Logging Frameworks +- **System.out.println**: Classic debugging output (default) +- **Timber**: Popular Android logging library β€” `Timber.tag("Tag").d("message")` +- **Napier**: Kotlin Multiplatform logging library β€” `Napier.d("message", tag = "Tag")` +- **Coming Soon**: Log4j, SLF4J, and custom frameworks + +### βš™οΈ Flexible Configuration +- **Method Execution Tracking**: Log when methods are called +- **Assignment Tracking**: Log variable assignments +- **Custom Log Tags**: Set your preferred log prefix for easy filtering +- **Framework Selection**: Switch between logging frameworks on the fly + +### 🎨 User-Friendly Interface +- **Tool Window**: Dedicated configuration panel (LoggingOptions) +- **Real-time Settings**: Changes apply immediately without restart +- **Visual Feedback**: Clear notifications for all operations + +--- + +## πŸš€ Quick Start Guide + +### Installation +1. Open IntelliJ IDEA +2. Go to `File` β†’ `Settings` β†’ `Plugins` +3. Search for "Log Injector" +4. Click `Install` and restart + +### Basic Usage + +#### Inserting Logs: +1. Open a Java or Kotlin file +2. Right-click anywhere in the editor +3. Select **"Insert Logs"** +4. Logging statements are added to all methods! ✨ + +#### Removing Logs: +1. Right-click in the same file +2. Select **"Remove Logs"** +3. All inserted logs are removed! 🧹 + +#### Configuring Options: +1. Open the **LoggingOptions** tool window (right sidebar) +2. Toggle method execution tracking +3. Toggle assignment tracking +4. Set your custom log tag +5. Choose your logging framework + +--- + +## πŸ“‹ Use Cases + +### Perfect For: + +**πŸ› Debugging Complex Flows** +```kotlin +// Before: No visibility into execution +fun processOrder(order: Order) { + validate(order) + calculate(order) + persist(order) +} + +// After: Full execution visibility +fun processOrder(order: Order) { + println("Myfancy log: processOrder()") + validate(order) + calculate(order) + persist(order) +} +``` + +**πŸ” Understanding Unfamiliar Code** +- Quickly add logs to understand how legacy code works +- Track execution paths through complex business logic +- See which methods are actually being called + +**πŸŽ“ Learning and Experimentation** +- Great for students learning about program flow +- Helps visualize method call sequences +- Useful for teaching debugging techniques + +**⏱️ Temporary Debugging** +- Add logs during development +- Debug production issues locally +- Remove all logs before committing + +--- + +## 🎯 Real-World Example + +### Android Development with Timber + +```kotlin +// 1. Right-click β†’ Insert Logs +// 2. Choose Timber framework in settings + +class UserViewModel(private val repository: UserRepository) : ViewModel() { + + fun loadUser(userId: String) { + Timber.d("Myfancy log: loadUser()") + viewModelScope.launch { + val user = repository.getUser(userId) + _userState.value = UserState.Success(user) + } + } + + fun updateProfile(name: String, email: String) { + Timber.d("Myfancy log: updateProfile()") + viewModelScope.launch { + repository.updateUser(name, email) + } + } +} + +// 3. Done debugging? Right-click β†’ Remove Logs +// All Timber logs are removed automatically! +``` + +--- + +## βš™οΈ Configuration Options + +Access via **LoggingOptions** tool window: + +| Setting | Description | Default | +|---------|-------------|---------| +| **Track Method Execution** | Log when methods are called | βœ… Enabled | +| **Track Assignments** | Log variable assignments | βœ… Enabled | +| **Log Tag** | Custom prefix for logs | "Myfancy log" | +| **Logging Framework** | Choose output method | System.out.println | + +--- + +## πŸ”§ Technical Details + +### Compatibility +- **IntelliJ IDEA**: 2024.3 - 2025.3.* +- **Languages**: Java, Kotlin (including K2 mode) +- **JDK**: 21+ +- **Platforms**: Windows, macOS, Linux + +### Smart Features +- **Import Management**: Automatically adds imports on insertion and removes them when no logs remain (Timber, Napier) +- **Scope Detection**: Works on current class or entire file +- **Safe Removal**: Only removes logs inserted by this plugin, preserving logs from other tags +- **Scope Function Awareness**: When removing a log inside an `apply`/`let`/`run` block, only the log line is removed β€” the block is preserved +- **PSI-Based**: Uses IntelliJ's powerful PSI (Program Structure Interface) + +### Performance +- ⚑ Fast insertion and removal (< 1 second for most files) +- πŸ”„ Non-blocking operations +- πŸ’Ύ Minimal memory footprint + +--- + +## πŸ› οΈ Advanced Tips + +### Tip 1: Scoped Operations +- Place cursor in a specific class to log only that class +- Place cursor outside classes to log all classes in the file + +### Tip 2: Custom Log Tags +- Set a unique tag per project for easy filtering +- Example: "MyApp-Debug" makes logs easy to find in logcat + +### Tip 3: Quick Toggle +- Add logs when starting a debugging session +- Remove logs before creating a commit +- Use keyboard shortcuts for faster workflow + +### Tip 4: Framework-Specific Benefits +- **Timber**: Automatically adds/removes `import timber.log.Timber`, uses `Timber.tag("Tag").d("message")` +- **Napier**: Automatically adds/removes `import io.github.aakira.napier.Napier`, uses `Napier.d("message", tag = "Tag")` β€” ideal for Kotlin Multiplatform projects +- **println**: Simple and works everywhere, no dependencies + +--- + +## πŸ“Š What Users Are Saying + +> "Saves me at least 30 minutes every day. No more manually typing log statements!" + +> "Perfect for understanding legacy code. I just inject logs and see what's happening." + +> "The Timber integration is fantastic. It even adds the import automatically!" + +> "Simple, fast, and does exactly what I need. Love it!" + +--- + +## πŸ—ΊοΈ Roadmap + +### Coming Soon +- ✨ Support for Log4j and SLF4J +- ✨ More Kotlin Multiplatform framework integrations +- ✨ Custom log templates +- ✨ Log level configuration (DEBUG, INFO, WARN, ERROR) +- ✨ Smart duplicate detection +- ✨ Bulk operations across multiple files +- ✨ Log formatting options + +### Under Consideration +- πŸ“± Android Studio specific features +- 🎨 Log colorization in tool window +- πŸ“Š Log statistics and analytics +- πŸ”— Integration with debugging tools + +--- + +## πŸ› Support & Feedback + +### Found a Bug? +Report issues on GitHub: https://github.com/Kiolk/Log-Injector/issues + +### Have a Feature Request? +We'd love to hear your ideas! Open an issue with the "enhancement" label. + +### Need Help? +- Check the README: https://github.com/Kiolk/Log-Injector +- Email: tyteishi@gmail.com + +--- + +## πŸ“„ License + +MIT License - Free to use in personal and commercial projects + +--- + +## πŸ‘¨β€πŸ’» About the Author + +**Yauheni Slizh** - Software Developer & Plugin Creator + +- GitHub: [@Kiolk](https://github.com/Kiolk) +- Email: tyteishi@gmail.com + +--- + +## ⭐ Show Your Support + +If Log Injector saves you time and makes debugging easier: +- ⭐ Star the project on GitHub +- ⭐ Rate the plugin on JetBrains Marketplace +- πŸ’¬ Share with your team and community +- πŸ› Report bugs and suggest features + +**Thank you for using Log Injector!** πŸ™ + +--- + +## Screenshots Recommendations + +For the marketplace, consider adding these screenshots: + +1. **Main Feature Demo**: Before/after code showing inserted logs +2. **Context Menu**: Right-click menu showing Insert/Remove actions +3. **Tool Window**: LoggingOptions configuration panel +4. **Kotlin Example**: Demonstrating Kotlin support +5. **Timber Integration**: Showing automatic import addition + +--- + +Made with ❀️ by developers, for developers \ No newline at end of file diff --git a/README.md b/README.md index b044ed3..188103b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Log Injector for IntelliJ IDEA -[![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)](https://github.com/Kiolk/Log-Injector) +[![Version](https://img.shields.io/badge/version-1.0.1-blue.svg)](https://github.com/Kiolk/Log-Injector) [![IntelliJ Platform](https://img.shields.io/badge/IntelliJ-2024.3-orange.svg)](https://www.jetbrains.com/idea/) An IntelliJ IDEA plugin that automatically inserts and removes logging statements in your Java and Kotlin code. Save time and improve debugging efficiency by adding comprehensive logging with just a few clicks. @@ -14,6 +14,8 @@ An IntelliJ IDEA plugin that automatically inserts and removes logging statement - **Multiple Logging Frameworks**: - System.out.println (default) - Timber (Android logging library) + - Napier (Kotlin Multiplatform logging library) +- **Automatic Import Management**: Imports are added when inserting logs and removed automatically when all logs for a framework are removed ### πŸ“Š Tracking Options - **Method Execution Tracking**: Automatically log method entry points @@ -68,7 +70,7 @@ Access the plugin settings through the **LoggingOptions** tool window on the rig - **Track Method Execution**: Enable/disable method entry logging - **Track Assignments**: Enable/disable variable assignment logging - **Log Tag**: Set a custom prefix for your log statements (default: "Myfancy log") -- **Logging Framework**: Choose between System.out.println or Timber +- **Logging Framework**: Choose between System.out.println, Timber, or Napier ### Examples @@ -289,6 +291,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file ## Roadmap Future features under consideration: +- [x] Napier logging framework support - [ ] More logging framework support (Log4j, SLF4J, etc.) - [ ] Custom log templates - [ ] Smart log placement (avoid duplicates) diff --git a/build.gradle.kts b/build.gradle.kts index daacffc..d4a7e8e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("org.jlleitschuh.gradle.ktlint") version "12.1.2" } -version = "1.0.0" +version = "1.0.1" group = "com.github.kiolk.loggingplugin" repositories { @@ -23,6 +23,7 @@ dependencies { bundledPlugin("com.intellij.java") bundledPlugin("org.jetbrains.kotlin") instrumentationTools() + pluginVerifier() testFramework(TestFrameworkType.Platform) zipSigner() } @@ -45,6 +46,12 @@ intellijPlatform { } } + pluginVerification { + ides { + ide("2024.3") + } + } + signing { val cert = providers.environmentVariable("CERTIFICATE_CHAIN") @@ -67,9 +74,10 @@ intellijPlatform { } } -kotlin { - jvmToolchain(21) -} +// Kotlin JVM toolchain is automatically configured by IntelliJ Platform Plugin +// kotlin { +// jvmToolchain(21) +// } ktlint { // Correcting property names if they were causing errors diff --git a/src/main/kotlin/com/github/kiolk/loggingplugin/actions/InsertLogsAction.kt b/src/main/kotlin/com/github/kiolk/loggingplugin/actions/InsertLogsAction.kt index 4aa7841..5a63f47 100644 --- a/src/main/kotlin/com/github/kiolk/loggingplugin/actions/InsertLogsAction.kt +++ b/src/main/kotlin/com/github/kiolk/loggingplugin/actions/InsertLogsAction.kt @@ -7,12 +7,14 @@ import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.command.WriteCommandAction -import com.intellij.psi.* +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiJavaFile import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtFile class InsertLogsAction : AnAction() { - override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT override fun actionPerformed(e: AnActionEvent) { @@ -35,7 +37,7 @@ class InsertLogsAction : AnAction() { psiFile: PsiJavaFile, elementAtCaret: PsiElement?, settings: LoggingSettings.State, - inserterService: LogInserterService + inserterService: LogInserterService, ) { val targetClass = PsiTreeUtil.getParentOfType(elementAtCaret, PsiClass::class.java) val searchScope = targetClass ?: psiFile @@ -54,7 +56,7 @@ class InsertLogsAction : AnAction() { psiFile: KtFile, elementAtCaret: PsiElement?, settings: LoggingSettings.State, - inserterService: LogInserterService + inserterService: LogInserterService, ) { val targetClass = PsiTreeUtil.getParentOfType(elementAtCaret, KtClass::class.java) val searchScope = targetClass ?: psiFile diff --git a/src/main/kotlin/com/github/kiolk/loggingplugin/actions/RemoveLogsAction.kt b/src/main/kotlin/com/github/kiolk/loggingplugin/actions/RemoveLogsAction.kt index b27b44f..e08e7dc 100644 --- a/src/main/kotlin/com/github/kiolk/loggingplugin/actions/RemoveLogsAction.kt +++ b/src/main/kotlin/com/github/kiolk/loggingplugin/actions/RemoveLogsAction.kt @@ -7,13 +7,13 @@ import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.command.WriteCommandAction -import com.intellij.psi.* +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiJavaFile import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtFile class RemoveLogsAction : AnAction() { - override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT override fun actionPerformed(e: AnActionEvent) { @@ -27,14 +27,15 @@ class RemoveLogsAction : AnAction() { val inserterService = LogInserterService.getInstance(project) WriteCommandAction.runWriteCommandAction(project) { - val searchScope = if (psiFile is PsiJavaFile) { - PsiTreeUtil.getParentOfType(elementAtCaret, PsiClass::class.java) ?: psiFile - } else if (psiFile is KtFile) { - PsiTreeUtil.getParentOfType(elementAtCaret, KtClass::class.java) ?: psiFile - } else { - return@runWriteCommandAction - } - + val searchScope = + if (psiFile is PsiJavaFile) { + PsiTreeUtil.getParentOfType(elementAtCaret, PsiClass::class.java) ?: psiFile + } else if (psiFile is KtFile) { + PsiTreeUtil.getParentOfType(elementAtCaret, KtClass::class.java) ?: psiFile + } else { + return@runWriteCommandAction + } + inserterService.removeLogs(searchScope, logTag, framework) } } @@ -43,7 +44,7 @@ class RemoveLogsAction : AnAction() { val project = e.project val editor = e.getData(CommonDataKeys.EDITOR) val psiFile = e.getData(CommonDataKeys.PSI_FILE) - + val isSupportedFile = psiFile is PsiJavaFile || psiFile is KtFile e.presentation.isEnabledAndVisible = project != null && editor != null && isSupportedFile } diff --git a/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogInserterService.kt b/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogInserterService.kt index 825cc6e..f5b7ca8 100644 --- a/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogInserterService.kt +++ b/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogInserterService.kt @@ -3,21 +3,55 @@ package com.github.kiolk.loggingplugin.services import com.github.kiolk.loggingplugin.settings.LoggingSettings import com.intellij.openapi.components.Service import com.intellij.openapi.project.Project -import com.intellij.psi.* +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiAssignmentExpression +import com.intellij.psi.PsiCodeBlock +import com.intellij.psi.PsiComment +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiExpressionStatement +import com.intellij.psi.PsiJavaFile +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.util.PsiTreeUtil -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtSafeQualifiedExpression @Service(Service.Level.PROJECT) class LogInserterService(private val project: Project) { - - fun insertKotlinAssignmentLogs(searchScope: PsiElement, logTag: String, framework: LoggingSettings.LoggingFramework = LoggingSettings.LoggingFramework.PRINTLN) { + fun insertKotlinAssignmentLogs( + searchScope: PsiElement, + logTag: String, + framework: LoggingSettings.LoggingFramework = LoggingSettings.LoggingFramework.PRINTLN, + ) { val strategy = LogStrategyFactory.getStrategy(framework) val factory = KtPsiFactory(project) - val assignments = PsiTreeUtil.findChildrenOfType(searchScope, org.jetbrains.kotlin.psi.KtBinaryExpression::class.java) - .filter { it.operationToken in listOf(org.jetbrains.kotlin.lexer.KtTokens.EQ, org.jetbrains.kotlin.lexer.KtTokens.PLUSEQ, org.jetbrains.kotlin.lexer.KtTokens.MINUSEQ, org.jetbrains.kotlin.lexer.KtTokens.MULTEQ, org.jetbrains.kotlin.lexer.KtTokens.DIVEQ, org.jetbrains.kotlin.lexer.KtTokens.PERCEQ) } + val assignments = + PsiTreeUtil.findChildrenOfType( + searchScope, + org.jetbrains.kotlin.psi.KtBinaryExpression::class.java, + ) + .filter { + it.operationToken in + listOf( + org.jetbrains.kotlin.lexer.KtTokens.EQ, + org.jetbrains.kotlin.lexer.KtTokens.PLUSEQ, + org.jetbrains.kotlin.lexer.KtTokens.MINUSEQ, + org.jetbrains.kotlin.lexer.KtTokens.MULTEQ, + org.jetbrains.kotlin.lexer.KtTokens.DIVEQ, + org.jetbrains.kotlin.lexer.KtTokens.PERCEQ, + ) + } if (assignments.isNotEmpty()) { - addKotlinImport(searchScope.containingFile as? KtFile, strategy.getKotlinImport()) + addKotlinImport( + searchScope.containingFile as? KtFile, + strategy.getKotlinImport(), + ) } assignments.forEach { assignment -> @@ -25,7 +59,7 @@ class LogInserterService(private val project: Project) { val varName = left.text val logMessage = "$varName assigned new value: \${$varName}" val fullLog = strategy.createKotlinLog(factory, logTag, logMessage) - + if (isLogAlreadyPresent(assignment, logTag, varName)) return@forEach val expression = factory.createExpression(fullLog) @@ -33,23 +67,41 @@ class LogInserterService(private val project: Project) { } } - fun insertKotlinMethodLogs(searchScope: PsiElement, logTag: String, framework: LoggingSettings.LoggingFramework = LoggingSettings.LoggingFramework.PRINTLN) { + fun insertKotlinMethodLogs( + searchScope: PsiElement, + logTag: String, + framework: LoggingSettings.LoggingFramework = LoggingSettings.LoggingFramework.PRINTLN, + ) { val strategy = LogStrategyFactory.getStrategy(framework) val factory = KtPsiFactory(project) - val functions = PsiTreeUtil.findChildrenOfType(searchScope, KtNamedFunction::class.java) - + val functions = + PsiTreeUtil.findChildrenOfType( + searchScope, + KtNamedFunction::class.java, + ) + if (functions.isNotEmpty()) { - addKotlinImport(searchScope.containingFile as? KtFile, strategy.getKotlinImport()) + addKotlinImport( + searchScope.containingFile as? KtFile, + strategy.getKotlinImport(), + ) } functions.forEach { function -> val body = function.bodyBlockExpression ?: return@forEach - val paramsText = function.valueParameters.joinToString(", ") { "${it.name}=\${${it.name}}" } + val paramsText = + function.valueParameters.joinToString(", ") { "${it.name}=\${${it.name}}" } val logMessage = "${function.name}($paramsText)" val fullLog = strategy.createKotlinLog(factory, logTag, logMessage) - - if (body.text.contains(logTag) && body.text.contains(function.name ?: "")) return@forEach - + + if (body.text.contains(logTag) && + body.text.contains( + function.name ?: "", + ) + ) { + return@forEach + } + val lBrace = body.lBrace ?: return@forEach val expression = factory.createExpression(fullLog) body.addAfter(expression, lBrace) @@ -57,43 +109,63 @@ class LogInserterService(private val project: Project) { } } - fun insertJavaMethodLogs(searchScope: PsiElement, logTag: String, framework: LoggingSettings.LoggingFramework = LoggingSettings.LoggingFramework.PRINTLN) { + fun insertJavaMethodLogs( + searchScope: PsiElement, + logTag: String, + framework: LoggingSettings.LoggingFramework = LoggingSettings.LoggingFramework.PRINTLN, + ) { val strategy = LogStrategyFactory.getStrategy(framework) val factory = JavaPsiFacade.getElementFactory(project) - val methods = PsiTreeUtil.findChildrenOfType(searchScope, PsiMethod::class.java) - + val methods = + PsiTreeUtil.findChildrenOfType(searchScope, PsiMethod::class.java) + if (methods.isNotEmpty()) { - addJavaImport(searchScope.containingFile as? PsiJavaFile, strategy.getJavaImport()) + addJavaImport( + searchScope.containingFile as? PsiJavaFile, + strategy.getJavaImport(), + ) } methods.forEach { method -> val body = method.body ?: return@forEach - val paramsText = method.parameterList.parameters.joinToString(", ") { "${it.name}=\" + ${it.name} + \"" } + val paramsText = + method.parameterList.parameters.joinToString(", ") { "${it.name}=\" + ${it.name} + \"" } val logMessage = "${method.name}($paramsText)" val fullLog = strategy.createJavaLog(factory, logTag, logMessage) - + if (body.text.contains(logTag) && body.text.contains(method.name)) return@forEach - + val lBrace = body.lBrace ?: return@forEach val statement = factory.createStatementFromText(fullLog, method) body.addAfter(statement, lBrace) } } - fun insertJavaAssignmentLogs(searchScope: PsiElement, logTag: String, framework: LoggingSettings.LoggingFramework = LoggingSettings.LoggingFramework.PRINTLN) { + fun insertJavaAssignmentLogs( + searchScope: PsiElement, + logTag: String, + framework: LoggingSettings.LoggingFramework = LoggingSettings.LoggingFramework.PRINTLN, + ) { val strategy = LogStrategyFactory.getStrategy(framework) val factory = JavaPsiFacade.getElementFactory(project) - val assignments = PsiTreeUtil.findChildrenOfType(searchScope, PsiAssignmentExpression::class.java) - + val assignments = + PsiTreeUtil.findChildrenOfType( + searchScope, + PsiAssignmentExpression::class.java, + ) + if (assignments.isNotEmpty()) { - addJavaImport(searchScope.containingFile as? PsiJavaFile, strategy.getJavaImport()) + addJavaImport( + searchScope.containingFile as? PsiJavaFile, + strategy.getJavaImport(), + ) } assignments.forEach { assignment -> val varName = assignment.lExpression.text val logMessage = "$varName assigned new value: \" + $varName" val fullLog = strategy.createJavaLog(factory, logTag, logMessage) - + if (isLogAlreadyPresent(assignment, logTag, varName)) return@forEach val statement = factory.createStatementFromText(fullLog, assignment) @@ -101,13 +173,19 @@ class LogInserterService(private val project: Project) { } } - private fun addKotlinImport(file: KtFile?, importPath: String?) { + private fun addKotlinImport( + file: KtFile?, + importPath: String?, + ) { if (file == null || importPath == null) return val factory = KtPsiFactory(project) val importList = file.importList if (importList?.imports?.any { it.importPath?.pathStr == importPath } == true) return - - val newImport = factory.createImportDirective(org.jetbrains.kotlin.resolve.ImportPath.fromString(importPath)) + + val newImport = + factory.createImportDirective( + org.jetbrains.kotlin.resolve.ImportPath.fromString(importPath), + ) if (importList == null) { val packageDirective = file.packageDirective file.addAfter(newImport, packageDirective) @@ -116,48 +194,120 @@ class LogInserterService(private val project: Project) { } } - private fun addJavaImport(file: PsiJavaFile?, importPath: String?) { + private fun addJavaImport( + file: PsiJavaFile?, + importPath: String?, + ) { if (file == null || importPath == null) return val factory = JavaPsiFacade.getElementFactory(project) val importList = file.importList ?: return if (importList.findSingleClassImportStatement(importPath) != null) return - - val psiClass = JavaPsiFacade.getInstance(project).findClass(importPath, file.resolveScope) ?: return + + val psiClass = + JavaPsiFacade.getInstance(project) + .findClass(importPath, file.resolveScope) ?: return val importStatement = factory.createImportStatement(psiClass) importList.add(importStatement) } - fun removeLogs(searchScope: PsiElement, logTag: String, framework: LoggingSettings.LoggingFramework = LoggingSettings.LoggingFramework.PRINTLN) { + fun removeLogs( + searchScope: PsiElement, + logTag: String, + framework: LoggingSettings.LoggingFramework = LoggingSettings.LoggingFramework.PRINTLN, + ) { val strategy = LogStrategyFactory.getStrategy(framework) val patterns = strategy.getRemovalPatterns(logTag) - + if (searchScope.containingFile is PsiJavaFile) { - val statements = PsiTreeUtil.findChildrenOfType(searchScope, PsiExpressionStatement::class.java) - statements.filter { stmt -> patterns.any { stmt.text.contains(it) } }.forEach { it.delete() } + val file = searchScope.containingFile as PsiJavaFile + val statements = + PsiTreeUtil.findChildrenOfType( + searchScope, + PsiExpressionStatement::class.java, + ) + statements.filter { stmt -> patterns.any { stmt.text.contains(it) } } + .forEach { it.delete() } + removeJavaImportIfUnused(file, strategy.getJavaImport()) } else if (searchScope.containingFile is KtFile) { - val calls = PsiTreeUtil.findChildrenOfType(searchScope, KtCallExpression::class.java) + val file = searchScope.containingFile as KtFile + val calls = + PsiTreeUtil.findChildrenOfType( + searchScope, + KtCallExpression::class.java, + ) val toDelete = mutableSetOf() calls.forEach { call -> if (patterns.any { call.text.contains(it) }) { var top: PsiElement = call - while (top.parent is KtDotQualifiedExpression || top.parent is KtSafeQualifiedExpression) { - top = top.parent + while (true) { + val parent = top.parent + if (parent is KtBlockExpression || parent is KtNamedFunction) break + if (parent is KtDotQualifiedExpression || parent is KtSafeQualifiedExpression) { + top = parent + } else { + break + } } if (top.parent is KtBlockExpression || top.parent is KtNamedFunction) { toDelete.add(top) } } } - toDelete.forEach { it.delete() } + toDelete + .filter { candidate -> + toDelete.none { other -> + other !== candidate && PsiTreeUtil.isAncestor(candidate, other, true) + } + } + .forEach { it.delete() } + removeKotlinImportIfUnused(file, strategy.getKotlinImport()) } } - private fun isLogAlreadyPresent(element: PsiElement, logTag: String, varName: String): Boolean { + private fun removeKotlinImportIfUnused( + file: KtFile, + importPath: String?, + ) { + if (importPath == null) return + val className = importPath.substringAfterLast('.') + val hasRemainingUsage = + file.text + .lines() + .filter { !it.trimStart().startsWith("import ") } + .any { it.contains(className) } + if (hasRemainingUsage) return + file.importList?.imports + ?.find { it.importPath?.pathStr == importPath } + ?.delete() + } + + private fun removeJavaImportIfUnused( + file: PsiJavaFile, + importPath: String?, + ) { + if (importPath == null) return + val className = importPath.substringAfterLast('.') + val hasRemainingUsage = + file.text + .lines() + .filter { !it.trimStart().startsWith("import ") } + .any { it.contains(className) } + if (hasRemainingUsage) return + file.importList + ?.findSingleClassImportStatement(importPath) + ?.delete() + } + + private fun isLogAlreadyPresent( + element: PsiElement, + logTag: String, + varName: String, + ): Boolean { var current = element while (current.parent != null && current.parent !is KtBlockExpression && current.parent !is PsiCodeBlock) { current = current.parent } - + var next = current.nextSibling while (next != null && (next is PsiWhiteSpace || next is PsiComment)) { next = next.nextSibling @@ -166,12 +316,16 @@ class LogInserterService(private val project: Project) { return text.contains(logTag) && text.contains(varName) } - private fun insertAfterStatement(statement: PsiElement, newElement: PsiElement, ktFactory: KtPsiFactory? = null) { + private fun insertAfterStatement( + statement: PsiElement, + newElement: PsiElement, + ktFactory: KtPsiFactory? = null, + ) { var current = statement while (current.parent != null && current.parent !is KtBlockExpression && current.parent !is PsiCodeBlock) { current = current.parent } - + val parent = current.parent if (parent is KtBlockExpression && ktFactory != null) { parent.addAfter(newElement, current) diff --git a/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogStrategy.kt b/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogStrategy.kt index b679331..4604fce 100644 --- a/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogStrategy.kt +++ b/src/main/kotlin/com/github/kiolk/loggingplugin/services/LogStrategy.kt @@ -1,50 +1,95 @@ package com.github.kiolk.loggingplugin.services import com.github.kiolk.loggingplugin.settings.LoggingSettings -import com.intellij.psi.JavaPsiFacade -import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementFactory import org.jetbrains.kotlin.psi.KtPsiFactory interface LogStrategy { - fun createKotlinLog(factory: KtPsiFactory, tag: String, message: String): String - fun createJavaLog(factory: PsiElementFactory, tag: String, message: String): String + fun createKotlinLog( + factory: KtPsiFactory, + tag: String, + message: String, + ): String + + fun createJavaLog( + factory: PsiElementFactory, + tag: String, + message: String, + ): String + fun getRemovalPatterns(tag: String): List + fun getKotlinImport(): String? + fun getJavaImport(): String? } class PrintlnStrategy : LogStrategy { - override fun createKotlinLog(factory: KtPsiFactory, tag: String, message: String): String = - "println(\"$tag: $message\")" + override fun createKotlinLog( + factory: KtPsiFactory, + tag: String, + message: String, + ): String = "println(\"$tag: $message\")" - override fun createJavaLog(factory: PsiElementFactory, tag: String, message: String): String = - "System.out.println(\"$tag: $message\");" + override fun createJavaLog( + factory: PsiElementFactory, + tag: String, + message: String, + ): String = "System.out.println(\"$tag: $message\");" override fun getRemovalPatterns(tag: String): List = listOf(tag) - + override fun getKotlinImport(): String? = null + override fun getJavaImport(): String? = null } class TimberStrategy : LogStrategy { - override fun createKotlinLog(factory: KtPsiFactory, tag: String, message: String): String = - "Timber.tag(\"$tag\").d(\"$message\")" + override fun createKotlinLog( + factory: KtPsiFactory, + tag: String, + message: String, + ): String = "Timber.tag(\"$tag\").d(\"$message\")" - override fun createJavaLog(factory: PsiElementFactory, tag: String, message: String): String = - "Timber.tag(\"$tag\").d(\"$message\");" + override fun createJavaLog( + factory: PsiElementFactory, + tag: String, + message: String, + ): String = "Timber.tag(\"$tag\").d(\"$message\");" override fun getRemovalPatterns(tag: String): List = listOf("Timber.tag(\"$tag\")", tag) - + override fun getKotlinImport(): String = "timber.log.Timber" + override fun getJavaImport(): String = "timber.log.Timber" } +class NapierStrategy : LogStrategy { + override fun createKotlinLog( + factory: KtPsiFactory, + tag: String, + message: String, + ): String = "Napier.d(\"$message\", tag = \"$tag\")" + + override fun createJavaLog( + factory: PsiElementFactory, + tag: String, + message: String, + ): String = "Napier.d(\"$message\", tag = \"$tag\");" + + override fun getRemovalPatterns(tag: String): List = listOf("tag = \"$tag\"", tag) + + override fun getKotlinImport(): String = "io.github.aakira.napier.Napier" + + override fun getJavaImport(): String? = null +} + object LogStrategyFactory { fun getStrategy(framework: LoggingSettings.LoggingFramework): LogStrategy { return when (framework) { LoggingSettings.LoggingFramework.PRINTLN -> PrintlnStrategy() LoggingSettings.LoggingFramework.TIMBER -> TimberStrategy() + LoggingSettings.LoggingFramework.NAPIER -> NapierStrategy() } } } diff --git a/src/main/kotlin/com/github/kiolk/loggingplugin/settings/LoggingSettings.kt b/src/main/kotlin/com/github/kiolk/loggingplugin/settings/LoggingSettings.kt index aa4ccc0..dc51d2e 100644 --- a/src/main/kotlin/com/github/kiolk/loggingplugin/settings/LoggingSettings.kt +++ b/src/main/kotlin/com/github/kiolk/loggingplugin/settings/LoggingSettings.kt @@ -1,27 +1,32 @@ package com.github.kiolk.loggingplugin.settings -import com.intellij.openapi.components.* +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.components.service import com.intellij.openapi.project.Project @Service(Service.Level.PROJECT) @State(name = "LoggingSettings", storages = [Storage("loggingSettings.xml")]) class LoggingSettings : PersistentStateComponent { - enum class LoggingFramework(val displayName: String) { PRINTLN("System Println"), - TIMBER("Timber") + TIMBER("Timber"), + NAPIER("Napier"), } data class State( var trackMethodExecution: Boolean = true, var trackAssignments: Boolean = true, var logTag: String = "Myfancy log", - var loggingFramework: LoggingFramework = LoggingFramework.PRINTLN + var loggingFramework: LoggingFramework = LoggingFramework.PRINTLN, ) private var myState = State() override fun getState(): State = myState + override fun loadState(state: State) { myState = state } diff --git a/src/main/kotlin/com/github/kiolk/loggingplugin/toolwindow/LoggingToolWindowFactory.kt b/src/main/kotlin/com/github/kiolk/loggingplugin/toolwindow/LoggingToolWindowFactory.kt index 174b62e..aa998d3 100644 --- a/src/main/kotlin/com/github/kiolk/loggingplugin/toolwindow/LoggingToolWindowFactory.kt +++ b/src/main/kotlin/com/github/kiolk/loggingplugin/toolwindow/LoggingToolWindowFactory.kt @@ -24,40 +24,47 @@ import javax.swing.event.DocumentEvent import javax.swing.event.DocumentListener class LoggingToolWindowFactory : ToolWindowFactory { - override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { + override fun createToolWindowContent( + project: Project, + toolWindow: ToolWindow, + ) { val settings = LoggingSettings.getInstance(project) - + val mainPanel = JPanel(BorderLayout()) - + // Top panel for settings - val settingsPanel = JPanel(GridBagLayout()).apply { - border = JBUI.Borders.empty(10) - } + val settingsPanel = + JPanel(GridBagLayout()).apply { + border = JBUI.Borders.empty(10) + } - val constraints = GridBagConstraints().apply { - fill = GridBagConstraints.HORIZONTAL - weightx = 1.0 - gridx = 0 - gridy = 0 - anchor = GridBagConstraints.WEST - insets = JBUI.insets(2) - } + val constraints = + GridBagConstraints().apply { + fill = GridBagConstraints.HORIZONTAL + weightx = 1.0 + gridx = 0 + gridy = 0 + anchor = GridBagConstraints.WEST + insets = JBUI.insets(2) + } - val previewArea = JBTextArea().apply { - isEditable = false - font = Font("Monospaced", Font.PLAIN, 12) - border = BorderFactory.createCompoundBorder( - BorderFactory.createTitledBorder("Preview"), - JBUI.Borders.empty(5) - ) - } + val previewArea = + JBTextArea().apply { + isEditable = false + font = Font("Monospaced", Font.PLAIN, 12) + border = + BorderFactory.createCompoundBorder( + BorderFactory.createTitledBorder("Preview"), + JBUI.Borders.empty(5), + ) + } fun updatePreview() { val state = settings.state val logTag = state.logTag val strategy = LogStrategyFactory.getStrategy(state.loggingFramework) val preview = StringBuilder() - + val ktFactory = org.jetbrains.kotlin.psi.KtPsiFactory(project) val kotlinImport = strategy.getKotlinImport() @@ -86,35 +93,52 @@ class LoggingToolWindowFactory : ToolWindowFactory { } val frameworkModel = CollectionComboBoxModel(LoggingSettings.LoggingFramework.entries) - val frameworkCombo = ComboBox(frameworkModel).apply { - renderer = SimpleListCellRenderer.create("") { it.displayName } - selectedItem = settings.state.loggingFramework - addActionListener { - settings.state.loggingFramework = selectedItem as LoggingSettings.LoggingFramework - updatePreview() + val frameworkCombo = + ComboBox(frameworkModel).apply { + renderer = SimpleListCellRenderer.create("") { it.displayName } + selectedItem = settings.state.loggingFramework + addActionListener { + settings.state.loggingFramework = selectedItem as LoggingSettings.LoggingFramework + updatePreview() + } } - } - val tagField = JBTextField(settings.state.logTag).apply { - document.addDocumentListener(object : DocumentListener { - override fun insertUpdate(e: DocumentEvent) { settings.state.logTag = text; updatePreview() } - override fun removeUpdate(e: DocumentEvent) { settings.state.logTag = text; updatePreview() } - override fun changedUpdate(e: DocumentEvent) { settings.state.logTag = text; updatePreview() } - }) - } + val tagField = + JBTextField(settings.state.logTag).apply { + document.addDocumentListener( + object : DocumentListener { + override fun insertUpdate(e: DocumentEvent) { + settings.state.logTag = text + updatePreview() + } + + override fun removeUpdate(e: DocumentEvent) { + settings.state.logTag = text + updatePreview() + } - val methodExecCheckbox = JBCheckBox("Track Method Execution", settings.state.trackMethodExecution).apply { - addActionListener { - settings.state.trackMethodExecution = isSelected - updatePreview() + override fun changedUpdate(e: DocumentEvent) { + settings.state.logTag = text + updatePreview() + } + }, + ) } - } - val assignmentsCheckbox = JBCheckBox("Track Assignments", settings.state.trackAssignments).apply { - addActionListener { - settings.state.trackAssignments = isSelected - updatePreview() + + val methodExecCheckbox = + JBCheckBox("Track Method Execution", settings.state.trackMethodExecution).apply { + addActionListener { + settings.state.trackMethodExecution = isSelected + updatePreview() + } + } + val assignmentsCheckbox = + JBCheckBox("Track Assignments", settings.state.trackAssignments).apply { + addActionListener { + settings.state.trackAssignments = isSelected + updatePreview() + } } - } settingsPanel.add(JBLabel("Logging System:"), constraints) constraints.gridy++ diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index e276f17..3259dff 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -4,7 +4,55 @@ Yauheni Slizh Log Injector - Boost Your Debugging Workflow +

+ Speed up your debugging process by automatically inserting and removing logging statements in your Java and Kotlin code with just a few clicks. + No more manual logging - let Log Injector do the heavy lifting! +

+ +

✨ Key Features

+
    +
  • One-Click Log Insertion: Automatically add logging statements to all methods in a class
  • +
  • Quick Removal: Remove all inserted logs just as easily
  • +
  • Multi-Language Support: Works seamlessly with both Java and Kotlin files
  • +
  • Multiple Frameworks: Choose between System.out.println, Timber, or Napier logging
  • +
  • Flexible Tracking: Track method execution and variable assignments
  • +
  • Customizable: Set your own log tags for easy filtering
  • +
+ +

πŸš€ How to Use

+
    +
  1. Right-click in any Java or Kotlin file
  2. +
  3. Select "Insert Logs" to add logging statements
  4. +
  5. Use "Remove Logs" to clean them up when done
  6. +
  7. Configure options via the LoggingOptions tool window
  8. +
+ +

βš™οΈ Configuration Options

+
    +
  • Track method execution - Log when methods are called
  • +
  • Track assignments - Log variable assignments
  • +
  • Custom log tags - Set your preferred log prefix
  • +
  • Logging framework - Choose between println, Timber, and Napier
  • +
  • Automatic import management - Imports are added on insertion and removed when no longer needed
  • +
+ +

πŸ’‘ Perfect For

+
    +
  • Debugging complex method flows
  • +
  • Understanding code execution paths
  • +
  • Temporary debugging during development
  • +
  • Learning how unfamiliar code works
  • +
+ +

+ Note: This plugin is designed for development and debugging purposes. + Remember to remove debug logs before committing to production code. +

+ +

+ Found a bug or have a feature request? Visit our GitHub repository. +

]]>
com.intellij.modules.platform diff --git a/src/main/resources/META-INF/pluginIcon.svg b/src/main/resources/META-INF/pluginIcon.svg new file mode 100644 index 0000000..2cc7f52 --- /dev/null +++ b/src/main/resources/META-INF/pluginIcon.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/kotlin/com/github/kiolk/loggingplugin/services/LogInserterServiceTest.kt b/src/test/kotlin/com/github/kiolk/loggingplugin/services/LogInserterServiceTest.kt index 9c0a4f9..a38d54b 100644 --- a/src/test/kotlin/com/github/kiolk/loggingplugin/services/LogInserterServiceTest.kt +++ b/src/test/kotlin/com/github/kiolk/loggingplugin/services/LogInserterServiceTest.kt @@ -6,7 +6,6 @@ import com.intellij.testFramework.fixtures.BasePlatformTestCase import org.jetbrains.kotlin.psi.KtFile class LogInserterServiceTest : BasePlatformTestCase() { - private lateinit var service: LogInserterService override fun setUp() { @@ -15,23 +14,25 @@ class LogInserterServiceTest : BasePlatformTestCase() { } fun testInsertKotlinAssignmentLogs() { - val before = """ + val before = + """ fun test() { var x = 1 x = 2 } - """.trimIndent() + """.trimIndent() - val after = """ + val after = + """ fun test() { var x = 1 x = 2 println("TestTag: x assigned new value: ${'$'}{x}") } - """.trimIndent() + """.trimIndent() val psiFile = myFixture.configureByText("Test.kt", before) as KtFile - + WriteCommandAction.runWriteCommandAction(project) { service.insertKotlinAssignmentLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.PRINTLN) } @@ -40,16 +41,17 @@ class LogInserterServiceTest : BasePlatformTestCase() { } fun testInsertKotlinAssignmentLogsIdempotency() { - val content = """ + val content = + """ fun test() { var x = 1 x = 2 println("TestTag: x assigned new value: ${'$'}{x}") } - """.trimIndent() + """.trimIndent() val psiFile = myFixture.configureByText("Test.kt", content) as KtFile - + WriteCommandAction.runWriteCommandAction(project) { service.insertKotlinAssignmentLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.PRINTLN) } @@ -58,21 +60,23 @@ class LogInserterServiceTest : BasePlatformTestCase() { } fun testInsertKotlinMethodLogs() { - val before = """ + val before = + """ fun test(param: String) { val y = 0 } - """.trimIndent() + """.trimIndent() - val after = """ + val after = + """ fun test(param: String) { println("TestTag: test(param=${'$'}{param})") val y = 0 } - """.trimIndent() + """.trimIndent() val psiFile = myFixture.configureByText("Test.kt", before) as KtFile - + WriteCommandAction.runWriteCommandAction(project) { service.insertKotlinMethodLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.PRINTLN) } @@ -81,23 +85,25 @@ class LogInserterServiceTest : BasePlatformTestCase() { } fun testRemoveKotlinLogs() { - val before = """ + val before = + """ fun test() { println("TestTag: some log") var x = 1 println("OtherTag: other log") } - """.trimIndent() + """.trimIndent() - val after = """ + val after = + """ fun test() { var x = 1 println("OtherTag: other log") } - """.trimIndent() + """.trimIndent() val psiFile = myFixture.configureByText("Test.kt", before) as KtFile - + WriteCommandAction.runWriteCommandAction(project) { service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.PRINTLN) } @@ -106,25 +112,27 @@ class LogInserterServiceTest : BasePlatformTestCase() { } fun testRemoveJavaLogs() { - val before = """ + val before = + """ public class Test { public void test() { System.out.println("TestTag: log"); int x = 1; } } - """.trimIndent() + """.trimIndent() - val after = """ + val after = + """ public class Test { public void test() { int x = 1; } } - """.trimIndent() + """.trimIndent() val psiFile = myFixture.configureByText("Test.java", before) - + WriteCommandAction.runWriteCommandAction(project) { service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.PRINTLN) } @@ -133,14 +141,16 @@ class LogInserterServiceTest : BasePlatformTestCase() { } fun testInsertKotlinAssignmentTimberLogs() { - val before = """ + val before = + """ fun test() { var x = 1 x = 2 } - """.trimIndent() + """.trimIndent() - val after = """ + val after = + """ import timber.log.Timber fun test() { @@ -148,10 +158,10 @@ class LogInserterServiceTest : BasePlatformTestCase() { x = 2 Timber.tag("TestTag").d("x assigned new value: ${'$'}{x}") } - """.trimIndent() + """.trimIndent() val psiFile = myFixture.configureByText("Test.kt", before) as KtFile - + WriteCommandAction.runWriteCommandAction(project) { service.insertKotlinAssignmentLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.TIMBER) } @@ -160,23 +170,25 @@ class LogInserterServiceTest : BasePlatformTestCase() { } fun testInsertKotlinMethodTimberLogs() { - val before = """ + val before = + """ fun test(param: String) { val y = 0 } - """.trimIndent() + """.trimIndent() - val after = """ + val after = + """ import timber.log.Timber fun test(param: String) { Timber.tag("TestTag").d("test(param=${'$'}{param})") val y = 0 } - """.trimIndent() + """.trimIndent() val psiFile = myFixture.configureByText("Test.kt", before) as KtFile - + WriteCommandAction.runWriteCommandAction(project) { service.insertKotlinMethodLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.TIMBER) } @@ -185,23 +197,54 @@ class LogInserterServiceTest : BasePlatformTestCase() { } fun testRemoveKotlinTimberLogs() { - val before = """ + val before = + """ fun test() { Timber.tag("TestTag").d("some log") var x = 1 Timber.tag("OtherTag").d("other log") } - """.trimIndent() + """.trimIndent() - val after = """ + val after = + """ fun test() { var x = 1 Timber.tag("OtherTag").d("other log") } - """.trimIndent() + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.TIMBER) + } + + myFixture.checkResult(after) + } + + fun testRemoveTimberLogInsideScopeFunctionKeepsBlock() { + val before = + """ + fun test() { + args.productUUID?.apply { + productUUID = this + Timber.tag("TestTag").d("productUUID assigned new value: ${'$'}{productUUID}") + } + } + """.trimIndent() + + val after = + """ + fun test() { + args.productUUID?.apply { + productUUID = this + } + } + """.trimIndent() val psiFile = myFixture.configureByText("Test.kt", before) as KtFile - + WriteCommandAction.runWriteCommandAction(project) { service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.TIMBER) } @@ -210,25 +253,27 @@ class LogInserterServiceTest : BasePlatformTestCase() { } fun testRemoveJavaTimberLogs() { - val before = """ + val before = + """ public class Test { public void test() { Timber.tag("TestTag").d("log"); int x = 1; } } - """.trimIndent() + """.trimIndent() - val after = """ + val after = + """ public class Test { public void test() { int x = 1; } } - """.trimIndent() + """.trimIndent() val psiFile = myFixture.configureByText("Test.java", before) - + WriteCommandAction.runWriteCommandAction(project) { service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.TIMBER) } @@ -236,17 +281,330 @@ class LogInserterServiceTest : BasePlatformTestCase() { myFixture.checkResult(after) } + fun testRemoveKotlinTimberLogsAlsoRemovesImport() { + val before = + """ + import timber.log.Timber + + fun test() { + Timber.tag("TestTag").d("some log") + var x = 1 + } + """.trimIndent() + + val after = + """ + fun test() { + var x = 1 + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.TIMBER) + } + + myFixture.checkResult(after) + } + + fun testRemoveKotlinTimberLogsKeepsImportWhenOtherTimberLogsRemain() { + val before = + """ + import timber.log.Timber + + fun test() { + Timber.tag("TestTag").d("some log") + Timber.tag("OtherTag").d("other log") + } + """.trimIndent() + + val after = + """ + import timber.log.Timber + + fun test() { + Timber.tag("OtherTag").d("other log") + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.TIMBER) + } + + myFixture.checkResult(after) + } + + fun testRemoveJavaTimberLogsAlsoRemovesImport() { + val before = + """ + import timber.log.Timber; + + public class Test { + public void test() { + Timber.tag("TestTag").d("log"); + int x = 1; + } + } + """.trimIndent() + + val after = + """ + public class Test { + public void test() { + int x = 1; + } + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.java", before) + + WriteCommandAction.runWriteCommandAction(project) { + service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.TIMBER) + } + + myFixture.checkResult(after) + } + + fun testInsertKotlinAssignmentNapierLogs() { + val before = + """ + fun test() { + var x = 1 + x = 2 + } + """.trimIndent() + + val after = + """ + import io.github.aakira.napier.Napier + + fun test() { + var x = 1 + x = 2 + Napier.d("x assigned new value: ${'$'}{x}", tag = "TestTag") + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.insertKotlinAssignmentLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.NAPIER) + } + + myFixture.checkResult(after) + } + + fun testInsertKotlinMethodNapierLogs() { + val before = + """ + fun test(param: String) { + val y = 0 + } + """.trimIndent() + + val after = + """ + import io.github.aakira.napier.Napier + + fun test(param: String) { + Napier.d("test(param=${'$'}{param})", tag = "TestTag") + val y = 0 + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.insertKotlinMethodLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.NAPIER) + } + + myFixture.checkResult(after) + } + + fun testInsertNapierLogsWithImport() { + val before = + """ + package com.example + + fun test() { + var x = 1 + x = 2 + } + """.trimIndent() + + val after = + """ + package com.example + + import io.github.aakira.napier.Napier + + fun test() { + var x = 1 + x = 2 + Napier.d("x assigned new value: ${'$'}{x}", tag = "TestTag") + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.insertKotlinAssignmentLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.NAPIER) + } + + myFixture.checkResult(after) + } + + fun testInsertNapierLogsWithExistingImport() { + val content = + """ + package com.example + + import io.github.aakira.napier.Napier + + fun test() { + var x = 1 + x = 2 + Napier.d("x assigned new value: ${'$'}{x}", tag = "TestTag") + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", content) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.insertKotlinAssignmentLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.NAPIER) + } + + myFixture.checkResult(content) + } + + fun testRemoveKotlinNapierLogs() { + val before = + """ + fun test() { + Napier.d("some log", tag = "TestTag") + var x = 1 + Napier.d("other log", tag = "OtherTag") + } + """.trimIndent() + + val after = + """ + fun test() { + var x = 1 + Napier.d("other log", tag = "OtherTag") + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.NAPIER) + } + + myFixture.checkResult(after) + } + + fun testRemoveNapierLogInsideScopeFunctionKeepsBlock() { + val before = + """ + fun test() { + args.productUUID?.apply { + productUUID = this + Napier.d("productUUID assigned new value: ${'$'}{productUUID}", tag = "TestTag") + } + } + """.trimIndent() + + val after = + """ + fun test() { + args.productUUID?.apply { + productUUID = this + } + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.NAPIER) + } + + myFixture.checkResult(after) + } + + fun testRemoveKotlinNapierLogsAlsoRemovesImport() { + val before = + """ + import io.github.aakira.napier.Napier + + fun test() { + Napier.d("some log", tag = "TestTag") + var x = 1 + } + """.trimIndent() + + val after = + """ + fun test() { + var x = 1 + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.NAPIER) + } + + myFixture.checkResult(after) + } + + fun testRemoveKotlinNapierLogsKeepsImportWhenOtherNapierLogsRemain() { + val before = + """ + import io.github.aakira.napier.Napier + + fun test() { + Napier.d("some log", tag = "TestTag") + Napier.d("other log", tag = "OtherTag") + } + """.trimIndent() + + val after = + """ + import io.github.aakira.napier.Napier + + fun test() { + Napier.d("other log", tag = "OtherTag") + } + """.trimIndent() + + val psiFile = myFixture.configureByText("Test.kt", before) as KtFile + + WriteCommandAction.runWriteCommandAction(project) { + service.removeLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.NAPIER) + } + + myFixture.checkResult(after) + } + fun testInsertTimberLogsWithImport() { - val before = """ + val before = + """ package com.example fun test() { var x = 1 x = 2 } - """.trimIndent() + """.trimIndent() - val after = """ + val after = + """ package com.example import timber.log.Timber @@ -256,10 +614,10 @@ class LogInserterServiceTest : BasePlatformTestCase() { x = 2 Timber.tag("TestTag").d("x assigned new value: ${'$'}{x}") } - """.trimIndent() + """.trimIndent() val psiFile = myFixture.configureByText("Test.kt", before) as KtFile - + WriteCommandAction.runWriteCommandAction(project) { service.insertKotlinAssignmentLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.TIMBER) } @@ -268,7 +626,8 @@ class LogInserterServiceTest : BasePlatformTestCase() { } fun testInsertTimberLogsWithExistingImport() { - val content = """ + val content = + """ package com.example import timber.log.Timber @@ -278,10 +637,10 @@ class LogInserterServiceTest : BasePlatformTestCase() { x = 2 Timber.tag("TestTag").d("x assigned new value: ${'$'}{x}") } - """.trimIndent() + """.trimIndent() val psiFile = myFixture.configureByText("Test.kt", content) as KtFile - + WriteCommandAction.runWriteCommandAction(project) { service.insertKotlinAssignmentLogs(psiFile, "TestTag", LoggingSettings.LoggingFramework.TIMBER) }