Skip to content

chore: add JetBrains auto-approval compliance linter #139

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

Merged
merged 3 commits into from
Jul 8, 2025
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
68 changes: 68 additions & 0 deletions .github/workflows/jetbrains-compliance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: JetBrains Auto-Approval Compliance

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]

jobs:
compliance-check:
runs-on: ubuntu-latest
name: JetBrains Compliance Linting

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'

- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Make gradlew executable
run: chmod +x ./gradlew

- name: Run JetBrains Compliance Checks
run: |
echo "Running JetBrains auto-approval compliance checks with detekt..."
./gradlew detekt

- name: Upload detekt reports
uses: actions/upload-artifact@v4
if: always()
with:
name: detekt-reports
path: |
build/reports/detekt/
retention-days: 30

- name: Comment PR with compliance status
if: github.event_name == 'pull_request' && failure()
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '⚠️ **JetBrains Auto-Approval Compliance Check Failed**\n\n' +
'This PR contains code that violates JetBrains auto-approval requirements:\n\n' +
'- ❌ Do **not** use forbidden Kotlin experimental APIs\n' +
'- ❌ Do **not** add lambdas, handlers, or class handles to Java runtime hooks\n' +
'- ❌ Do **not** create threads manually (use coroutines or ensure cleanup in `CoderRemoteProvider#close()`)\n' +
'- ❌ Do **not** bundle libraries already provided by Toolbox\n' +
'- ❌ Do **not** perform ill-intentioned actions\n\n' +
'Please check the workflow logs for detailed violations and fix them before merging.'
})
85 changes: 85 additions & 0 deletions JETBRAINS_COMPLIANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# JetBrains Auto-Approval Compliance

This document describes the linting setup to ensure compliance with JetBrains auto-approval requirements for Toolbox plugins.

## Overview

JetBrains has enabled auto-approval for this plugin, which requires following specific guidelines to maintain the approval status. This repository includes automated checks to ensure compliance.

## Requirements

Based on communication with JetBrains team, the following requirements must be met:

### βœ… Allowed
- **Coroutines**: Use `coroutineScope.launch` for concurrent operations
- **Library-managed threads**: Libraries like OkHttp with their own thread pools are acceptable
- **Some experimental coroutines APIs**: `kotlinx.coroutines.selects.select` and `kotlinx.coroutines.selects.onTimeout` are acceptable
- **Proper cleanup**: Ensure resources are released in `CoderRemoteProvider#close()` method

### ❌ Forbidden
- **Kotlin experimental APIs**: Core Kotlin experimental APIs (not coroutines-specific ones)
- **Java runtime hooks**: No lambdas, handlers, or class handles to Java runtime hooks
- **Manual thread creation**: Avoid `Thread()`, `Executors.new*()`, `ThreadPoolExecutor`, etc.
- **Bundled libraries**: Don't bundle libraries already provided by Toolbox
- **Ill-intentioned actions**: No malicious or harmful code

## Linting Setup

### JetBrains Compliance with Detekt

The primary compliance checking is done using Detekt with custom configuration in `detekt.yml`:

```bash
./gradlew detekt
```

This configuration includes JetBrains-specific rules that check for:
- **ForbiddenAnnotation**: Detects forbidden experimental API usage
- **ForbiddenMethodCall**: Detects Java runtime hooks and manual thread creation
- **ForbiddenImport**: Detects potentially bundled libraries
- **Standard code quality rules**: Complexity, naming, performance, etc.



## CI/CD Integration

The GitHub Actions workflow `.github/workflows/jetbrains-compliance.yml` runs compliance checks on every PR and push.

## Running Locally

```bash
# Run JetBrains compliance and code quality check
./gradlew detekt

# View HTML report
open build/reports/detekt/detekt.html
```



## Understanding Results

### Compliance Check Results

- **βœ… No critical violations**: Code complies with JetBrains requirements
- **❌ Critical violations**: Must be fixed before auto-approval
- **⚠️ Warnings**: Should be reviewed but may be acceptable

### Common Warnings

1. **Manual thread creation**: If you see warnings about thread creation:
- Prefer coroutines: `coroutineScope.launch { ... }`
- If using libraries with threads, ensure cleanup in `close()`

2. **Library imports**: If you see warnings about library imports:
- Verify the library isn't bundled in the final plugin
- Check that Toolbox doesn't already provide the library

