Skip to content
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
6 changes: 2 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ on:
jobs:
build:
runs-on: ubuntu-latest
env:
INTELLIJ_VERSION: '2024.2'

steps:
- name: Checkout repository
Expand All @@ -38,13 +40,9 @@ jobs:

- name: Build plugin
run: ./gradlew buildPlugin
env:
INTELLIJ_VERSION: '2023.1'

- name: Verify plugin
run: ./gradlew runPluginVerifier
env:
INTELLIJ_VERSION: '2023.1'

- name: Upload plugin artifact
uses: actions/upload-artifact@v4
Expand Down
47 changes: 39 additions & 8 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ plugins {
id 'org.jetbrains.intellij' version '1.17.0'
}

group 'com.github.tky0065'
version '1.0.0'
group = 'com.github.tky0065'
version = '1.0.1'

repositories {
mavenCentral()
Expand All @@ -19,20 +19,51 @@ dependencies {
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
}

// Définition explicite de la version d'IntelliJ pour éviter toute confusion
def intellijVersion = System.getenv("INTELLIJ_VERSION") ?: "2024.2"
def intellijType = System.getenv("INTELLIJ_TYPE") ?: "IC" // IC pour Community, IU pour Ultimate

intellij {
version = '2023.1'
type = 'IC' // IntelliJ IDEA Community Edition
plugins = ['java']
version.set(intellijVersion) // Utilisation de la méthode set() pour éviter les conflits
type.set(intellijType) // Configurable via variable d'environnement
plugins.set(['java'])
updateSinceUntilBuild.set(true)
downloadSources.set(true)
}

// Configuration pour supprimer les avertissements lors du build
buildSearchableOptions {
enabled = false // Désactive la génération des options searchable qui produit des avertissements
}

runIde {
// Supprime les avertissements concernant JCEF en mode headless
systemProperty("ide.browser.jcef.headless.enabled", "true")

// Définit explicitement la version de JDK pour éviter les problèmes avec Java 25
jvmArgs("-Djdk.module.illegalAccess.silent=true")

// Ignorer les avertissements liés à la compatibilité Gradle
systemProperty("gradle.skip.compatibility.check", "true")

// Éviter les mises à jour automatiques qui peuvent causer des avertissements
systemProperty("ide.plugins.snapshot.on.unload.disable", "true")
}

// Configuration pour la publication du plugin
publishPlugin {
token.set(System.getenv("PUBLISH_TOKEN") ?: "")
channels.set(System.getenv("PUBLISH_CHANNEL") ? [System.getenv("PUBLISH_CHANNEL")] : ["default"])
}

patchPluginXml {
changeNotes.set("""
<ul>
<li>1.0.0 - Version initiale du générateur d'API</li>
<li>1.0.1 - Version initiale du générateur d'API</li>
</ul>
""")
sinceBuild.set("231") // Compatible avec 2023.1+
untilBuild.set("233.*") // Compatible jusqu'à 2023.3.x
sinceBuild.set("242") // Compatible depuis IntelliJ 2024.2
untilBuild.set("252.*") // Compatible jusqu'à 2025.2.x inclus
}

test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.openapi.vfs.VirtualFile;

import org.jetbrains.annotations.NotNull;

import javax.swing.*;
Expand Down Expand Up @@ -54,6 +56,7 @@ public GenerateApiAction() {
this.existingFileService = new ExistingFileServiceImpl(loggingService);
}


@Override
public void update(@NotNull AnActionEvent e) {
// Active ou désactive l'action en fonction du contexte
Expand Down Expand Up @@ -469,8 +472,13 @@ private FileAction showFileExistsDialog(Project project, String packageName, Str
* Crée récursivement les répertoires nécessaires pour un package.
*/
private PsiDirectory createPackageDirectories(Project project, String packageName) {
PsiDirectory baseDir = PsiManager.getInstance(project).findDirectory(
project.getBaseDir());
// Utilisation de ProjectUtil.guessProjectDir au lieu de project.getBaseDir()
VirtualFile projectDir = com.intellij.openapi.project.ProjectUtil.guessProjectDir(project);
if (projectDir == null) {
throw new IllegalStateException("Impossible de trouver le répertoire de base du projet");
}

PsiDirectory baseDir = PsiManager.getInstance(project).findDirectory(projectDir);
if (baseDir == null) {
throw new IllegalStateException("Impossible de trouver le répertoire de base du projet");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,7 @@ private void addGetAllMethod(TypeSpec.Builder classBuilder, TypeName entityType,
.addModifiers(Modifier.PUBLIC)
.addAnnotation(getMappingAnnotation)
.returns(returnType)
.addStatement("List<$T> entities = service.findAll()", entityType)
.addStatement("// Ici, vous devez convertir les entités en DTOs si nécessaire")
.addStatement("return $T.ok(entities)", ClassName.get("org.springframework.http", "ResponseEntity"))
.addStatement("return $T.ok(service.findAll())", ClassName.get("org.springframework.http", "ResponseEntity"))
.build();

classBuilder.addMethod(getAllMethod);
Expand All @@ -222,17 +220,19 @@ private void addGetByIdMethod(TypeSpec.Builder classBuilder, TypeName entityType
.build();

ClassName pathVariable = ClassName.get("org.springframework.web.bind.annotation", "PathVariable");
ClassName responseEntityClass = ClassName.get("org.springframework.http", "ResponseEntity");

// Générer le code avec une seule instruction pour éviter les problèmes de formatage
MethodSpec getByIdMethod = MethodSpec.methodBuilder("getById")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(getMappingAnnotation)
.addParameter(ParameterSpec.builder(idType, "id")
.addAnnotation(pathVariable)
.build())
.returns(returnType)
.addStatement("return service.findById(id)")
.addStatement(" .map(entity -> $T.ok(entity))", ClassName.get("org.springframework.http", "ResponseEntity"))
.addStatement(" .orElseGet(() -> $T.notFound().build())", ClassName.get("org.springframework.http", "ResponseEntity"))
.addCode("return service.findById(id)\n")
.addCode(" .map($T::ok)\n", responseEntityClass)
.addCode(" .orElseGet(() -> $T.notFound().build());\n", responseEntityClass)
.build();

classBuilder.addMethod(getByIdMethod);
Expand All @@ -256,11 +256,9 @@ private void addCreateMethod(TypeSpec.Builder classBuilder, TypeName entityType,
.addAnnotation(requestBody)
.build())
.returns(returnType)
.addStatement("// Ici, vous devez convertir le DTO en entité si nécessaire")
.addStatement("$T savedEntity = service.save(($T) dto)", entityType, entityType)
.addStatement("// Puis reconvertir en DTO pour la réponse")
.addStatement("return $T.created(null).body(($T) savedEntity)",
ClassName.get("org.springframework.http", "ResponseEntity"), dtoType)
.addStatement("$T savedDto = service.save(dto)", dtoType)
.addStatement("return $T.created(null).body(savedDto)",
ClassName.get("org.springframework.http", "ResponseEntity"))
.build();

classBuilder.addMethod(createMethod);
Expand All @@ -281,7 +279,9 @@ private void addUpdateMethod(TypeSpec.Builder classBuilder, TypeName entityType,

ClassName pathVariable = ClassName.get("org.springframework.web.bind.annotation", "PathVariable");
ClassName requestBody = ClassName.get("org.springframework.web.bind.annotation", "RequestBody");
ClassName responseEntityClass = ClassName.get("org.springframework.http", "ResponseEntity");

// Générer le code avec une structure plus explicite pour éviter les problèmes de formatage
MethodSpec updateMethod = MethodSpec.methodBuilder("update")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(putMappingAnnotation)
Expand All @@ -292,15 +292,13 @@ private void addUpdateMethod(TypeSpec.Builder classBuilder, TypeName entityType,
.addAnnotation(requestBody)
.build())
.returns(returnType)
.addStatement("return service.findById(id)")
.addStatement(" .map(existingEntity -> {")
.addStatement(" // Ici, mettre à jour l'entité existante avec les valeurs du DTO")
.addStatement(" $T updatedEntity = service.save(existingEntity)", entityType)
.addStatement(" return $T.ok(($T) updatedEntity)",
ClassName.get("org.springframework.http", "ResponseEntity"), dtoType)
.addStatement(" })")
.addStatement(" .orElseGet(() -> $T.notFound().build())",
ClassName.get("org.springframework.http", "ResponseEntity"))
.addCode("return service.findById(id)\n")
.addCode(" .map(existingDto -> {\n")
.addCode(" // Ici, vous pouvez copier les champs modifiables de dto vers existingDto si besoin\n")
.addCode(" $T updatedDto = service.save(dto);\n", dtoType)
.addCode(" return $T.ok(updatedDto);\n", responseEntityClass)
.addCode(" })\n")
.addCode(" .orElseGet(() -> $T.notFound().build());\n", responseEntityClass)
.build();

classBuilder.addMethod(updateMethod);
Expand Down Expand Up @@ -329,8 +327,7 @@ private void addDeleteMethod(TypeSpec.Builder classBuilder, TypeName idType) {
.build())
.returns(returnType)
.addStatement("service.deleteById(id)")
.addStatement("return $T.noContent().build()",
ClassName.get("org.springframework.http", "ResponseEntity"))
.addStatement("return $T.noContent().build()", ClassName.get("org.springframework.http", "ResponseEntity"))
.build();

classBuilder.addMethod(deleteMethod);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class DependencyValidationServiceImpl implements DependencyValidationServ
"JPA Entity", "<dependency>\n <groupId>jakarta.persistence</groupId>\n <artifactId>jakarta.persistence-api</artifactId>\n <version>3.1.0</version>\n</dependency>",
"Spring Data JPA", "<dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-data-jpa</artifactId>\n</dependency>",
"Spring Web", "<dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-web</artifactId>\n</dependency>",
"MapStruct", "<dependency>\n <groupId>org.mapstruct</groupId>\n <artifactId>mapstruct</artifactId>\n <version>1.5.3.Final</version>\n</dependency>\n<dependency>\n <groupId>org.mapstruct</groupId>\n <artifactId>mapstruct-processor</artifactId>\n <version>1.5.3.Final</version>\n <scope>provided</scope>\n</dependency>",
"MapStruct", "<dependency>\n <groupId>org.mapstruct</groupId>\n <artifactId>mapstruct</artifactId>\n <version>1.6.3.Final</version>\n</dependency>\n<dependency>\n <groupId>org.mapstruct</groupId>\n <artifactId>mapstruct-processor</artifactId>\n <version>1.6.3.Final</version>\n <scope>provided</scope>\n</dependency>",
"Lombok", "<dependency>\n <groupId>org.projectlombok</groupId>\n <artifactId>lombok</artifactId>\n <version>1.18.28</version>\n <scope>provided</scope>\n</dependency>"
);

Expand All @@ -47,7 +47,7 @@ public class DependencyValidationServiceImpl implements DependencyValidationServ
"JPA Entity", "implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'",
"Spring Data JPA", "implementation 'org.springframework.boot:spring-boot-starter-data-jpa'",
"Spring Web", "implementation 'org.springframework.boot:spring-boot-starter-web'",
"MapStruct", "implementation 'org.mapstruct:mapstruct:1.5.3.Final'\nannotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'",
"MapStruct", "implementation 'org.mapstruct:mapstruct:1.6.3'\nannotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3'",
"Lombok", "compileOnly 'org.projectlombok:lombok:1.18.28'\nannotationProcessor 'org.projectlombok:lombok:1.18.28'"
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ public String getGeneratedPackageName(EntityModel entityModel, ApiGeneratorConfi
}

private void addField(TypeSpec.Builder classBuilder, String name, TypeName typeName, boolean useLombok) {
// Vérifier si un champ avec ce nom existe déjà pour éviter les doublons
for (FieldSpec field : classBuilder.fieldSpecs) {
if (field.name.equals(name)) {
return; // Ne pas ajouter de doublons
}
}

FieldSpec.Builder fieldBuilder = FieldSpec.builder(typeName, name, Modifier.PRIVATE);
classBuilder.addField(fieldBuilder.build());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,13 @@
import com.github.tky0065.apigenerator.service.ExistingFileService;
import com.github.tky0065.apigenerator.service.LoggingService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -147,20 +143,24 @@ private String getFileKey(String packageName, String className) {
/**
* Trouve un fichier Java dans le projet.
*/

private PsiFile findFile(Project project, String packageName, String className) {
// Convertir le package en chemin de répertoire
String packagePath = packageName.replace('.', '/');

// Rechercher dans les répertoires sources du projet
PsiManager psiManager = PsiManager.getInstance(project);
VirtualFile baseDir = project.getBaseDir();

// Récupérer le répertoire racine du projet
VirtualFile projectDir = com.intellij.openapi.project.ProjectUtil.guessProjectDir(project);
if (projectDir == null) {
return null;
}

PsiDirectory baseDir = psiManager.findDirectory(projectDir);
if (baseDir == null) {
return null;
}

// Chercher dans src/main/java
VirtualFile srcDir = baseDir.findFileByRelativePath("src/main/java");
VirtualFile srcDir = projectDir.findFileByRelativePath("src/main/java");
if (srcDir != null) {
VirtualFile packageDir = srcDir.findFileByRelativePath(packagePath);
if (packageDir != null) {
Expand All @@ -174,6 +174,7 @@ private PsiFile findFile(Project project, String packageName, String className)
return null;
}


/**
* Ajoute une signature à un fichier généré.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public String generateCode(Project project, EntityModel entityModel, ApiGenerato
ClassName repositoryAnnotation = ClassName.get("org.springframework.stereotype", "Repository");
interfaceBuilder.addAnnotation(repositoryAnnotation);

// Ajouter des méthodes de recherche personnalisées basées sur les champs de l'entité
addCustomQueryMethods(interfaceBuilder, entityModel);
// Ne pas générer de méthodes personnalisées pour éviter les doublons
// Spring Data JPA génère déjà automatiquement les méthodes de base

// Créer le fichier Java
JavaFile javaFile = JavaFile.builder(getGeneratedPackageName(entityModel, config), interfaceBuilder.build())
Expand Down Expand Up @@ -77,61 +77,6 @@ private TypeName getIdTypeName(EntityModel entityModel) {
return ClassName.get("java.lang", "Long");
}

/**
* Ajoute des méthodes de recherche personnalisées basées sur les champs de l'entité.
*/
private void addCustomQueryMethods(TypeSpec.Builder interfaceBuilder, EntityModel entityModel) {
// Ajouter findBy... pour les champs importants (non-collection, non-transient, etc.)
for (EntityModel.EntityField field : entityModel.getFields()) {
// Ignorer les champs qui ne sont pas adaptés pour les requêtes
if (field.isTransient() || field.isCollection()) {
continue;
}

// Si c'est un champ de type String, ajouter une méthode findByFieldContainingIgnoreCase
if ("String".equals(field.getType())) {
String methodName = "findBy" + capitalizeFirstLetter(field.getName()) + "ContainingIgnoreCase";

TypeName returnType = ParameterizedTypeName.get(
ClassName.get("java.util", "List"),
ClassName.get(entityModel.getPackageName(), entityModel.getClassName())
);

MethodSpec method = MethodSpec.methodBuilder(methodName)
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.returns(returnType)
.addParameter(String.class, field.getName())
.build();

interfaceBuilder.addMethod(method);
}
// Pour les autres types, ajouter une méthode findByField
else {
String methodName = "findBy" + capitalizeFirstLetter(field.getName());

TypeName returnType = ParameterizedTypeName.get(
ClassName.get("java.util", "List"),
ClassName.get(entityModel.getPackageName(), entityModel.getClassName())
);

MethodSpec method = MethodSpec.methodBuilder(methodName)
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.returns(returnType)
.addParameter(determineTypeName(field.getType()), field.getName())
.build();

interfaceBuilder.addMethod(method);
}
}
}

private String capitalizeFirstLetter(String input) {
if (input == null || input.isEmpty()) {
return input;
}
return input.substring(0, 1).toUpperCase() + input.substring(1);
}

private TypeName determineTypeName(String type) {
switch (type) {
case "int": return TypeName.INT;
Expand Down
Loading
Loading