casid / jte-intellij

IntelliJ plugin for jte template files.
https://github.com/casid/jte
Apache License 2.0
23 stars 5 forks source link

Errors on import statements in IntelliJ 2024.3 #53

Open schowave opened 1 week ago

schowave commented 1 week ago

I am not sure if this is the right place for a bug report, but since updating to IntelliJ 2024.3, I have suddenly started receiving error messages regarding imports in the .kte files.

The error message reads: “Package directive and imports are forbidden in code fragments”

Bildschirmfoto 2024-11-15 um 08 52 16

Is this an IntelliJ bug, or is it a syntax issue with JTE?

Thank you for your support!

casid commented 4 days ago

Thank you for reporting this @schowave.

Are you maybe using K2 mode in IntelliJ? There are currently problems with K2 and the way kte templates work (see https://github.com/casid/jte-intellij/issues/49)

schowave commented 4 days ago

Oh, you are right, then this issue is a duplicate. Sorry for the inconvenience!

casid commented 3 days ago

No worries at all!

schowave commented 1 day ago

Claude-3.5-Sonnet says: (This is just a output of my question how to fix the import statements...)

1 Modify the KTE file parsing: You'll need to change how the plugin handles KTE files, especially the @import statements. The goal is to prevent these statements from being treated as regular Kotlin code fragments.
2 Update the KtePsiFile class: In the src/main/java/org/jusecase/jte/intellij/language/psi/KtePsiFile.java file, you might need to override methods that deal with imports. You may need to implement a custom way of handling imports that doesn't conflict with K2's expectations.
3 Implement a custom KteLanguageInjector: Create a new class that extends LanguageInjector to handle how Kotlin code is injected into KTE files. This injector should handle @import statements separately from the main code body.
4 Update the KteLanguage class: In src/main/java/org/jusecase/jte/intellij/language/KteLanguage.java, you might need to register your custom language injector.
5 Modify the KteElementType: In src/main/java/org/jusecase/jte/intellij/language/parsing/KteElementType.java, you might need to create a special element type for imports that won't be processed as regular Kotlin code.

1 First, let's modify the KtePsiFile class to handle imports differently:

 public class KtePsiFile extends PsiFileBase {                                                                                                                                                                                                                                                                       
     private List<PsiElement> importStatements;                                                                                                                                                                                                                                                                      

     // ... existing constructor and methods ...                                                                                                                                                                                                                                                                     

     public List<PsiElement> getImportStatements() {                                                                                                                                                                                                                                                                 
         if (importStatements == null) {                                                                                                                                                                                                                                                                             
             importStatements = new ArrayList<>();                                                                                                                                                                                                                                                                   
             // Parse the file and collect @import statements                                                                                                                                                                                                                                                        
             // This is a simplified example; you'll need to implement proper parsing                                                                                                                                                                                                                                
             for (PsiElement child : getChildren()) {                                                                                                                                                                                                                                                                
                 if (child.getText().startsWith("@import")) {                                                                                                                                                                                                                                                        
                     importStatements.add(child);                                                                                                                                                                                                                                                                    
                 }                                                                                                                                                                                                                                                                                                   
             }                                                                                                                                                                                                                                                                                                       
         }                                                                                                                                                                                                                                                                                                           
         return importStatements;                                                                                                                                                                                                                                                                                    
     }                                                                                                                                                                                                                                                                                                               

     @Override                                                                                                                                                                                                                                                                                                       
     public void subtreeChanged() {                                                                                                                                                                                                                                                                                  
         super.subtreeChanged();                                                                                                                                                                                                                                                                                     
         importStatements = null; // Reset cache when file changes                                                                                                                                                                                                                                                   
     }                                                                                                                                                                                                                                                                                                               
 }                                

2 Next, create a new KteKotlinLanguageInjector class:

package org.jusecase.jte.intellij.language;                                                                                                                                                                                                                                                                         

 import com.intellij.lang.injection.MultiHostInjector;                                                                                                                                                                                                                                                               
 import com.intellij.lang.injection.MultiHostRegistrar;                                                                                                                                                                                                                                                              
 import com.intellij.openapi.util.TextRange;                                                                                                                                                                                                                                                                         
 import com.intellij.psi.PsiElement;                                                                                                                                                                                                                                                                                 
 import com.intellij.psi.PsiLanguageInjectionHost;                                                                                                                                                                                                                                                                   
 import org.jetbrains.annotations.NotNull;                                                                                                                                                                                                                                                                           
 import org.jetbrains.kotlin.idea.KotlinLanguage;                                                                                                                                                                                                                                                                    
 import org.jusecase.jte.intellij.language.psi.KtePsiFile;                                                                                                                                                                                                                                                           

 import java.util.Collections;                                                                                                                                                                                                                                                                                       
 import java.util.List;                                                                                                                                                                                                                                                                                              

 public class KteKotlinLanguageInjector implements MultiHostInjector {                                                                                                                                                                                                                                               
     @Override                                                                                                                                                                                                                                                                                                       
     public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) {                                                                                                                                                                                                          
         if (!(context instanceof KtePsiFile)) return;                                                                                                                                                                                                                                                               

         KtePsiFile kteFile = (KtePsiFile) context;                                                                                                                                                                                                                                                                  
         List<PsiElement> imports = kteFile.getImportStatements();                                                                                                                                                                                                                                                   

         // Inject Kotlin language for the main content, excluding imports                                                                                                                                                                                                                                           
         TextRange contentRange = getContentRange(kteFile, imports);                                                                                                                                                                                                                                                 
         registrar.startInjecting(KotlinLanguage.INSTANCE)                                                                                                                                                                                                                                                           
                  .addPlace(null, null, (PsiLanguageInjectionHost) context, contentRange)                                                                                                                                                                                                                            
                  .doneInjecting();                                                                                                                                                                                                                                                                                  

         // Handle imports separately (optional, depending on how you want to process them)                                                                                                                                                                                                                          
         for (PsiElement importStatement : imports) {                                                                                                                                                                                                                                                                
             registrar.startInjecting(KteLanguage.INSTANCE)                                                                                                                                                                                                                                                          
                      .addPlace(null, null, (PsiLanguageInjectionHost) context, importStatement.getTextRange())                                                                                                                                                                                                      
                      .doneInjecting();                                                                                                                                                                                                                                                                              
         }                                                                                                                                                                                                                                                                                                           
     }                                                                                                                                                                                                                                                                                                               

     private TextRange getContentRange(KtePsiFile file, List<PsiElement> imports) {                                                                                                                                                                                                                                  
         int start = 0;                                                                                                                                                                                                                                                                                              
         for (PsiElement importStmt : imports) {                                                                                                                                                                                                                                                                     
             start = Math.max(start, importStmt.getTextRange().getEndOffset());                                                                                                                                                                                                                                      
         }                                                                                                                                                                                                                                                                                                           
         return new TextRange(start, file.getTextLength());                                                                                                                                                                                                                                                          
     }                                                                                                                                                                                                                                                                                                               

     @NotNull                                                                                                                                                                                                                                                                                                        
     @Override                                                                                                                                                                                                                                                                                                       
     public List<? extends Class<? extends PsiElement>> elementsToInjectIn() {                                                                                                                                                                                                                                       
         return Collections.singletonList(KtePsiFile.class);                                                                                                                                                                                                                                                         
     }                                                                                                                                                                                                                                                                                                               
 }                                                                 

3 Update the plugin.xml file to register the new injector:

Add this line within the section:

<multiHostInjector implementation="org.jusecase.jte.intellij.language.KteKotlinLanguageInjector"/>

4 You might also need to update the KteLanguage class to ensure it's not treated as a regular Kotlin file:

 public class KteLanguage extends Language implements TemplateLanguage {                                                                                                                                                                                                                                             
     // ... existing code ...                                                                                                                                                                                                                                                                                        

     @Override                                                                                                                                                                                                                                                                                                       
     public boolean isCaseSensitive() {                                                                                                                                                                                                                                                                              
         return true;                                                                                                                                                                                                                                                                                                
     }                                                                                                                                                                                                                                                                                                               

     @Override                                                                                                                                                                                                                                                                                                       
     public boolean isKindOf(Language another) {                                                                                                                                                                                                                                                                     
         return super.isKindOf(another) || another == KotlinLanguage.INSTANCE;                                                                                                                                                                                                                                       
     }                                                                                                                                                                                                                                                                                                               
 }                       

These changes should help address the K2 compiler issue by:

1 Separating the handling of @import statements from the main Kotlin code.
2 Injecting Kotlin language only for the main content, excluding imports.
3 Optionally handling imports separately within the KTE language context.