3. **GlobalScope usage**: If you see warnings about `GlobalScope`:
- Use the coroutine scope provided by Toolbox instead

## Resources

- [JetBrains Toolbox Plugin Development](https://plugins.jetbrains.com/docs/toolbox/)
- [Detekt Documentation](https://detekt.dev/)
- [Kotlin Coroutines Guide](https://kotlinlang.org/docs/coroutines-guide.html)
19 changes: 19 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ plugins {
alias(libs.plugins.gradle.wrapper)
alias(libs.plugins.changelog)
alias(libs.plugins.gettext)
alias(libs.plugins.detekt)
}


Expand Down Expand Up @@ -110,6 +111,24 @@ tasks.test {
useJUnitPlatform()
}

// Detekt configuration for JetBrains compliance and code quality
detekt {
config.setFrom("$projectDir/detekt.yml")
buildUponDefaultConfig = true
allRules = false
}

// Configure detekt for JetBrains compliance and code quality
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: we can do a static import here.

jvmTarget = "21"
reports {
html.required.set(true)
xml.required.set(true)
}
// Fail build on detekt issues for JetBrains compliance
ignoreFailures = false
}


tasks.jar {
archiveBaseName.set(extension.id)
Expand Down
204 changes: 204 additions & 0 deletions detekt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# Detekt configuration for JetBrains Toolbox Plugin Auto-Approval Compliance
# Based on clarified requirements from JetBrains team

build:
maxIssues: 1000 # Allow many issues for code quality reporting
excludeCorrectable: false

config:
validation: true
warningsAsErrors: false # Don't treat warnings as errors
checkExhaustiveness: false

# CRITICAL: JetBrains Compliance Rules using detekt built-in rules
style:
active: true

# JetBrains Auto-Approval Compliance: Forbidden experimental annotations
ForbiddenAnnotation:
active: true
annotations:
- reason: 'Forbidden for JetBrains auto-approval: Core Kotlin experimental APIs are not allowed'
value: 'kotlin.ExperimentalStdlibApi'
- reason: 'Forbidden for JetBrains auto-approval: Core Kotlin experimental APIs are not allowed'
value: 'kotlin.ExperimentalUnsignedTypes'
- reason: 'Forbidden for JetBrains auto-approval: Core Kotlin experimental APIs are not allowed'
value: 'kotlin.contracts.ExperimentalContracts'
- reason: 'Forbidden for JetBrains auto-approval: Core Kotlin experimental APIs are not allowed'
value: 'kotlin.experimental.ExperimentalTypeInference'
- reason: 'Forbidden for JetBrains auto-approval: Internal coroutines APIs should be avoided'
value: 'kotlinx.coroutines.InternalCoroutinesApi'
- reason: 'Forbidden for JetBrains auto-approval: Experimental time APIs are not allowed'
value: 'kotlin.time.ExperimentalTime'
# Note: ExperimentalCoroutinesApi, DelicateCoroutinesApi, FlowPreview are acceptable
# based on JetBrains feedback about select/onTimeout being OK

# JetBrains Auto-Approval Compliance: Forbidden method calls
ForbiddenMethodCall:
active: true
methods:
# Java runtime hooks - forbidden
- reason: 'Forbidden for JetBrains auto-approval: Java runtime hooks are not allowed'
value: 'java.lang.Runtime.addShutdownHook'
- reason: 'Forbidden for JetBrains auto-approval: Java runtime hooks are not allowed'
value: 'java.lang.System.setSecurityManager'
- reason: 'Forbidden for JetBrains auto-approval: Java runtime hooks are not allowed'
value: 'java.lang.Thread.setUncaughtExceptionHandler'
- reason: 'Forbidden for JetBrains auto-approval: Java runtime hooks are not allowed'
value: 'java.lang.Thread.setDefaultUncaughtExceptionHandler'
# Manual thread creation - warnings (allowed with proper cleanup)
- reason: 'Warning for JetBrains auto-approval: Manual thread creation detected. Consider using coroutineScope.launch or ensure proper cleanup in CoderRemoteProvider#close()'
value: 'java.lang.Thread.<init>'
- reason: 'Warning for JetBrains auto-approval: Manual thread creation detected. Consider using coroutineScope.launch or ensure proper cleanup in CoderRemoteProvider#close()'
value: 'java.util.concurrent.Executors.newFixedThreadPool'
- reason: 'Warning for JetBrains auto-approval: Manual thread creation detected. Consider using coroutineScope.launch or ensure proper cleanup in CoderRemoteProvider#close()'
value: 'java.util.concurrent.Executors.newCachedThreadPool'
- reason: 'Warning for JetBrains auto-approval: Manual thread creation detected. Consider using coroutineScope.launch or ensure proper cleanup in CoderRemoteProvider#close()'
value: 'java.util.concurrent.Executors.newSingleThreadExecutor'
- reason: 'Warning for JetBrains auto-approval: Manual thread creation detected. Consider using coroutineScope.launch or ensure proper cleanup in CoderRemoteProvider#close()'
value: 'java.util.concurrent.CompletableFuture.runAsync'
- reason: 'Warning for JetBrains auto-approval: Manual thread creation detected. Consider using coroutineScope.launch or ensure proper cleanup in CoderRemoteProvider#close()'
value: 'java.util.concurrent.CompletableFuture.supplyAsync'

# JetBrains Auto-Approval Compliance: Forbidden imports
ForbiddenImport:
active: true
imports:
# Potentially bundled libraries - warnings
- reason: 'Warning for JetBrains auto-approval: Ensure slf4j is not bundled - it is provided by Toolbox'
value: 'org.slf4j.*'
- reason: 'Warning for JetBrains auto-approval: Ensure annotations library is not bundled - it is provided by Toolbox'
value: 'org.jetbrains.annotations.*'
# Runtime hook classes - forbidden
- reason: 'Forbidden for JetBrains auto-approval: Runtime hook classes are not allowed'
value: 'java.lang.Runtime'
- reason: 'Forbidden for JetBrains auto-approval: Security manager modifications are not allowed'
value: 'java.security.SecurityManager'

# Other important style rules
MagicNumber:
active: true
ignoreNumbers:
- '-1'
- '0'
- '1'
- '2'
ignoreHashCodeFunction: true
ignorePropertyDeclaration: false
ignoreLocalVariableDeclaration: false
ignoreConstantDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotation: false
ignoreNamedArgument: true
ignoreEnums: false
ignoreRanges: false
ignoreExtensionFunctions: true

MaxLineLength:
active: true
maxLineLength: 120
excludePackageStatements: true
excludeImportStatements: true
excludeCommentStatements: false

NewLineAtEndOfFile:
active: true

WildcardImport:
active: true

# Essential built-in rules for basic code quality
complexity:
active: true
CyclomaticComplexMethod:
active: true
threshold: 15
LongMethod:
active: true
threshold: 60
LongParameterList:
active: true
functionThreshold: 6
constructorThreshold: 7
NestedBlockDepth:
active: true
threshold: 4

coroutines:
active: true
GlobalCoroutineUsage:
active: true
RedundantSuspendModifier:
active: true
SleepInsteadOfDelay:
active: true

exceptions:
active: true
ExceptionRaisedInUnexpectedLocation:
active: true
ObjectExtendsThrowable:
active: true
PrintStackTrace:
active: true
ReturnFromFinally:
active: true
SwallowedException:
active: true
ThrowingExceptionFromFinally:
active: true
ThrowingExceptionsWithoutMessageOrCause:
active: true
TooGenericExceptionCaught:
active: true
TooGenericExceptionThrown:
active: true

naming:
active: true
ClassNaming:
active: true
classPattern: '[A-Z][a-zA-Z0-9]*'
FunctionNaming:
active: true
functionPattern: '[a-z][a-zA-Z0-9]*'
PackageNaming:
active: true
packagePattern: '[a-z]+(\.?[a-z][A-Za-z0-9]*)*'
VariableNaming:
active: true
variablePattern: '[a-z][A-Za-z0-9]*'

performance:
active: true
ArrayPrimitive:
active: true
ForEachOnRange:
active: true
SpreadOperator:
active: true
UnnecessaryTemporaryInstantiation:
active: true

potential-bugs:
active: true
EqualsAlwaysReturnsTrueOrFalse:
active: true
EqualsWithHashCodeExist:
active: true
ExplicitGarbageCollectionCall:
active: true
HasPlatformType:
active: true
InvalidRange:
active: true
UnreachableCatchBlock:
active: true
UnreachableCode:
active: true
UnsafeCallOnNullableType:
active: true
UnsafeCast:
active: true
WrongEqualsTypeParameter:
active: true
Loading
Loading